diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index fb27b68e96..032eeb65cb 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -6192,6 +6192,107 @@ describe("Codex app-server provider", () => { ]); }); + test.each([ + [ + "a usage limit", + { type: "usageLimitExceeded", limitId: "image_gen", resetsAt: 1786150800 }, + { type: "usageLimitExceeded", limitId: "image_gen", resetsAt: 1786150800 }, + ], + ["no failure details", null, { message: "Image generation failed" }], + ])( + "emits failed imageGeneration items with %s as failed tool calls", + (_label, failure, expectedError) => { + const session = createSession(); + const events: AgentStreamEvent[] = []; + session.subscribe((event) => events.push(event)); + + asInternals(session).handleNotification("item/completed", { + item: { + id: "image-generation-failed", + type: "imageGeneration", + status: "failed", + revisedPrompt: "paint a blue whale", + result: "", + transparentBackground: null, + failure, + savedPath: null, + }, + }); + + expect(events).toEqual([ + { + type: "timeline", + provider: "codex", + turnId: "test-turn", + item: { + type: "tool_call", + callId: "image-generation-failed", + name: "image_generation", + status: "failed", + error: expectedError, + detail: { + type: "unknown", + input: { prompt: "paint a blue whale" }, + output: null, + }, + }, + }, + ]); + }, + ); + + test("keeps failed imageGeneration items in persisted history", async () => { + const session = createSession(); + session.client = { + request: vi.fn(async () => ({ + thread: { + turns: [ + { + items: [ + { + type: "imageGeneration", + id: "image-generation-failed", + status: "failed", + revisedPrompt: "paint a blue whale", + result: "", + transparentBackground: null, + failure: { type: "usageLimitExceeded", limitId: "image_gen", resetsAt: null }, + savedPath: null, + }, + ], + }, + ], + }, + })), + }; + + await asInternals(session).loadPersistedHistory(session.client); + + const history: AgentStreamEvent[] = []; + for await (const event of session.streamHistory()) { + history.push(event); + } + + expect(history).toEqual([ + { + type: "timeline", + provider: "codex", + item: { + type: "tool_call", + callId: "image-generation-failed", + name: "image_generation", + status: "failed", + error: { type: "usageLimitExceeded", limitId: "image_gen", resetsAt: null }, + detail: { + type: "unknown", + input: { prompt: "paint a blue whale" }, + output: null, + }, + }, + }, + ]); + }); + test("emits usage_updated on token usage updates and keeps usage on turn completion", () => { const session = createSession(); const events: AgentStreamEvent[] = []; diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index f15c5a60ad..019a03bc59 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -1949,6 +1949,15 @@ function mapCodexThreadImageItem( }); } + if (normalizedItem.status === "failed") { + return mapCodexToolCallEnvelope({ + callId: firstStringField(normalizedItem, ["id"]), + name: "image_generation", + input: { prompt: firstStringField(normalizedItem, ["revisedPrompt", "revised_prompt"]) }, + error: normalizedItem.failure ?? { message: "Image generation failed" }, + }); + } + const savedPath = firstStringField(normalizedItem, ["savedPath", "saved_path"]); const result = codexImageOutputFromResult(normalizedItem.result); return renderProviderImageOutputAsAssistantMarkdown(