Skip to content

Commit 0c517b0

Browse files
authored
fix: Handle more web search events (#184)
* fix: Handle more web search events * Expose full web search item as raw input * fix types from new codex version * Update web search snapshots with raw input metadata
1 parent 26b801d commit 0c517b0

6 files changed

Lines changed: 274 additions & 26 deletions

File tree

src/CodexAcpServer.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
createDynamicToolCallUpdate,
4040
createFileChangeUpdate,
4141
createMcpToolCallUpdate,
42+
formatWebSearchTitle,
4243
} from "./CodexToolCallMapper";
4344
import {
4445
createFastModeConfigOption,
@@ -898,7 +899,7 @@ export class CodexAcpServer implements acp.Agent {
898899
sessionUpdate: "tool_call",
899900
toolCallId: item.id,
900901
kind: "search",
901-
title: this.formatWebSearchTitle(item),
902+
title: formatWebSearchTitle(item),
902903
status: "completed",
903904
rawInput: {
904905
query: item.query,
@@ -958,29 +959,6 @@ export class CodexAcpServer implements acp.Agent {
958959
};
959960
}
960961

961-
private formatWebSearchTitle(item: ThreadItem & { type: "webSearch" }): string {
962-
const action = item.action;
963-
if (!action) {
964-
return item.query ? `Web search: ${item.query}` : "Web search";
965-
}
966-
switch (action.type) {
967-
case "search": {
968-
const queries = action.queries?.filter((query) => query && query.length > 0) ?? [];
969-
const query = action.query ?? (queries.length > 0 ? queries.join(", ") : null) ?? item.query;
970-
return query ? `Web search: ${query}` : "Web search";
971-
}
972-
case "openPage":
973-
return action.url ? `Open page: ${action.url}` : "Open page";
974-
case "findInPage": {
975-
const pattern = action.pattern ? ` for '${action.pattern}'` : "";
976-
const url = action.url ? ` in ${action.url}` : "";
977-
return `Find in page${pattern}${url}`.trim();
978-
}
979-
case "other":
980-
return "Web search";
981-
}
982-
}
983-
984962
private toAcpToolCallStatus(status: CollabAgentToolCallStatus): "in_progress" | "completed" | "failed" {
985963
switch (status) {
986964
case "inProgress":

src/CodexEventHandler.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import {
4040
createFuzzyFileSearchComplete,
4141
createFuzzyFileSearchStartOrUpdate,
4242
createMcpToolCallUpdate,
43+
createWebSearchCompleteUpdate,
44+
createWebSearchStartUpdate,
4345
fuzzyFileSearchToolCallId,
4446
} from "./CodexToolCallMapper";
4547
import { stripShellPrefix } from "./CommandUtils";
@@ -303,12 +305,13 @@ export class CodexEventHandler {
303305
return await createMcpToolCallUpdate(event.item);
304306
case "dynamicToolCall":
305307
return await createDynamicToolCallUpdate(event.item);
308+
case "webSearch":
309+
return createWebSearchStartUpdate(event.item);
306310
case "collabAgentToolCall":
307311
case "userMessage":
308312
case "hookPrompt":
309313
case "agentMessage":
310314
case "reasoning":
311-
case "webSearch":
312315
case "imageView":
313316
case "imageGeneration":
314317
case "enteredReviewMode":
@@ -348,11 +351,12 @@ export class CodexEventHandler {
348351
text: summary
349352
}
350353
}
354+
case "webSearch":
355+
return createWebSearchCompleteUpdate(event.item);
351356
case "collabAgentToolCall":
352357
case "userMessage":
353358
case "hookPrompt":
354359
case "agentMessage":
355-
case "webSearch":
356360
case "imageView":
357361
case "imageGeneration":
358362
case "enteredReviewMode":

src/CodexToolCallMapper.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed";
3333
type GuardianApprovalReviewNotification =
3434
| ItemGuardianApprovalReviewStartedNotification
3535
| ItemGuardianApprovalReviewCompletedNotification;
36+
type WebSearchItem = ThreadItem & { type: "webSearch" };
3637

3738
function toAcpStatus(status: CodexItemStatus): AcpToolCallStatus {
3839
switch (status) {
@@ -229,6 +230,54 @@ export function createFuzzyFileSearchComplete(
229230
};
230231
}
231232

233+
export function createWebSearchStartUpdate(
234+
item: WebSearchItem
235+
): UpdateSessionEvent {
236+
return {
237+
sessionUpdate: "tool_call",
238+
toolCallId: item.id,
239+
kind: "search",
240+
title: formatWebSearchTitle(item),
241+
status: "in_progress",
242+
rawInput: item,
243+
};
244+
}
245+
246+
export function createWebSearchCompleteUpdate(
247+
item: WebSearchItem
248+
): UpdateSessionEvent {
249+
return {
250+
sessionUpdate: "tool_call_update",
251+
toolCallId: item.id,
252+
title: formatWebSearchTitle(item),
253+
status: "completed",
254+
rawInput: item,
255+
};
256+
}
257+
258+
export function formatWebSearchTitle(item: WebSearchItem): string {
259+
const action = item.action;
260+
if (!action) {
261+
return item.query ? `Web search: ${item.query}` : "Web search";
262+
}
263+
switch (action.type) {
264+
case "search": {
265+
const queries = action.queries?.filter((query) => query && query.length > 0) ?? [];
266+
const query = action.query ?? (queries.length > 0 ? queries.join(", ") : null) ?? item.query;
267+
return query ? `Web search: ${query}` : "Web search";
268+
}
269+
case "openPage":
270+
return action.url ? `Open page: ${action.url}` : "Open page";
271+
case "findInPage": {
272+
const pattern = action.pattern ? ` for '${action.pattern}'` : "";
273+
const url = action.url ? ` in ${action.url}` : "";
274+
return `Find in page${pattern}${url}`.trim();
275+
}
276+
case "other":
277+
return "Web search";
278+
}
279+
}
280+
232281
function createCommandActionEvent(
233282
id: string,
234283
status: CommandExecutionStatus,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "tool_call",
8+
"toolCallId": "web-open-1",
9+
"kind": "search",
10+
"title": "Open page: https://agentclientprotocol.com",
11+
"status": "in_progress",
12+
"rawInput": {
13+
"type": "webSearch",
14+
"id": "web-open-1",
15+
"query": "https://agentclientprotocol.com",
16+
"action": {
17+
"type": "openPage",
18+
"url": "https://agentclientprotocol.com"
19+
}
20+
}
21+
}
22+
}
23+
]
24+
}
25+
{
26+
"method": "sessionUpdate",
27+
"args": [
28+
{
29+
"sessionId": "test-session-id",
30+
"update": {
31+
"sessionUpdate": "tool_call",
32+
"toolCallId": "web-find-1",
33+
"kind": "search",
34+
"title": "Find in page for 'tool calls' in https://agentclientprotocol.com/protocol",
35+
"status": "in_progress",
36+
"rawInput": {
37+
"type": "webSearch",
38+
"id": "web-find-1",
39+
"query": "protocol",
40+
"action": {
41+
"type": "findInPage",
42+
"url": "https://agentclientprotocol.com/protocol",
43+
"pattern": "tool calls"
44+
}
45+
}
46+
}
47+
}
48+
]
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "tool_call",
8+
"toolCallId": "web-search-1",
9+
"kind": "search",
10+
"title": "Web search: agent client protocol",
11+
"status": "in_progress",
12+
"rawInput": {
13+
"type": "webSearch",
14+
"id": "web-search-1",
15+
"query": "agent client protocol",
16+
"action": {
17+
"type": "search",
18+
"query": "agent client protocol",
19+
"queries": null
20+
}
21+
}
22+
}
23+
}
24+
]
25+
}
26+
{
27+
"method": "sessionUpdate",
28+
"args": [
29+
{
30+
"sessionId": "test-session-id",
31+
"update": {
32+
"sessionUpdate": "tool_call_update",
33+
"toolCallId": "web-search-1",
34+
"title": "Web search: agent client protocol",
35+
"status": "completed",
36+
"rawInput": {
37+
"type": "webSearch",
38+
"id": "web-search-1",
39+
"query": "agent client protocol",
40+
"action": {
41+
"type": "search",
42+
"query": "agent client protocol",
43+
"queries": null
44+
}
45+
}
46+
}
47+
}
48+
]
49+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
import type { ServerNotification } from "../../app-server";
3+
import type { SessionState } from "../../CodexAcpServer";
4+
import { AgentMode } from "../../AgentMode";
5+
import {
6+
createCodexMockTestFixture,
7+
createTestSessionState,
8+
setupPromptAndSendNotifications,
9+
type CodexMockTestFixture
10+
} from "../acp-test-utils";
11+
12+
describe("CodexEventHandler - web search events", () => {
13+
let mockFixture: CodexMockTestFixture;
14+
const sessionId = "test-session-id";
15+
16+
beforeEach(() => {
17+
mockFixture = createCodexMockTestFixture();
18+
vi.clearAllMocks();
19+
});
20+
21+
const sessionState: SessionState = createTestSessionState({
22+
sessionId,
23+
currentModelId: "model-id[effort]",
24+
agentMode: AgentMode.DEFAULT_AGENT_MODE
25+
});
26+
27+
it("maps web search start and completion to a search tool call", async () => {
28+
const notifications: ServerNotification[] = [
29+
{
30+
method: "item/started",
31+
params: {
32+
threadId: sessionId,
33+
turnId: "turn-1",
34+
startedAtMs: 0,
35+
item: {
36+
type: "webSearch",
37+
id: "web-search-1",
38+
query: "agent client protocol",
39+
action: {
40+
type: "search",
41+
query: "agent client protocol",
42+
queries: null,
43+
},
44+
},
45+
},
46+
},
47+
{
48+
method: "item/completed",
49+
params: {
50+
threadId: sessionId,
51+
turnId: "turn-1",
52+
completedAtMs: 0,
53+
item: {
54+
type: "webSearch",
55+
id: "web-search-1",
56+
query: "agent client protocol",
57+
action: {
58+
type: "search",
59+
query: "agent client protocol",
60+
queries: null,
61+
},
62+
},
63+
},
64+
},
65+
];
66+
67+
await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications);
68+
69+
await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot(
70+
"data/web-search-start-and-complete.json"
71+
);
72+
});
73+
74+
it("formats open-page and find-in-page web search actions", async () => {
75+
const notifications: ServerNotification[] = [
76+
{
77+
method: "item/started",
78+
params: {
79+
threadId: sessionId,
80+
turnId: "turn-1",
81+
startedAtMs: 0,
82+
item: {
83+
type: "webSearch",
84+
id: "web-open-1",
85+
query: "https://agentclientprotocol.com",
86+
action: {
87+
type: "openPage",
88+
url: "https://agentclientprotocol.com",
89+
},
90+
},
91+
},
92+
},
93+
{
94+
method: "item/started",
95+
params: {
96+
threadId: sessionId,
97+
turnId: "turn-1",
98+
startedAtMs: 0,
99+
item: {
100+
type: "webSearch",
101+
id: "web-find-1",
102+
query: "protocol",
103+
action: {
104+
type: "findInPage",
105+
url: "https://agentclientprotocol.com/protocol",
106+
pattern: "tool calls",
107+
},
108+
},
109+
},
110+
},
111+
];
112+
113+
await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications);
114+
115+
await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot(
116+
"data/web-search-action-titles.json"
117+
);
118+
});
119+
});

0 commit comments

Comments
 (0)