Test Codex MCP images through app-server boundary

This commit is contained in:
Mohamed Boudra
2026-07-03 01:16:57 +02:00
parent 4ca89f21f9
commit 6781521c8e
@@ -2359,86 +2359,124 @@ describe("Codex app-server provider", () => {
rmSync(source, { force: true }); rmSync(source, { force: true });
}); });
test("emits mcpToolCall image content as a completed tool call plus assistant markdown image", () => { test("mcpToolCall image content emits a completed tool call plus assistant markdown image", async () => {
const session = createSession(); const appServer = createFakeCodexAppServer();
const session = new CodexAppServerAgentSession(
createConfig({ cwd: "/workspace/project" }),
null,
createTestLogger(),
async () => appServer.child,
);
const events: AgentStreamEvent[] = []; const events: AgentStreamEvent[] = [];
session.subscribe((event) => events.push(event)); const timelineEvents: Array<Extract<AgentStreamEvent, { type: "timeline" }>> = [];
const timelineItemsReceived = new Promise<void>((resolve, reject) => {
asInternals(session).handleNotification("item/completed", { const timeout = setTimeout(() => {
item: { unsubscribe();
id: "mcp-browser-screenshot", reject(new Error("Timed out waiting for MCP image timeline events"));
type: "mcpToolCall", }, 1000);
status: "completed", const unsubscribe = session.subscribe((event) => {
server: "paseo", events.push(event);
tool: "browser_screenshot", if (event.type !== "timeline") {
arguments: { browserId: "11111111-1111-4111-8111-111111111111" }, return;
result: { }
content: [ timelineEvents.push(event);
{ type: "text", text: "Captured browser screenshot (1x1)." }, if (timelineEvents.length === 2) {
{ type: "image", data: ONE_BY_ONE_PNG_BASE64, mimeType: "image/png" }, clearTimeout(timeout);
], unsubscribe();
structuredContent: { resolve();
ok: true, }
result: { });
command: "screenshot",
browserId: "11111111-1111-4111-8111-111111111111",
mimeType: "image/png",
width: 1,
height: 1,
},
},
},
},
}); });
expect(events.length).toBe(2); try {
expect(events[0]).toEqual({ const { turnId } = await session.startTurn("capture a browser screenshot");
type: "timeline", appServer.child.stdout.write(
provider: "codex", `${JSON.stringify({
turnId: "test-turn", method: "item/completed",
item: { params: {
type: "tool_call", item: {
callId: "mcp-browser-screenshot", id: "mcp-browser-screenshot",
name: "paseo.browser_screenshot", type: "mcpToolCall",
status: "completed", status: "completed",
error: null, server: "paseo",
detail: { tool: "browser_screenshot",
type: "unknown", arguments: { browserId: "11111111-1111-4111-8111-111111111111" },
input: { browserId: "11111111-1111-4111-8111-111111111111" },
output: {
content: [
{ type: "text", text: "Captured browser screenshot (1x1)." },
{ type: "text", text: "[image]" },
],
structuredContent: {
ok: true,
result: { result: {
command: "screenshot", content: [
browserId: "11111111-1111-4111-8111-111111111111", { type: "text", text: "Captured browser screenshot (1x1)." },
mimeType: "image/png", { type: "image", data: ONE_BY_ONE_PNG_BASE64, mimeType: "image/png" },
width: 1, ],
height: 1, structuredContent: {
ok: true,
result: {
command: "screenshot",
browserId: "11111111-1111-4111-8111-111111111111",
mimeType: "image/png",
width: 1,
height: 1,
},
},
},
},
},
})}\n`,
);
await timelineItemsReceived;
expect(timelineEvents).toEqual([
{
type: "timeline",
provider: "codex",
turnId,
item: {
type: "tool_call",
callId: "mcp-browser-screenshot",
name: "paseo.browser_screenshot",
status: "completed",
error: null,
detail: {
type: "unknown",
input: { browserId: "11111111-1111-4111-8111-111111111111" },
output: {
content: [
{ type: "text", text: "Captured browser screenshot (1x1)." },
{ type: "text", text: "[image]" },
],
structuredContent: {
ok: true,
result: {
command: "screenshot",
browserId: "11111111-1111-4111-8111-111111111111",
mimeType: "image/png",
width: 1,
height: 1,
},
},
}, },
}, },
}, },
}, },
}, {
}); type: "timeline",
expect(events[1]).toMatchObject({ provider: "codex",
type: "timeline", turnId,
provider: "codex", item: expect.objectContaining({ type: "assistant_message" }),
turnId: "test-turn", },
item: { type: "assistant_message" }, ]);
}); const imageEvent = timelineEvents[1];
const imageEvent = events[1]; if (imageEvent.item.type !== "assistant_message") {
if (imageEvent?.type !== "timeline" || imageEvent.item.type !== "assistant_message") { throw new Error("Expected assistant image timeline event");
throw new Error("Expected assistant image timeline event"); }
expect(JSON.stringify(events)).not.toContain(ONE_BY_ONE_PNG_BASE64);
const source = markdownImageSource(imageEvent.item.text);
expect(source).toMatch(/paseo-attachments[\\/].+\.png$/);
expect(existsSync(source)).toBe(true);
rmSync(source, { force: true });
appServer.assertNoErrors();
} finally {
await session.close();
} }
expect(JSON.stringify(events)).not.toContain(ONE_BY_ONE_PNG_BASE64);
const source = markdownImageSource(imageEvent.item.text);
expect(source).toMatch(/paseo-attachments[\\/].+\.png$/);
expect(existsSync(source)).toBe(true);
rmSync(source, { force: true });
}); });
test("ignores incomplete imageGeneration thread items without failing the turn", () => { test("ignores incomplete imageGeneration thread items without failing the turn", () => {