Skip to content

Commit d9298a7

Browse files
committed
fix(codex): prefer gateway-managed generated images
1 parent cd7e3df commit d9298a7

2 files changed

Lines changed: 55 additions & 5 deletions

File tree

extensions/codex/src/app-server/event-projector.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
resetGlobalHookRunner,
1919
} from "openclaw/plugin-sdk/hook-runtime";
2020
import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
21+
import { withTempDir } from "openclaw/plugin-sdk/test-env";
2122
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2223
import {
2324
CodexAppServerEventProjector,
@@ -743,6 +744,47 @@ describe("CodexAppServerEventProjector", () => {
743744
expect(result.toolMediaUrls?.[0]).not.toBe(savedPath);
744745
});
745746

747+
it("prefers gateway-managed image media when the typed event arrives first", async () => {
748+
await withTempDir("openclaw-codex-media-state-", async (stateDir) => {
749+
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
750+
const projector = await createProjector();
751+
const savedPath = "/home/dev-user/.codex/generated_images/session-1/ig_123.png";
752+
753+
await projector.handleNotification(
754+
forCurrentTurn("item/completed", {
755+
item: {
756+
type: "imageGeneration",
757+
id: "ig_123",
758+
status: "completed",
759+
revisedPrompt: "A tiny blue square",
760+
result: tinyPngBase64,
761+
savedPath,
762+
},
763+
}),
764+
);
765+
await projector.handleNotification(
766+
forCurrentTurn("rawResponseItem/completed", {
767+
item: {
768+
type: "image_generation_call",
769+
id: "ig_123",
770+
status: "generating",
771+
result: tinyPngBase64,
772+
},
773+
}),
774+
);
775+
776+
const result = projector.buildResult(buildEmptyToolTelemetry());
777+
const mediaUrl = result.toolMediaUrls?.[0];
778+
779+
expect(result.toolMediaUrls).toHaveLength(1);
780+
expect(mediaUrl).not.toBe(savedPath);
781+
expect(mediaUrl).toContain(`${path.sep}media${path.sep}tool-image-generation${path.sep}`);
782+
await expect(fs.readFile(mediaUrl ?? "")).resolves.toEqual(
783+
Buffer.from(tinyPngBase64, "base64"),
784+
);
785+
});
786+
});
787+
746788
it("preserves distinct raw image-generation items with identical image bytes", async () => {
747789
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-media-state-"));
748790
tempDirs.add(stateDir);

extensions/codex/src/app-server/event-projector.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ export class CodexAppServerEventProjector {
188188
private readonly toolTrajectoryItemsById = new Map<string, CodexThreadItem>();
189189
private readonly transcriptToolProgressCallIds = new Set<string>();
190190
private lastNativeToolError: EmbeddedRunAttemptResult["lastToolError"];
191-
private readonly nativeGeneratedMediaUrls = new Set<string>();
192191
private readonly nativeGeneratedMediaItemIds = new Set<string>();
193192
private readonly nativeGeneratedMediaUrlsByItemId = new Map<string, string>();
194193
private readonly diagnosticToolStartedAtByItem = new Map<string, number>();
@@ -1028,6 +1027,9 @@ export class CodexAppServerEventProjector {
10281027
this.recordNativeGeneratedMediaUrl({
10291028
itemId,
10301029
mediaUrl: saved.path,
1030+
// The typed savedPath may belong to a remote app-server host. Always
1031+
// prefer the copy persisted into this gateway's managed media root.
1032+
replaceExisting: true,
10311033
});
10321034
} catch (error) {
10331035
embeddedAgentLog.warn("codex app-server raw image generation result save failed", {
@@ -1037,13 +1039,19 @@ export class CodexAppServerEventProjector {
10371039
}
10381040
}
10391041

1040-
private recordNativeGeneratedMediaUrl(params: { itemId: string; mediaUrl: string }): void {
1041-
if (this.nativeGeneratedMediaUrlsByItemId.has(params.itemId)) {
1042+
private recordNativeGeneratedMediaUrl(params: {
1043+
itemId: string;
1044+
mediaUrl: string;
1045+
replaceExisting?: boolean;
1046+
}): void {
1047+
if (
1048+
this.nativeGeneratedMediaUrlsByItemId.has(params.itemId) &&
1049+
params.replaceExisting !== true
1050+
) {
10421051
this.nativeGeneratedMediaItemIds.add(params.itemId);
10431052
return;
10441053
}
10451054
this.nativeGeneratedMediaUrlsByItemId.set(params.itemId, params.mediaUrl);
1046-
this.nativeGeneratedMediaUrls.add(params.mediaUrl);
10471055
this.nativeGeneratedMediaItemIds.add(params.itemId);
10481056
}
10491057

@@ -1052,7 +1060,7 @@ export class CodexAppServerEventProjector {
10521060
toolTelemetry.toolMediaUrls?.map((url) => url.trim()).filter(Boolean) ?? [],
10531061
);
10541062
if ((toolTelemetry.messagingToolSentMediaUrls?.length ?? 0) === 0) {
1055-
for (const mediaUrl of this.nativeGeneratedMediaUrls) {
1063+
for (const mediaUrl of this.nativeGeneratedMediaUrlsByItemId.values()) {
10561064
mediaUrls.add(mediaUrl);
10571065
}
10581066
}

0 commit comments

Comments
 (0)