From 14f1b4f9b7863cb53fbe3ee5191fd73201a50d76 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 9 Jun 2026 09:22:25 +0200 Subject: [PATCH 1/4] fix: Handle more web search events --- src/CodexAcpServer.ts | 26 +--- src/CodexEventHandler.ts | 8 +- src/CodexToolCallMapper.ts | 58 +++++++++ .../data/web-search-action-titles.json | 45 +++++++ .../data/web-search-start-and-complete.json | 45 +++++++ .../CodexACPAgent/web-search-events.test.ts | 115 ++++++++++++++++++ 6 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 src/__tests__/CodexACPAgent/data/web-search-action-titles.json create mode 100644 src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json create mode 100644 src/__tests__/CodexACPAgent/web-search-events.test.ts diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 99da1dd7..3f8440f4 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -39,6 +39,7 @@ import { createDynamicToolCallUpdate, createFileChangeUpdate, createMcpToolCallUpdate, + formatWebSearchTitle, } from "./CodexToolCallMapper"; import { createFastModeConfigOption, @@ -898,7 +899,7 @@ export class CodexAcpServer implements acp.Agent { sessionUpdate: "tool_call", toolCallId: item.id, kind: "search", - title: this.formatWebSearchTitle(item), + title: formatWebSearchTitle(item), status: "completed", rawInput: { query: item.query, @@ -958,29 +959,6 @@ export class CodexAcpServer implements acp.Agent { }; } - private formatWebSearchTitle(item: ThreadItem & { type: "webSearch" }): string { - const action = item.action; - if (!action) { - return item.query ? `Web search: ${item.query}` : "Web search"; - } - switch (action.type) { - case "search": { - const queries = action.queries?.filter((query) => query && query.length > 0) ?? []; - const query = action.query ?? (queries.length > 0 ? queries.join(", ") : null) ?? item.query; - return query ? `Web search: ${query}` : "Web search"; - } - case "openPage": - return action.url ? `Open page: ${action.url}` : "Open page"; - case "findInPage": { - const pattern = action.pattern ? ` for '${action.pattern}'` : ""; - const url = action.url ? ` in ${action.url}` : ""; - return `Find in page${pattern}${url}`.trim(); - } - case "other": - return "Web search"; - } - } - private toAcpToolCallStatus(status: CollabAgentToolCallStatus): "in_progress" | "completed" | "failed" { switch (status) { case "inProgress": diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index 642ec40a..8cadd0bb 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -40,6 +40,8 @@ import { createFuzzyFileSearchComplete, createFuzzyFileSearchStartOrUpdate, createMcpToolCallUpdate, + createWebSearchCompleteUpdate, + createWebSearchStartUpdate, fuzzyFileSearchToolCallId, } from "./CodexToolCallMapper"; import { stripShellPrefix } from "./CommandUtils"; @@ -303,12 +305,13 @@ export class CodexEventHandler { return await createMcpToolCallUpdate(event.item); case "dynamicToolCall": return await createDynamicToolCallUpdate(event.item); + case "webSearch": + return createWebSearchStartUpdate(event.item); case "collabAgentToolCall": case "userMessage": case "hookPrompt": case "agentMessage": case "reasoning": - case "webSearch": case "imageView": case "imageGeneration": case "enteredReviewMode": @@ -348,11 +351,12 @@ export class CodexEventHandler { text: summary } } + case "webSearch": + return createWebSearchCompleteUpdate(event.item); case "collabAgentToolCall": case "userMessage": case "hookPrompt": case "agentMessage": - case "webSearch": case "imageView": case "imageGeneration": case "enteredReviewMode": diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index 06454aab..199b9012 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -229,6 +229,64 @@ export function createFuzzyFileSearchComplete( }; } +export function createWebSearchStartUpdate( + item: ThreadItem & { type: "webSearch" } +): UpdateSessionEvent { + return { + sessionUpdate: "tool_call", + toolCallId: item.id, + kind: "search", + title: formatWebSearchTitle(item), + status: "in_progress", + rawInput: createWebSearchRawInput(item), + }; +} + +export function createWebSearchCompleteUpdate( + item: ThreadItem & { type: "webSearch" } +): UpdateSessionEvent { + return { + sessionUpdate: "tool_call_update", + toolCallId: item.id, + title: formatWebSearchTitle(item), + status: "completed", + rawInput: createWebSearchRawInput(item), + }; +} + +export function formatWebSearchTitle(item: ThreadItem & { type: "webSearch" }): string { + const action = item.action; + if (!action) { + return item.query ? `Web search: ${item.query}` : "Web search"; + } + switch (action.type) { + case "search": { + const queries = action.queries?.filter((query) => query && query.length > 0) ?? []; + const query = action.query ?? (queries.length > 0 ? queries.join(", ") : null) ?? item.query; + return query ? `Web search: ${query}` : "Web search"; + } + case "openPage": + return action.url ? `Open page: ${action.url}` : "Open page"; + case "findInPage": { + const pattern = action.pattern ? ` for '${action.pattern}'` : ""; + const url = action.url ? ` in ${action.url}` : ""; + return `Find in page${pattern}${url}`.trim(); + } + case "other": + return "Web search"; + } +} + +function createWebSearchRawInput(item: ThreadItem & { type: "webSearch" }): { + query: string, + action: (ThreadItem & { type: "webSearch" })["action"], +} { + return { + query: item.query, + action: item.action, + }; +} + function createCommandActionEvent( id: string, status: CommandExecutionStatus, diff --git a/src/__tests__/CodexACPAgent/data/web-search-action-titles.json b/src/__tests__/CodexACPAgent/data/web-search-action-titles.json new file mode 100644 index 00000000..e22175bb --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/web-search-action-titles.json @@ -0,0 +1,45 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "web-open-1", + "kind": "search", + "title": "Open page: https://agentclientprotocol.com", + "status": "in_progress", + "rawInput": { + "query": "https://agentclientprotocol.com", + "action": { + "type": "openPage", + "url": "https://agentclientprotocol.com" + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "web-find-1", + "kind": "search", + "title": "Find in page for 'tool calls' in https://agentclientprotocol.com/protocol", + "status": "in_progress", + "rawInput": { + "query": "protocol", + "action": { + "type": "findInPage", + "url": "https://agentclientprotocol.com/protocol", + "pattern": "tool calls" + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json b/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json new file mode 100644 index 00000000..63540b36 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json @@ -0,0 +1,45 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "web-search-1", + "kind": "search", + "title": "Web search: agent client protocol", + "status": "in_progress", + "rawInput": { + "query": "agent client protocol", + "action": { + "type": "search", + "query": "agent client protocol", + "queries": null + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "web-search-1", + "title": "Web search: agent client protocol", + "status": "completed", + "rawInput": { + "query": "agent client protocol", + "action": { + "type": "search", + "query": "agent client protocol", + "queries": null + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/CodexACPAgent/web-search-events.test.ts b/src/__tests__/CodexACPAgent/web-search-events.test.ts new file mode 100644 index 00000000..77c3b741 --- /dev/null +++ b/src/__tests__/CodexACPAgent/web-search-events.test.ts @@ -0,0 +1,115 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ServerNotification } from "../../app-server"; +import type { SessionState } from "../../CodexAcpServer"; +import { AgentMode } from "../../AgentMode"; +import { + createCodexMockTestFixture, + createTestSessionState, + setupPromptAndSendNotifications, + type CodexMockTestFixture +} from "../acp-test-utils"; + +describe("CodexEventHandler - web search events", () => { + let mockFixture: CodexMockTestFixture; + const sessionId = "test-session-id"; + + beforeEach(() => { + mockFixture = createCodexMockTestFixture(); + vi.clearAllMocks(); + }); + + const sessionState: SessionState = createTestSessionState({ + sessionId, + currentModelId: "model-id[effort]", + agentMode: AgentMode.DEFAULT_AGENT_MODE + }); + + it("maps web search start and completion to a search tool call", async () => { + const notifications: ServerNotification[] = [ + { + method: "item/started", + params: { + threadId: sessionId, + turnId: "turn-1", + item: { + type: "webSearch", + id: "web-search-1", + query: "agent client protocol", + action: { + type: "search", + query: "agent client protocol", + queries: null, + }, + }, + }, + }, + { + method: "item/completed", + params: { + threadId: sessionId, + turnId: "turn-1", + item: { + type: "webSearch", + id: "web-search-1", + query: "agent client protocol", + action: { + type: "search", + query: "agent client protocol", + queries: null, + }, + }, + }, + }, + ]; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + "data/web-search-start-and-complete.json" + ); + }); + + it("formats open-page and find-in-page web search actions", async () => { + const notifications: ServerNotification[] = [ + { + method: "item/started", + params: { + threadId: sessionId, + turnId: "turn-1", + item: { + type: "webSearch", + id: "web-open-1", + query: "https://agentclientprotocol.com", + action: { + type: "openPage", + url: "https://agentclientprotocol.com", + }, + }, + }, + }, + { + method: "item/started", + params: { + threadId: sessionId, + turnId: "turn-1", + item: { + type: "webSearch", + id: "web-find-1", + query: "protocol", + action: { + type: "findInPage", + url: "https://agentclientprotocol.com/protocol", + pattern: "tool calls", + }, + }, + }, + }, + ]; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + "data/web-search-action-titles.json" + ); + }); +}); From c82dc5ef86afcb06f0438018f65fead23d326f15 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 15 Jun 2026 14:40:53 +0200 Subject: [PATCH 2/4] Expose full web search item as raw input --- src/CodexToolCallMapper.ts | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index 199b9012..a2aeb548 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -33,6 +33,7 @@ type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed"; type GuardianApprovalReviewNotification = | ItemGuardianApprovalReviewStartedNotification | ItemGuardianApprovalReviewCompletedNotification; +type WebSearchItem = ThreadItem & { type: "webSearch" }; function toAcpStatus(status: CodexItemStatus): AcpToolCallStatus { switch (status) { @@ -230,7 +231,7 @@ export function createFuzzyFileSearchComplete( } export function createWebSearchStartUpdate( - item: ThreadItem & { type: "webSearch" } + item: WebSearchItem ): UpdateSessionEvent { return { sessionUpdate: "tool_call", @@ -238,23 +239,23 @@ export function createWebSearchStartUpdate( kind: "search", title: formatWebSearchTitle(item), status: "in_progress", - rawInput: createWebSearchRawInput(item), + rawInput: item, }; } export function createWebSearchCompleteUpdate( - item: ThreadItem & { type: "webSearch" } + item: WebSearchItem ): UpdateSessionEvent { return { sessionUpdate: "tool_call_update", toolCallId: item.id, title: formatWebSearchTitle(item), status: "completed", - rawInput: createWebSearchRawInput(item), + rawInput: item, }; } -export function formatWebSearchTitle(item: ThreadItem & { type: "webSearch" }): string { +export function formatWebSearchTitle(item: WebSearchItem): string { const action = item.action; if (!action) { return item.query ? `Web search: ${item.query}` : "Web search"; @@ -277,16 +278,6 @@ export function formatWebSearchTitle(item: ThreadItem & { type: "webSearch" }): } } -function createWebSearchRawInput(item: ThreadItem & { type: "webSearch" }): { - query: string, - action: (ThreadItem & { type: "webSearch" })["action"], -} { - return { - query: item.query, - action: item.action, - }; -} - function createCommandActionEvent( id: string, status: CommandExecutionStatus, From ea2b67cb0384cc1f05c44ee54c836857aebc0a4d Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 15 Jun 2026 14:47:16 +0200 Subject: [PATCH 3/4] fix types from new codex version --- src/__tests__/CodexACPAgent/web-search-events.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/__tests__/CodexACPAgent/web-search-events.test.ts b/src/__tests__/CodexACPAgent/web-search-events.test.ts index 77c3b741..da1074b1 100644 --- a/src/__tests__/CodexACPAgent/web-search-events.test.ts +++ b/src/__tests__/CodexACPAgent/web-search-events.test.ts @@ -31,6 +31,7 @@ describe("CodexEventHandler - web search events", () => { params: { threadId: sessionId, turnId: "turn-1", + startedAtMs: 0, item: { type: "webSearch", id: "web-search-1", @@ -48,6 +49,7 @@ describe("CodexEventHandler - web search events", () => { params: { threadId: sessionId, turnId: "turn-1", + completedAtMs: 0, item: { type: "webSearch", id: "web-search-1", @@ -76,6 +78,7 @@ describe("CodexEventHandler - web search events", () => { params: { threadId: sessionId, turnId: "turn-1", + startedAtMs: 0, item: { type: "webSearch", id: "web-open-1", @@ -92,6 +95,7 @@ describe("CodexEventHandler - web search events", () => { params: { threadId: sessionId, turnId: "turn-1", + startedAtMs: 0, item: { type: "webSearch", id: "web-find-1", From e4d44a0d91e02c70323924cd80c24f820967bf6f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 15 Jun 2026 14:53:16 +0200 Subject: [PATCH 4/4] Update web search snapshots with raw input metadata --- .../CodexACPAgent/data/web-search-action-titles.json | 4 ++++ .../CodexACPAgent/data/web-search-start-and-complete.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/__tests__/CodexACPAgent/data/web-search-action-titles.json b/src/__tests__/CodexACPAgent/data/web-search-action-titles.json index e22175bb..09bc7e92 100644 --- a/src/__tests__/CodexACPAgent/data/web-search-action-titles.json +++ b/src/__tests__/CodexACPAgent/data/web-search-action-titles.json @@ -10,6 +10,8 @@ "title": "Open page: https://agentclientprotocol.com", "status": "in_progress", "rawInput": { + "type": "webSearch", + "id": "web-open-1", "query": "https://agentclientprotocol.com", "action": { "type": "openPage", @@ -32,6 +34,8 @@ "title": "Find in page for 'tool calls' in https://agentclientprotocol.com/protocol", "status": "in_progress", "rawInput": { + "type": "webSearch", + "id": "web-find-1", "query": "protocol", "action": { "type": "findInPage", diff --git a/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json b/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json index 63540b36..4b93beba 100644 --- a/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json +++ b/src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json @@ -10,6 +10,8 @@ "title": "Web search: agent client protocol", "status": "in_progress", "rawInput": { + "type": "webSearch", + "id": "web-search-1", "query": "agent client protocol", "action": { "type": "search", @@ -32,6 +34,8 @@ "title": "Web search: agent client protocol", "status": "completed", "rawInput": { + "type": "webSearch", + "id": "web-search-1", "query": "agent client protocol", "action": { "type": "search",