diff --git a/src/replay/orchestrator.ts b/src/replay/orchestrator.ts index 97300d1..cd6edd3 100644 --- a/src/replay/orchestrator.ts +++ b/src/replay/orchestrator.ts @@ -214,6 +214,7 @@ export async function attemptFallback( path: { id: sessionId }, body: { model: { providerID, modelID }, + agent: agentName ?? undefined, parts: promptParts as NonNullable< Parameters[0]["body"] >["parts"], diff --git a/test/helpers/mock-client.ts b/test/helpers/mock-client.ts index b8ff8ea..b278687 100644 --- a/test/helpers/mock-client.ts +++ b/test/helpers/mock-client.ts @@ -11,7 +11,7 @@ export interface MockMessageEntry { export interface MockClientCalls { abort: string[]; revert: Array<{ sessionId: string; messageID: string }>; - prompt: Array<{ sessionId: string; providerID: string; modelID: string; parts: unknown[] }>; + prompt: Array<{ sessionId: string; providerID: string; modelID: string; agent?: string; parts: unknown[] }>; toasts: Array<{ title?: string; message: string; variant: string }>; logs: Array<{ level: string; message: string }>; } @@ -52,13 +52,14 @@ export function makeMockClient(opts: MockClientOptions = {}): { }, prompt: async (options: { path: { id: string }; - body?: { model?: { providerID: string; modelID: string }; parts?: unknown[] }; + body?: { model?: { providerID: string; modelID: string }; agent?: string; parts?: unknown[] }; }) => { if (opts.promptError) throw opts.promptError; calls.prompt.push({ sessionId: options.path.id, providerID: options.body?.model?.providerID ?? "", modelID: options.body?.model?.modelID ?? "", + agent: options.body?.agent, parts: options.body?.parts ?? [], }); return { data: {} }; diff --git a/test/orchestrator.test.ts b/test/orchestrator.test.ts index 4254bbb..c031069 100644 --- a/test/orchestrator.test.ts +++ b/test/orchestrator.test.ts @@ -453,6 +453,41 @@ describe("attemptFallback — concurrency", () => { }); }); +// ─── Agent preservation ─────────────────────────────────────────────────────── + +describe("attemptFallback — agent preservation", () => { + it("passes the agent name to the prompt call so the fallback model runs under the same agent", async () => { + const { client, calls } = makeMockClient({ + messages: [makeUserMessage("s1", "m1", "openai", "gpt-5.3-codex", "OpenCoder")], + }); + const store = makeStore(); + store.sessions.setOriginalModel("s1", "openai/gpt-5.3-codex"); + const logger = new Logger(client, "/tmp/test.log", false); + + await attemptFallback("s1", "rate_limit", client, store, BASE_CONFIG, logger, "/tmp"); + + expect(calls.prompt).toHaveLength(1); + expect(calls.prompt[0].agent).toBe("OpenCoder"); + }); + + it("omits agent from prompt when it could not be resolved", async () => { + // No agent field in the message → agentName resolves to null + const msg = makeUserMessage("s1", "m1", "openai", "gpt-5.3-codex"); + // Strip the agent field so resolveAgentName has nothing to work with + (msg.info as any).agent = undefined; + + const { client, calls } = makeMockClient({ messages: [msg] }); + const store = makeStore(); + store.sessions.setOriginalModel("s1", "openai/gpt-5.3-codex"); + const logger = new Logger(client, "/tmp/test.log", false); + + await attemptFallback("s1", "rate_limit", client, store, BASE_CONFIG, logger, "/tmp"); + + expect(calls.prompt).toHaveLength(1); + expect(calls.prompt[0].agent).toBeUndefined(); + }); +}); + // ─── Replay step failures ───────────────────────────────────────────────────── describe("attemptFallback — replay step failures", () => {