summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-10 09:51:00 +0900
committerAdam Malczewski <[email protected]>2026-06-10 09:51:00 +0900
commit5ff460688519e48fd0bfab893ebaed4258dee789 (patch)
tree8c5bcdfb04122b178c5e9bc8654afd563872a8a0
parenta980aba97aed34692dd655e716747c4ff53fb186 (diff)
downloaddispatch-5ff460688519e48fd0bfab893ebaed4258dee789.tar.gz
dispatch-5ff460688519e48fd0bfab893ebaed4258dee789.zip
kernel/run-turn: thread providerOpts (model) into provider.stream
executeStep built the stream opts with only the logger, so providerOpts.model (the selected model) never reached any provider — each fell back to its own default. Carry providerOpts through StepContext into the per-step stream opts, plus a regression test asserting the model is forwarded.
-rw-r--r--packages/kernel/src/runtime/run-turn.test.ts29
-rw-r--r--packages/kernel/src/runtime/run-turn.ts13
2 files changed, 40 insertions, 2 deletions
diff --git a/packages/kernel/src/runtime/run-turn.test.ts b/packages/kernel/src/runtime/run-turn.test.ts
index 6db6645..fa2aba4 100644
--- a/packages/kernel/src/runtime/run-turn.test.ts
+++ b/packages/kernel/src/runtime/run-turn.test.ts
@@ -1523,6 +1523,35 @@ describe("runTurn", () => {
expect(capturedOpts).toBeDefined();
expect(capturedOpts?.logger).toBeUndefined();
});
+
+ it("threads providerOpts.model through to provider.stream opts", async () => {
+ let capturedOpts: Record<string, unknown> | undefined;
+
+ const provider: ProviderContract = {
+ id: "fake",
+ stream(_messages, _tools, opts) {
+ capturedOpts = opts !== undefined ? { ...opts } : undefined;
+ return (async function* () {
+ yield { type: "text-delta", delta: "hi" } as ProviderEvent;
+ yield { type: "usage", usage: { inputTokens: 1, outputTokens: 1 } } as ProviderEvent;
+ yield { type: "finish", reason: "stop" } as ProviderEvent;
+ })();
+ },
+ };
+
+ await runTurn({
+ provider,
+ messages: [userMessage],
+ tools: [],
+ dispatch: { maxConcurrent: 1, eager: false },
+ conversationId: "conv-1",
+ turnId: "turn-1",
+ emit: () => {},
+ providerOpts: { model: "some-model-id" },
+ });
+
+ expect(capturedOpts?.model).toBe("some-model-id");
+ });
});
describe("span tree nesting", () => {
diff --git a/packages/kernel/src/runtime/run-turn.ts b/packages/kernel/src/runtime/run-turn.ts
index d5db1bf..b50e8ee 100644
--- a/packages/kernel/src/runtime/run-turn.ts
+++ b/packages/kernel/src/runtime/run-turn.ts
@@ -1,6 +1,11 @@
import type { ChatMessage, Chunk, StepId } from "../contracts/conversation.js";
import type { Logger, Span } from "../contracts/logging.js";
-import type { ProviderContract, ProviderEvent, Usage } from "../contracts/provider.js";
+import type {
+ ProviderContract,
+ ProviderEvent,
+ ProviderStreamOptions,
+ Usage,
+} from "../contracts/provider.js";
import type { EventEmitter, RunTurnInput, RunTurnResult } from "../contracts/runtime.js";
import type { ToolCall, ToolContract } from "../contracts/tool.js";
import { createStepDispatcher, type StepDispatcher } from "./dispatch.js";
@@ -100,6 +105,8 @@ interface StepContext {
readonly toolSpans: Map<string, Span>;
readonly cwd: string | undefined;
readonly now: (() => number) | undefined;
+ /** Per-turn provider options (model, systemPrompt, …) threaded to stream(). */
+ readonly providerOpts: ProviderStreamOptions | undefined;
}
interface TimingState {
@@ -295,7 +302,8 @@ async function executeStep(ctx: StepContext): Promise<StepResult> {
}
try {
- const opts = {
+ const opts: ProviderStreamOptions = {
+ ...ctx.providerOpts,
...(ctx.turnSpan !== undefined && stepSpan !== undefined ? { logger: stepSpan.log } : {}),
};
const stream = ctx.provider.stream(ctx.messages, ctx.tools, opts);
@@ -501,6 +509,7 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> {
toolSpans,
cwd: input.cwd,
now,
+ providerOpts: input.providerOpts,
});
totalUsage = addUsage(totalUsage, stepResult.usage);