From c677f6a32d275e129955c1d05c618e30941aa32b Mon Sep 17 00:00:00 2001 From: achose369 Date: Thu, 22 Oct 2026 10:21:33 +0800 Subject: [PATCH] fix(server): show Codex tool-output images inline Co-Authored-By: Claude Sonnet 5.5 --- .../Adapters/CodexAdapterV2.test.ts | 22 +++++++++++++++++++ .../Adapters/CodexAdapterV2.ts | 20 +++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 5f00beb2fbc3..542ac4d480fa 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -6129,3 +6129,25 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); }); }); + +describe("codexDynamicToolOutput", () => { + it("translates inputImage data URLs into image blocks the work log can serve", () => { + const output = CodexAdapterV2.codexDynamicToolOutput({ + type: "dynamicToolCall", + id: "exec-image", + tool: "exec", + status: "completed", + arguments: {}, + contentItems: [ + { type: "inputText", text: "Script completed" }, + { type: "inputImage", imageUrl: "data:image/png;base64,iVBORw0KGgo=" }, + { type: "inputImage", imageUrl: "https://example.test/remote.png" }, + ], + } as never); + assert.deepEqual(output, [ + { type: "inputText", text: "Script completed" }, + { type: "image", mimeType: "image/png", data: "iVBORw0KGgo=" }, + { type: "inputImage", imageUrl: "https://example.test/remote.png" }, + ]); + }); +}); diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 5c4df98d310d..313ceb8707d6 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -483,11 +483,27 @@ function codexMcpAppResourceUri(item: CodexDynamicToolItem): string | undefined return typeof uri === "string" && uri.startsWith(MCP_APP_RESOURCE_SCHEME) ? uri : undefined; } -function codexDynamicToolOutput( +const CODEX_IMAGE_DATA_URL = /^data:(image\/[a-z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/i; + +/** + * Codex reports image output as `inputImage` with a data URL; the work log and + * asset routes only recognize the `image` block shape, so translate it here. + */ +function codexContentItemImage(item: T): T | { type: "image"; mimeType: string; data: string } { + if (typeof item !== "object" || item === null) return item; + const { type, imageUrl } = item as { type?: unknown; imageUrl?: unknown }; + if (type !== "inputImage" || typeof imageUrl !== "string") return item; + const match = CODEX_IMAGE_DATA_URL.exec(imageUrl); + return match?.[1] && match[2] + ? { type: "image", mimeType: match[1].toLowerCase(), data: match[2] } + : item; +} + +export function codexDynamicToolOutput( item: Extract, ): unknown | undefined { if (item.contentItems !== null && item.contentItems !== undefined) { - return item.contentItems; + return item.contentItems.map(codexContentItemImage); } return item.success === false ? { success: false } : undefined; }