Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

### Dependencies and maintenance

- Build model resources once from resolved run intent; remove intermediate factories while keeping selection separate from provider, metrics, and stream state.
- Make Options use one element collection and declarative checkbox bindings; share settings normalization between load and save without mixing legacy migrations or managed policy into persistence.
- Share page-media resolution and embedded transcript composition across HTML and Firecrawl extraction, keeping source-specific metadata, Markdown, and timeout policies separate.
- Delete the unreachable daemon chat pipeline and unused browser media adapters; keep chat on the agent endpoint and audio on the bounded chunked decoder.
Expand Down
2 changes: 2 additions & 0 deletions docs/engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ The user-facing error text remains descriptive, but it is not a control-flow con

## Adapter ownership

Model selection resolves intent without creating process resources. `src/application/model-runtime.ts` then builds metrics, provider bindings, and the executable model in one factory; URL and asset flows share those same instances. Execution resources compose the flow contexts rather than rebuilding model options through intermediate factories.

`src/application/flow-contexts.ts` composes asset and URL contexts from the same IO, flags, model, and runtime hooks. Asset execution receives its flat context directly; `assetFormat` is the explicit asset-only override. Fetch tracking, shared caches, and summary-cache notifications stay wired at this application boundary.

Adapters own:
Expand Down
117 changes: 17 additions & 100 deletions src/application/execution-resources.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import type { CacheState } from "../cache.js";
import type { MediaCache } from "../content/index.js";
import type { SummaryStreamHandler } from "../engine/events.js";
import type { ModelExecutorDeps } from "../engine/model-executor.js";
import type { ExecFileFn } from "../markitdown.js";
import { executeAssetSummary } from "../run/flows/asset/summary.js";
import type { AssetSummaryContext } from "../run/flows/asset/types.js";
import type {
Expand All @@ -14,23 +11,10 @@ import type {
import type { PerfTrace } from "../run/perf-trace.js";
import { scopeTranscriptCacheForDiarization } from "../shared/transcript-diarization-cache-scope.js";
import { createRunFlowContexts } from "./flow-contexts.js";
import {
createExecutableRunModel,
createRunModelRuntime,
type ExecutableRunModel,
type ModelExecutorRequestOptions,
type RunModelRuntime,
} from "./model-runtime.js";
import type { ResolvedSummarizeRun, ResolvedSummarizeSpec } from "./run-spec.js";
import { createSummarizeModelResources, type SummarizeModelResources } from "./model-runtime.js";
import type { ResolvedSummarizeSpec } from "./run-spec.js";
import type { SummarizeEventSink } from "./summarize-contracts.js";

export type SummarizeModelResources = {
context: ResolvedSummarizeRun["bindings"]["context"];
envForRun: Record<string, string | undefined>;
runtime: RunModelRuntime;
model: ExecutableRunModel;
};

type SummarizeFlowAdapterHooks = Pick<
UrlFlowRuntimeHooks,
| "writeViaFooter"
Expand Down Expand Up @@ -193,92 +177,25 @@ export function createSummarizeFlowFlags(
};
}

export function createSummarizeModelResources(options: {
resolvedRun: ResolvedSummarizeRun;
env: Record<string, string | undefined>;
metricsEnv?: Record<string, string | undefined>;
fetchImpl: typeof fetch;
execFileImpl: ExecFileFn;
streamingEnabled: boolean;
summaryStream: SummaryStreamHandler | null;
requestOptions?: ModelExecutorRequestOptions;
log?: ModelExecutorDeps["log"];
trace?: ModelExecutorDeps["trace"];
}): SummarizeModelResources {
const {
resolvedRun,
env,
metricsEnv = env,
fetchImpl,
execFileImpl,
streamingEnabled,
summaryStream,
requestOptions,
log,
trace,
} = options;
const { context, envForRun } = resolvedRun.bindings;
const runtime = createRunModelRuntime({
context,
env,
envForRun,
metricsEnv,
fetchImpl,
execFileImpl,
maxOutputTokensArg: resolvedRun.spec.maxOutputTokensArg,
timeoutMs: resolvedRun.spec.timeoutMs,
retries: resolvedRun.spec.retries,
streamingEnabled,
requestOptions,
log,
trace,
});
const model = createExecutableRunModel({
spec: resolvedRun.bindings.model,
runtime,
context,
allowAutoCliFallback: resolvedRun.spec.allowAutoCliFallback,
summaryStream,
requestOptions,
});

return { context, envForRun, runtime, model };
}

export function createSummarizeExecutionResources(options: {
resolvedRun: ResolvedSummarizeRun;
env: Record<string, string | undefined>;
metricsEnv?: Record<string, string | undefined>;
fetchImpl: typeof fetch;
execFileImpl: ExecFileFn;
cacheState: CacheState;
mediaCache: MediaCache | null;
stdout: NodeJS.WritableStream;
stderr: NodeJS.WritableStream;
urlFetch?: typeof fetch | null;
summaryStream: SummaryStreamHandler | null;
requestOptions?: ModelExecutorRequestOptions;
log?: ModelExecutorDeps["log"];
trace?: ModelExecutorDeps["trace"];
flow: SummarizeFlowOptions;
adapterHooks: SummarizeFlowAdapterHooks;
eventHooks?: Partial<UrlFlowEventHooks>;
assetFormat?: Parameters<typeof createRunFlowContexts>[0]["assetFormat"];
perfTrace?: PerfTrace | null;
}): SummarizeExecutionResources {
export function createSummarizeExecutionResources(
options: Omit<Parameters<typeof createSummarizeModelResources>[0], "streamingEnabled"> & {
cacheState: CacheState;
mediaCache: MediaCache | null;
stdout: NodeJS.WritableStream;
stderr: NodeJS.WritableStream;
urlFetch?: typeof fetch | null;
flow: SummarizeFlowOptions;
adapterHooks: SummarizeFlowAdapterHooks;
eventHooks?: Partial<UrlFlowEventHooks>;
assetFormat?: Parameters<typeof createRunFlowContexts>[0]["assetFormat"];
perfTrace?: PerfTrace | null;
},
): SummarizeExecutionResources {
const { resolvedRun, flow } = options;
const { spec } = resolvedRun;
const modelResources = createSummarizeModelResources({
resolvedRun,
env: options.env,
metricsEnv: options.metricsEnv,
fetchImpl: options.fetchImpl,
execFileImpl: options.execFileImpl,
...options,
streamingEnabled: flow.streamingEnabled,
summaryStream: options.summaryStream,
requestOptions: options.requestOptions,
log: options.log,
trace: options.trace,
});
const { metrics } = modelResources.runtime;
const cacheState = scopeTranscriptCacheForDiarization(
Expand Down
115 changes: 30 additions & 85 deletions src/application/model-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import type { SummarizeConfig } from "../config.js";
import type { SummaryStreamHandler } from "../engine/events.js";
import { createModelExecutor, type ModelExecutorDeps } from "../engine/model-executor.js";
import type { LengthArg } from "../flags.js";
import type { ExecFileFn } from "../markitdown.js";
import { resolveRunApiStatus } from "./api-status.js";
import type { RunContextState } from "./context.js";
import { createRunMetrics } from "./metrics.js";
import { resolveModelSelection, type ModelSelection } from "./model-selection.js";
import { resolveDesiredOutputTokens } from "./output-policy.js";
import type { RunModelSpec } from "./model-selection.js";
import { resolveProviderRuntimeBindings } from "./provider-runtime.js";
import type { ResolvedSummarizeRun } from "./run-spec.js";

export type ModelExecutorRequestOptions = Pick<
ModelExecutorDeps,
"openaiRequestOptions" | "openaiRequestOptionsOverride" | "cliReasoningEffortOverride"
>;

export type RunModelSpec = ModelSelection & {
fixedModelSpec: Extract<ModelSelection["requestedModel"], { kind: "fixed" }> | null;
desiredOutputTokens: number | null;
};

export type RunModelRuntime = {
type RunModelRuntime = {
metrics: ReturnType<typeof createRunMetrics>;
apiStatus: ReturnType<typeof resolveRunApiStatus>;
summaryEngine: ReturnType<typeof createModelExecutor>;
};

export type SummarizeModelResources = {
context: RunContextState;
envForRun: Record<string, string | undefined>;
runtime: RunModelRuntime;
model: ExecutableRunModel;
};

export type ExecutableRunModel = RunModelSpec &
ModelExecutorRequestOptions & {
allowAutoCliFallback: boolean;
Expand All @@ -39,67 +40,31 @@ export type ExecutableRunModel = RunModelSpec &
llmCalls: RunModelRuntime["metrics"]["llmCalls"];
};

export function resolveRunModelSpec({
context,
envForRun,
explicitModelArg,
configForSelection,
lengthArg,
maxOutputTokensArg,
}: {
context: RunContextState;
envForRun: Record<string, string | undefined>;
explicitModelArg: string | null;
configForSelection: SummarizeConfig | null;
lengthArg: LengthArg;
maxOutputTokensArg: number | null;
}): RunModelSpec {
const selection = resolveModelSelection({
config: context.config,
configForCli: configForSelection,
configPath: context.configPath,
envForRun,
explicitModelArg,
});
return {
...selection,
fixedModelSpec: selection.requestedModel.kind === "fixed" ? selection.requestedModel : null,
desiredOutputTokens: resolveDesiredOutputTokens({
lengthArg,
maxOutputTokensArg,
}),
};
}

export function createRunModelRuntime({
context,
export function createSummarizeModelResources({
resolvedRun,
env,
envForRun,
metricsEnv,
metricsEnv = env,
fetchImpl,
execFileImpl,
maxOutputTokensArg,
timeoutMs,
retries,
streamingEnabled,
summaryStream,
requestOptions = {},
log,
trace,
}: {
context: RunContextState;
resolvedRun: ResolvedSummarizeRun;
env: Record<string, string | undefined>;
envForRun: Record<string, string | undefined>;
metricsEnv: Record<string, string | undefined>;
metricsEnv?: Record<string, string | undefined>;
fetchImpl: typeof fetch;
execFileImpl: ModelExecutorDeps["execFileImpl"];
maxOutputTokensArg: number | null;
timeoutMs: number;
retries: number;
execFileImpl: ExecFileFn;
streamingEnabled: boolean;
summaryStream: SummaryStreamHandler | null;
requestOptions?: ModelExecutorRequestOptions;
log?: ModelExecutorDeps["log"];
trace?: ModelExecutorDeps["trace"];
}): RunModelRuntime {
}): SummarizeModelResources {
const { context, envForRun } = resolvedRun.bindings;
const { maxOutputTokensArg, timeoutMs, retries, allowAutoCliFallback } = resolvedRun.spec;
const metrics = createRunMetrics({
env: metricsEnv,
fetchImpl,
Expand Down Expand Up @@ -131,40 +96,20 @@ export function createRunModelRuntime({
openrouterApiKey: apiStatus.openrouterApiKey,
});

return {
metrics,
apiStatus,
summaryEngine,
};
}

export function createExecutableRunModel({
spec,
runtime,
context,
allowAutoCliFallback,
summaryStream,
requestOptions = {},
}: {
spec: RunModelSpec;
runtime: RunModelRuntime;
context: RunContextState;
allowAutoCliFallback: boolean;
summaryStream: SummaryStreamHandler | null;
requestOptions?: ModelExecutorRequestOptions;
}): ExecutableRunModel {
return {
...spec,
const model: ExecutableRunModel = {
...resolvedRun.bindings.model,
allowAutoCliFallback,
envForAuto: context.envForAuto,
cliAvailability: context.cliAvailability,
openaiUseChatCompletions: context.openaiUseChatCompletions,
openaiWhisperUsdPerMinute: context.openaiWhisperUsdPerMinute,
...requestOptions,
apiStatus: runtime.apiStatus,
summaryEngine: runtime.summaryEngine,
apiStatus,
summaryEngine,
summaryStream,
getLiteLlmCatalog: runtime.metrics.getLiteLlmCatalog,
llmCalls: runtime.metrics.llmCalls,
getLiteLlmCatalog: metrics.getLiteLlmCatalog,
llmCalls: metrics.llmCalls,
};

return { context, envForRun, runtime: { metrics, apiStatus, summaryEngine }, model };
}
40 changes: 40 additions & 0 deletions src/application/model-selection.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { CliProvider, ModelConfig, SummarizeConfig } from "../config.js";
import type { LengthArg } from "../flags.js";
import { mergeModelRequestOptions } from "../llm/model-options.js";
import type { RequestedModel } from "../model-spec.js";
import { parseRequestedModelId } from "../model-spec.js";
import type { RunContextState } from "./context.js";
import { BUILTIN_MODELS } from "./model-catalog.js";
import { resolveDesiredOutputTokens } from "./output-policy.js";

function resolveConfiguredCliModel(
provider: CliProvider,
Expand Down Expand Up @@ -67,6 +70,43 @@ export type ModelSelection = {
isFallbackModel: boolean;
};

export type RunModelSpec = ModelSelection & {
fixedModelSpec: Extract<ModelSelection["requestedModel"], { kind: "fixed" }> | null;
desiredOutputTokens: number | null;
};

export function resolveRunModelSpec({
context,
envForRun,
explicitModelArg,
configForSelection,
lengthArg,
maxOutputTokensArg,
}: {
context: RunContextState;
envForRun: Record<string, string | undefined>;
explicitModelArg: string | null;
configForSelection: SummarizeConfig | null;
lengthArg: LengthArg;
maxOutputTokensArg: number | null;
}): RunModelSpec {
const selection = resolveModelSelection({
config: context.config,
configForCli: configForSelection,
configPath: context.configPath,
envForRun,
explicitModelArg,
});
return {
...selection,
fixedModelSpec: selection.requestedModel.kind === "fixed" ? selection.requestedModel : null,
desiredOutputTokens: resolveDesiredOutputTokens({
lengthArg,
maxOutputTokensArg,
}),
};
}

export function resolveModelSelection({
config,
configForCli,
Expand Down
2 changes: 1 addition & 1 deletion src/application/run-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from "../run/run-settings.js";
import { createRunConfigInput, type RunConfigInput } from "./config-state.js";
import { resolveRunContextState, type RunContextState } from "./context.js";
import { resolveRunModelSpec, type RunModelSpec } from "./model-runtime.js";
import { resolveRunModelSpec, type RunModelSpec } from "./model-selection.js";
import type { SummarizeRequest } from "./summarize-contracts.js";

export type SummarizeRunInput = SummarizeRequest["input"];
Expand Down
Loading