Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 2 additions & 24 deletions src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
createDynamicToolCallUpdate,
createFileChangeUpdate,
createMcpToolCallUpdate,
formatWebSearchTitle,
} from "./CodexToolCallMapper";
import {
createFastModeConfigOption,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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":
Expand Down
8 changes: 6 additions & 2 deletions src/CodexEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {
createFuzzyFileSearchComplete,
createFuzzyFileSearchStartOrUpdate,
createMcpToolCallUpdate,
createWebSearchCompleteUpdate,
createWebSearchStartUpdate,
fuzzyFileSearchToolCallId,
} from "./CodexToolCallMapper";
import { stripShellPrefix } from "./CommandUtils";
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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":
Expand Down
49 changes: 49 additions & 0 deletions src/CodexToolCallMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -229,6 +230,54 @@ export function createFuzzyFileSearchComplete(
};
}

export function createWebSearchStartUpdate(
item: WebSearchItem
): UpdateSessionEvent {
return {
sessionUpdate: "tool_call",
toolCallId: item.id,
kind: "search",
title: formatWebSearchTitle(item),
status: "in_progress",
rawInput: item,
};
}

export function createWebSearchCompleteUpdate(
item: WebSearchItem
): UpdateSessionEvent {
return {
sessionUpdate: "tool_call_update",
toolCallId: item.id,
title: formatWebSearchTitle(item),
status: "completed",
rawInput: item,
};
}

export function formatWebSearchTitle(item: WebSearchItem): 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 createCommandActionEvent(
id: string,
status: CommandExecutionStatus,
Expand Down
49 changes: 49 additions & 0 deletions src/__tests__/CodexACPAgent/data/web-search-action-titles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"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": {
"type": "webSearch",
"id": "web-open-1",
"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": {
"type": "webSearch",
"id": "web-find-1",
"query": "protocol",
"action": {
"type": "findInPage",
"url": "https://agentclientprotocol.com/protocol",
"pattern": "tool calls"
}
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"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": {
"type": "webSearch",
"id": "web-search-1",
"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": {
"type": "webSearch",
"id": "web-search-1",
"query": "agent client protocol",
"action": {
"type": "search",
"query": "agent client protocol",
"queries": null
}
}
}
}
]
}
119 changes: 119 additions & 0 deletions src/__tests__/CodexACPAgent/web-search-events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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",
startedAtMs: 0,
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",
completedAtMs: 0,
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",
startedAtMs: 0,
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",
startedAtMs: 0,
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"
);
});
});
Loading