Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions apps/server/src/orchestration/ActivityPayloadProjection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 6 additions & 0 deletions apps/server/src/orchestration/ActivityPayloadProjection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ function projectViewedImagePath(data: Record<string, unknown>): 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;
Expand Down
71 changes: 71 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,77 @@ 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: "/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);
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"
? [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"],
],
);
}),
);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
it.effect("maps completed agent message items to canonical item.completed events", () =>
Effect.gen(function* () {
const { adapter, runtime } = yield* startLifecycleRuntime();
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/provider/Layers/CodexAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,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:
Expand Down
Loading