Test Codex MCP images through app-server boundary
This commit is contained in:
@@ -2359,12 +2359,41 @@ 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) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
unsubscribe();
|
||||||
|
reject(new Error("Timed out waiting for MCP image timeline events"));
|
||||||
|
}, 1000);
|
||||||
|
const unsubscribe = session.subscribe((event) => {
|
||||||
|
events.push(event);
|
||||||
|
if (event.type !== "timeline") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timelineEvents.push(event);
|
||||||
|
if (timelineEvents.length === 2) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
unsubscribe();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
asInternals(session).handleNotification("item/completed", {
|
try {
|
||||||
|
const { turnId } = await session.startTurn("capture a browser screenshot");
|
||||||
|
appServer.child.stdout.write(
|
||||||
|
`${JSON.stringify({
|
||||||
|
method: "item/completed",
|
||||||
|
params: {
|
||||||
item: {
|
item: {
|
||||||
id: "mcp-browser-screenshot",
|
id: "mcp-browser-screenshot",
|
||||||
type: "mcpToolCall",
|
type: "mcpToolCall",
|
||||||
@@ -2389,13 +2418,17 @@ describe("Codex app-server provider", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
})}\n`,
|
||||||
|
);
|
||||||
|
|
||||||
expect(events.length).toBe(2);
|
await timelineItemsReceived;
|
||||||
expect(events[0]).toEqual({
|
|
||||||
|
expect(timelineEvents).toEqual([
|
||||||
|
{
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
provider: "codex",
|
provider: "codex",
|
||||||
turnId: "test-turn",
|
turnId,
|
||||||
item: {
|
item: {
|
||||||
type: "tool_call",
|
type: "tool_call",
|
||||||
callId: "mcp-browser-screenshot",
|
callId: "mcp-browser-screenshot",
|
||||||
@@ -2423,15 +2456,16 @@ describe("Codex app-server provider", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
expect(events[1]).toMatchObject({
|
{
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
provider: "codex",
|
provider: "codex",
|
||||||
turnId: "test-turn",
|
turnId,
|
||||||
item: { type: "assistant_message" },
|
item: expect.objectContaining({ type: "assistant_message" }),
|
||||||
});
|
},
|
||||||
const imageEvent = events[1];
|
]);
|
||||||
if (imageEvent?.type !== "timeline" || imageEvent.item.type !== "assistant_message") {
|
const imageEvent = timelineEvents[1];
|
||||||
|
if (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);
|
expect(JSON.stringify(events)).not.toContain(ONE_BY_ONE_PNG_BASE64);
|
||||||
@@ -2439,6 +2473,10 @@ describe("Codex app-server provider", () => {
|
|||||||
expect(source).toMatch(/paseo-attachments[\\/].+\.png$/);
|
expect(source).toMatch(/paseo-attachments[\\/].+\.png$/);
|
||||||
expect(existsSync(source)).toBe(true);
|
expect(existsSync(source)).toBe(true);
|
||||||
rmSync(source, { force: true });
|
rmSync(source, { force: true });
|
||||||
|
appServer.assertNoErrors();
|
||||||
|
} finally {
|
||||||
|
await session.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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