From 8de452711f726d1b9b4624398d16d793653bf39b Mon Sep 17 00:00:00 2001 From: Trinh Viet Anh Date: Wed, 23 Sep 2026 09:37:13 +0700 Subject: [PATCH 1/2] fix(codex): preview generated images from saved paths --- .../ActivityPayloadProjection.test.ts | 34 ++++++++++++ .../ActivityPayloadProjection.ts | 6 +++ .../src/provider/Layers/CodexAdapter.test.ts | 54 +++++++++++++++++++ .../src/provider/Layers/CodexAdapter.ts | 2 +- 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration/ActivityPayloadProjection.test.ts b/apps/server/src/orchestration/ActivityPayloadProjection.test.ts index e6468ff8f789..ea5ec289ca48 100644 --- a/apps/server/src/orchestration/ActivityPayloadProjection.test.ts +++ b/apps/server/src/orchestration/ActivityPayloadProjection.test.ts @@ -168,6 +168,40 @@ describe("projectActivityPayload", () => { expect(JSON.stringify(openCode.payload).length).toBeLessThan(200); }); + it("keeps generated image paths while dropping image bytes, including stored activities", () => { + const imagePath = `/home/user/.codex/generated_images/${"nested folder/".repeat(16)}orca.png`; + const projected = projectActivityPayload( + activity({ + itemType: "image_view", + title: "Image view", + data: { + item: { + type: "imageGeneration", + savedPath: imagePath, + result: "base64-image-bytes".repeat(10_000), + status: "completed", + }, + }, + }), + ); + expect(projected.payload).toMatchObject({ data: { imagePath } }); + expect(projectActivityPayload(projected)).toEqual(projected); + expect(JSON.stringify(projected)).not.toContain("base64-image-bytes"); + }); + + it.each([undefined, null, "", " ", "/home/user/result.txt"])( + "does not expose a generated image preview for savedPath %s", + (savedPath) => { + const projected = projectActivityPayload( + activity({ + itemType: "image_view", + data: { item: { type: "imageGeneration", savedPath, result: "" } }, + }), + ); + expect(projected.payload).not.toMatchObject({ data: { imagePath: expect.anything() } }); + }, + ); + it("keeps full Claude Read image paths through repeated projection", () => { const imagePath = `/workspace/${"nested folder/".repeat(16)}reference image.webp`; const projected = projectActivityPayload( diff --git a/apps/server/src/orchestration/ActivityPayloadProjection.ts b/apps/server/src/orchestration/ActivityPayloadProjection.ts index adb6306efc5e..92f3e3925562 100644 --- a/apps/server/src/orchestration/ActivityPayloadProjection.ts +++ b/apps/server/src/orchestration/ActivityPayloadProjection.ts @@ -152,6 +152,12 @@ function projectViewedImagePath(data: Record): string | undefin return directPath; } + const item = asRecord(data.item); + const savedPath = item?.type === "imageGeneration" ? asTrimmedString(item.savedPath) : undefined; + if (savedPath && isWorkspaceImagePreviewPath(savedPath)) { + return savedPath; + } + const toolName = asTrimmedString(data.toolName)?.toLowerCase(); if (toolName !== "read" && toolName !== "read file") { return undefined; diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index 9f464bdaa177..7d4dd30a4219 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -1238,6 +1238,60 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { }), ); + it.effect("labels Codex image generation without changing ordinary image views", () => + Effect.gen(function* () { + const { adapter, runtime } = yield* startLifecycleRuntime(); + const next = yield* adapter.streamEvents.pipe( + Stream.take(3), + Stream.runCollect, + Effect.forkChild, + ); + for (const item of [ + { type: "imageGeneration", id: "generated", result: "", status: "in_progress" }, + { + type: "imageGeneration", + id: "generated", + result: "image-bytes", + status: "completed", + savedPath: "/home/user/.codex/generated_images/orca.png", + }, + { type: "imageView", id: "viewed", path: "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/workspace/reference.png" }, + ]) { + const lifecycle = item.status === "in_progress" ? "item/started" : "item/completed"; + yield* runtime.emit({ + id: asEventId(`evt-${item.id}-${lifecycle}`), + kind: "notification", + provider: ProviderDriverKind.make("codex"), + createdAt: "2026-01-01T00:00:00.000Z", + method: lifecycle, + threadId: asThreadId("thread-1"), + turnId: asTurnId("turn-1"), + itemId: asItemId(item.id), + payload: { + threadId: "thread-1", + turnId: "turn-1", + startedAtMs: 1_778_000_000_000, + completedAtMs: 1_778_000_000_100, + item, + }, + }); + } + const events = yield* Fiber.join(next); + NodeAssert.deepEqual( + events.map((event) => + event.type === "item.started" || event.type === "item.completed" + ? [event.type, event.payload.itemType, event.payload.title] + : event.type, + ), + [ + ["item.started", "image_view", "Generated image"], + ["item.completed", "image_view", "Generated image"], + ["item.completed", "image_view", "Image view"], + ], + ); + }), + ); + it.effect("maps completed agent message items to canonical item.completed events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts index 0ecc9693ab04..30ada4287c04 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.ts @@ -760,7 +760,7 @@ function itemTitle( case "web_search": return "Web search"; case "image_view": - return "Image view"; + return item?.type === "imageGeneration" ? "Generated image" : "Image view"; case "error": return "Error"; default: From 0fa497ee07ec52b25282368fe6f714e216486f69 Mon Sep 17 00:00:00 2001 From: Trinh Viet Anh Date: Wed, 23 Sep 2026 11:07:46 +0700 Subject: [PATCH 2/2] test(codex): assert generated image saved path survives adapter --- .../src/provider/Layers/CodexAdapter.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index 7d4dd30a4219..c56572c58a2e 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -1277,6 +1277,23 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { }); } const events = yield* Fiber.join(next); + const completedGeneration = events.find( + (event) => event.type === "item.completed" && event.payload.title === "Generated image", + ); + NodeAssert.ok(completedGeneration?.type === "item.completed"); + NodeAssert.deepStrictEqual(completedGeneration.payload.data, { + threadId: "thread-1", + turnId: "turn-1", + startedAtMs: 1_778_000_000_000, + completedAtMs: 1_778_000_000_100, + item: { + type: "imageGeneration", + id: "generated", + result: "image-bytes", + status: "completed", + savedPath: "/home/user/.codex/generated_images/orca.png", + }, + }); NodeAssert.deepEqual( events.map((event) => event.type === "item.started" || event.type === "item.completed"