Skip to content

Commit a193548

Browse files
authored
fix: Map collab agent tool call events (#223)
Matches replay behavior. Closes #222
1 parent 302e995 commit a193548

5 files changed

Lines changed: 189 additions & 34 deletions

File tree

src/CodexAcpServer.ts

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {ACPSessionConnection, type AcpClientConnection, type UpdateSessionEvent}
1010
import type {InputModality, ReasoningEffort} from "./app-server";
1111
import type {
1212
Account,
13-
CollabAgentToolCallStatus,
1413
Model,
1514
ReasoningEffortOption,
1615
Thread,
@@ -45,6 +44,7 @@ import {
4544
LEGACY_SET_SESSION_MODEL_METHOD,
4645
} from "./AcpExtensions";
4746
import {
47+
createCollabAgentToolCallUpdate,
4848
createCommandExecutionCompleteUpdate,
4949
createCommandExecutionUpdate,
5050
createDynamicToolCallUpdate,
@@ -907,7 +907,7 @@ export class CodexAcpServer {
907907
case "dynamicToolCall":
908908
return [await createDynamicToolCallUpdate(item)];
909909
case "collabAgentToolCall":
910-
return [this.createCollabAgentToolCallUpdate(item)];
910+
return [createCollabAgentToolCallUpdate(item)];
911911
case "webSearch":
912912
return [this.createWebSearchUpdate(item)];
913913
case "imageView":
@@ -947,25 +947,6 @@ export class CodexAcpServer {
947947
}));
948948
}
949949

950-
private createCollabAgentToolCallUpdate(
951-
item: ThreadItem & { type: "collabAgentToolCall" }
952-
): UpdateSessionEvent {
953-
return {
954-
sessionUpdate: "tool_call",
955-
toolCallId: item.id,
956-
kind: "other",
957-
title: `collab.${item.tool}`,
958-
status: this.toAcpToolCallStatus(item.status),
959-
rawInput: {
960-
prompt: item.prompt,
961-
senderThreadId: item.senderThreadId,
962-
receiverThreadIds: item.receiverThreadIds,
963-
agentsStates: item.agentsStates,
964-
status: item.status,
965-
},
966-
};
967-
}
968-
969950
private createWebSearchUpdate(
970951
item: ThreadItem & { type: "webSearch" }
971952
): UpdateSessionEvent {
@@ -1017,17 +998,6 @@ export class CodexAcpServer {
1017998
};
1018999
}
10191000

1020-
private toAcpToolCallStatus(status: CollabAgentToolCallStatus): "in_progress" | "completed" | "failed" {
1021-
switch (status) {
1022-
case "inProgress":
1023-
return "in_progress";
1024-
case "completed":
1025-
return "completed";
1026-
case "failed":
1027-
return "failed";
1028-
}
1029-
}
1030-
10311001
private userInputToContentBlocks(input: UserInput): acp.ContentBlock[] {
10321002
switch (input.type) {
10331003
case "text":

src/CodexEventHandler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import type { McpStartupCompleteEvent } from "./app-server";
3333
import {toTokenCount} from "./TokenCount";
3434
import {
3535
commandExecutionUsesTerminalOutput,
36+
createCollabAgentToolCallCompleteUpdate,
37+
createCollabAgentToolCallUpdate,
3638
createCommandExecutionUpdate,
3739
createDynamicToolCallUpdate,
3840
createFileChangeUpdate,
@@ -354,6 +356,7 @@ export class CodexEventHandler {
354356
this.activeImageGenerationItems.add(event.item.id);
355357
return createImageGenerationStartUpdate(event.item);
356358
case "collabAgentToolCall":
359+
return createCollabAgentToolCallUpdate(event.item);
357360
case "subAgentActivity":
358361
case "sleep":
359362
case "userMessage":
@@ -404,12 +407,13 @@ export class CodexEventHandler {
404407
return this.createCompletedReasoningEvent(event.item);
405408
case "webSearch":
406409
return createWebSearchCompleteUpdate(event.item);
410+
case "collabAgentToolCall":
411+
return createCollabAgentToolCallCompleteUpdate(event.item);
407412
case "exitedReviewMode":
408413
return this.createExitedReviewModeEvent(event.item);
409414
case "contextCompaction":
410415
return this.createContextCompactedEvent();
411416
//ignored types
412-
case "collabAgentToolCall":
413417
case "subAgentActivity":
414418
case "sleep":
415419
case "userMessage":

src/CodexToolCallMapper.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
FuzzyFileSearchSessionUpdatedNotification
1010
} from "./app-server";
1111
import type {
12+
CollabAgentToolCallStatus,
1213
CommandAction,
1314
CommandExecutionStatus,
1415
DynamicToolCallStatus,
@@ -32,12 +33,13 @@ import {
3233
type TerminalOutputMode,
3334
} from "./TerminalOutputMode";
3435

35-
type CodexItemStatus = CommandExecutionStatus | PatchApplyStatus | McpToolCallStatus | DynamicToolCallStatus;
36+
type CodexItemStatus = CommandExecutionStatus | PatchApplyStatus | McpToolCallStatus | DynamicToolCallStatus | CollabAgentToolCallStatus;
3637
type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed";
3738
type GuardianApprovalReviewNotification =
3839
| ItemGuardianApprovalReviewStartedNotification
3940
| ItemGuardianApprovalReviewCompletedNotification;
4041
type WebSearchItem = ThreadItem & { type: "webSearch" };
42+
type CollabAgentToolCallItem = ThreadItem & { type: "collabAgentToolCall" };
4143
type CommandExecutionItem = ThreadItem & { type: "commandExecution" };
4244
type AcpToolCallEvent = Extract<UpdateSessionEvent, { sessionUpdate: "tool_call" }>;
4345

@@ -356,6 +358,41 @@ export function createWebSearchCompleteUpdate(
356358
};
357359
}
358360

361+
export function createCollabAgentToolCallUpdate(
362+
item: CollabAgentToolCallItem
363+
): UpdateSessionEvent {
364+
return {
365+
sessionUpdate: "tool_call",
366+
toolCallId: item.id,
367+
kind: "other",
368+
title: item.tool,
369+
status: toAcpStatus(item.status),
370+
rawInput: createCollabAgentToolCallRawInput(item),
371+
};
372+
}
373+
374+
export function createCollabAgentToolCallCompleteUpdate(
375+
item: CollabAgentToolCallItem
376+
): UpdateSessionEvent {
377+
return {
378+
sessionUpdate: "tool_call_update",
379+
toolCallId: item.id,
380+
title: item.tool,
381+
status: toAcpStatus(item.status),
382+
rawInput: createCollabAgentToolCallRawInput(item),
383+
};
384+
}
385+
386+
function createCollabAgentToolCallRawInput(item: CollabAgentToolCallItem) {
387+
return {
388+
prompt: item.prompt,
389+
senderThreadId: item.senderThreadId,
390+
receiverThreadIds: item.receiverThreadIds,
391+
agentsStates: item.agentsStates,
392+
status: item.status,
393+
};
394+
}
395+
359396
export function formatWebSearchTitle(item: WebSearchItem): string {
360397
const action = item.action;
361398
if (!action) {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 - collab agent tool call 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 live collab agent tool calls to ACP tool call updates", 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: "collabAgentToolCall",
37+
id: "call-spawn-weather",
38+
tool: "spawnAgent",
39+
status: "inProgress",
40+
senderThreadId: "thread-main",
41+
receiverThreadIds: ["thread-paris"],
42+
prompt: "Find the current weather in Paris.",
43+
model: null,
44+
reasoningEffort: null,
45+
agentsStates: {
46+
"thread-paris": {
47+
status: "running",
48+
message: "Checking weather",
49+
},
50+
},
51+
},
52+
},
53+
},
54+
{
55+
method: "item/completed",
56+
params: {
57+
threadId: sessionId,
58+
turnId: "turn-1",
59+
completedAtMs: 0,
60+
item: {
61+
type: "collabAgentToolCall",
62+
id: "call-spawn-weather",
63+
tool: "spawnAgent",
64+
status: "completed",
65+
senderThreadId: "thread-main",
66+
receiverThreadIds: ["thread-paris"],
67+
prompt: "Find the current weather in Paris.",
68+
model: null,
69+
reasoningEffort: null,
70+
agentsStates: {
71+
"thread-paris": {
72+
status: "completed",
73+
message: null,
74+
},
75+
},
76+
},
77+
},
78+
},
79+
];
80+
81+
await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications);
82+
83+
await expect(`${mockFixture.getAcpConnectionDump([])}\n`).toMatchFileSnapshot(
84+
"data/collab-agent-tool-call-flow.json"
85+
);
86+
});
87+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "tool_call",
8+
"toolCallId": "call-spawn-weather",
9+
"kind": "other",
10+
"title": "spawnAgent",
11+
"status": "in_progress",
12+
"rawInput": {
13+
"prompt": "Find the current weather in Paris.",
14+
"senderThreadId": "thread-main",
15+
"receiverThreadIds": [
16+
"thread-paris"
17+
],
18+
"agentsStates": {
19+
"thread-paris": {
20+
"status": "running",
21+
"message": "Checking weather"
22+
}
23+
},
24+
"status": "inProgress"
25+
}
26+
}
27+
}
28+
]
29+
}
30+
{
31+
"method": "sessionUpdate",
32+
"args": [
33+
{
34+
"sessionId": "test-session-id",
35+
"update": {
36+
"sessionUpdate": "tool_call_update",
37+
"toolCallId": "call-spawn-weather",
38+
"title": "spawnAgent",
39+
"status": "completed",
40+
"rawInput": {
41+
"prompt": "Find the current weather in Paris.",
42+
"senderThreadId": "thread-main",
43+
"receiverThreadIds": [
44+
"thread-paris"
45+
],
46+
"agentsStates": {
47+
"thread-paris": {
48+
"status": "completed",
49+
"message": null
50+
}
51+
},
52+
"status": "completed"
53+
}
54+
}
55+
}
56+
]
57+
}

0 commit comments

Comments
 (0)