Test Codex MCP images through app-server boundary
This commit is contained in:
@@ -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", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user