Skip to content

Commit 83702c4

Browse files
author
Aleksandr Slapoguzov
committed
LLM-25226 Update Codex to 0.106.0 and align event mapping for new app-server API
- update integration to Codex 0.106.0 schema/contracts - keep persistExtendedHistory disabled (false) to avoid experimental full-history persistence - support dynamicToolCall as ACP tool calls (start + completion updates) - support fuzzyFileSearch/sessionUpdated and fuzzyFileSearch/sessionCompleted as search tool call flow - map model/rerouted notifications to user-visible agent_thought_chunk updates - update tests/snapshots for new protocol shapes and added event mappings - ensure compatibility changes needed for gpt-5.3-codex availability for JetBrains AI users
1 parent e497416 commit 83702c4

22 files changed

Lines changed: 679 additions & 12 deletions

src/AgentMode.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export class AgentMode {
2323
"Read-only",
2424
"Requires approval to edit files and run commands.",
2525
"on-request",
26-
{"type": "readOnly"},
26+
{
27+
"type": "readOnly",
28+
"access": {"type": "fullAccess"}
29+
},
2730
"read-only"
2831
);
2932
static readonly Agent = new AgentMode(
@@ -34,6 +37,7 @@ export class AgentMode {
3437
{
3538
type: "workspaceWrite",
3639
writableRoots: [],
40+
readOnlyAccess: {"type": "fullAccess"},
3741
networkAccess: false,
3842
excludeTmpdirEnvVar: false,
3943
excludeSlashTmp: false

src/CodexAcpClient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export class CodexAcpClient {
193193
modelProvider: this.getModelProvider(),
194194
path: null,
195195
personality: null,
196+
persistExtendedHistory: false,
196197
threadId: request.sessionId,
197198
});
198199
const codexModels = await this.fetchAvailableModels();
@@ -217,6 +218,7 @@ export class CodexAcpClient {
217218
modelProvider: this.getModelProvider(),
218219
path: null,
219220
personality: null,
221+
persistExtendedHistory: false,
220222
threadId: request.sessionId,
221223
});
222224
const codexModels = await this.fetchAvailableModels();
@@ -243,7 +245,8 @@ export class CodexAcpClient {
243245
developerInstructions: null,
244246
personality: null,
245247
ephemeral: null,
246-
experimentalRawEvents: false
248+
experimentalRawEvents: false,
249+
persistExtendedHistory: false
247250
});
248251

249252
const codexModels = await this.fetchAvailableModels();

src/CodexAcpServer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {logger} from "./Logger";
2222
import {isExtMethodRequest} from "./AcpExtensions";
2323
import {
2424
createCommandExecutionUpdate,
25+
createDynamicToolCallUpdate,
2526
createFileChangeUpdate,
2627
createMcpToolCallUpdate,
2728
} from "./CodexToolCallMapper";
@@ -401,6 +402,8 @@ export class CodexAcpServer implements acp.Agent {
401402
return [await createCommandExecutionUpdate(item)];
402403
case "mcpToolCall":
403404
return [await createMcpToolCallUpdate(item)];
405+
case "dynamicToolCall":
406+
return [await createDynamicToolCallUpdate(item)];
404407
case "collabAgentToolCall":
405408
return [this.createCollabAgentToolCallUpdate(item)];
406409
case "webSearch":

src/CodexEventHandler.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type {ServerNotification} from "./app-server";
1+
import type {
2+
FuzzyFileSearchSessionCompletedNotification,
3+
FuzzyFileSearchSessionUpdatedNotification,
4+
ServerNotification
5+
} from "./app-server";
26
import type {SessionState} from "./CodexAcpServer";
37
import * as acp from "@agentclientprotocol/sdk";
48
import {type PlanEntry, RequestError} from "@agentclientprotocol/sdk";
@@ -12,14 +16,19 @@ import type {
1216
ErrorNotification,
1317
ItemCompletedNotification,
1418
ItemStartedNotification, ThreadItem,
19+
ModelReroutedNotification,
1520
ThreadTokenUsageUpdatedNotification,
1621
TurnPlanUpdatedNotification
1722
} from "./app-server/v2";
1823
import {toTokenCount} from "./TokenCount";
1924
import {
2025
createCommandExecutionUpdate,
26+
createDynamicToolCallUpdate,
2127
createFileChangeUpdate,
28+
createFuzzyFileSearchComplete,
29+
createFuzzyFileSearchStartOrUpdate,
2230
createMcpToolCallUpdate,
31+
fuzzyFileSearchToolCallId,
2332
} from "./CodexToolCallMapper";
2433
import { stripShellPrefix } from "./CommandUtils";
2534

@@ -30,6 +39,7 @@ export class CodexEventHandler {
3039
private readonly connection: acp.AgentSideConnection;
3140
private readonly sessionState: SessionState;
3241
private failure: RequestError | null = null;
42+
private readonly activeFuzzyFileSearchSessions = new Set<string>();
3343

3444
constructor(connection: acp.AgentSideConnection, sessionState: SessionState) {
3545
this.connection = connection;
@@ -102,6 +112,16 @@ export class CodexEventHandler {
102112
}
103113
};
104114
case "windows/worldWritableWarning":
115+
case "thread/status/changed":
116+
case "thread/archived":
117+
case "thread/unarchived":
118+
case "thread/closed":
119+
case "thread/realtime/started":
120+
case "thread/realtime/itemAdded":
121+
case "thread/realtime/outputAudio/delta":
122+
case "thread/realtime/error":
123+
case "thread/realtime/closed":
124+
case "windowsSandbox/setupCompleted":
105125
case "account/login/completed":
106126
case "authStatusChange":
107127
case "loginChatGptComplete":
@@ -114,6 +134,12 @@ export class CodexEventHandler {
114134
case "item/plan/delta":
115135
case "app/list/updated":
116136
return null;
137+
case "model/rerouted":
138+
return this.createModelReroutedEvent(notification.params);
139+
case "fuzzyFileSearch/sessionUpdated":
140+
return this.handleFuzzyFileSearchSessionUpdated(notification.params);
141+
case "fuzzyFileSearch/sessionCompleted":
142+
return this.handleFuzzyFileSearchSessionCompleted(notification.params);
117143
}
118144
}
119145

@@ -138,6 +164,16 @@ export class CodexEventHandler {
138164
}
139165
}
140166

167+
private createModelReroutedEvent(event: ModelReroutedNotification): UpdateSessionEvent {
168+
return {
169+
sessionUpdate: "agent_thought_chunk",
170+
content: {
171+
type: "text",
172+
text: `Model rerouted from ${event.fromModel} to ${event.toModel} (${event.reason}).\n\n`
173+
}
174+
};
175+
}
176+
141177
private async createItemEvent(event: ItemStartedNotification): Promise<UpdateSessionEvent | null> {
142178
switch (event.item.type) {
143179
case "fileChange":
@@ -146,6 +182,8 @@ export class CodexEventHandler {
146182
return await createCommandExecutionUpdate(event.item);
147183
case "mcpToolCall":
148184
return await createMcpToolCallUpdate(event.item);
185+
case "dynamicToolCall":
186+
return await createDynamicToolCallUpdate(event.item);
149187
case "collabAgentToolCall":
150188
case "userMessage":
151189
case "agentMessage":
@@ -171,6 +209,12 @@ export class CodexEventHandler {
171209
}
172210
case "commandExecution":
173211
return this.completeCommandExecutionEvent(event.item);
212+
case "dynamicToolCall":
213+
return {
214+
sessionUpdate: "tool_call_update",
215+
toolCallId: event.item.id,
216+
status: event.item.status === "completed" ? "completed" : "failed"
217+
}
174218
case "reasoning":
175219
const summary = event.item.summary[0];
176220
if (!summary) return null;
@@ -278,10 +322,28 @@ export class CodexEventHandler {
278322
if (!this.sessionState.rateLimits) {
279323
this.sessionState.rateLimits = new Map();
280324
}
281-
this.sessionState.rateLimits.set(params.limitId, {
282-
limitId: params.limitId,
283-
limitName: params.limitName,
325+
const limitId = params.rateLimits.limitId ?? params.rateLimits.limitName ?? "unknown";
326+
this.sessionState.rateLimits.set(limitId, {
327+
limitId: limitId,
328+
limitName: params.rateLimits.limitName ?? limitId,
284329
snapshot: params.rateLimits,
285330
});
286331
}
332+
333+
private handleFuzzyFileSearchSessionUpdated(
334+
params: FuzzyFileSearchSessionUpdatedNotification
335+
): UpdateSessionEvent {
336+
const toolCallId = fuzzyFileSearchToolCallId(params.sessionId);
337+
const started = !this.activeFuzzyFileSearchSessions.has(toolCallId);
338+
this.activeFuzzyFileSearchSessions.add(toolCallId);
339+
return createFuzzyFileSearchStartOrUpdate(params, started);
340+
}
341+
342+
private handleFuzzyFileSearchSessionCompleted(
343+
params: FuzzyFileSearchSessionCompletedNotification
344+
): UpdateSessionEvent {
345+
const toolCallId = fuzzyFileSearchToolCallId(params.sessionId);
346+
this.activeFuzzyFileSearchSessions.delete(toolCallId);
347+
return createFuzzyFileSearchComplete(params);
348+
}
287349
}

src/CodexToolCallMapper.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import type { ToolCallContent } from "@agentclientprotocol/sdk";
22
import { applyPatch } from "diff";
33
import { readFile } from "node:fs/promises";
4+
import path from "node:path";
45
import type { UpdateSessionEvent } from "./ACPSessionConnection";
56
import { stripShellPrefix } from "./CommandUtils";
7+
import type {
8+
FuzzyFileSearchSessionCompletedNotification,
9+
FuzzyFileSearchSessionUpdatedNotification
10+
} from "./app-server";
611
import type {
712
CommandAction,
813
CommandExecutionStatus,
14+
DynamicToolCallStatus,
915
FileUpdateChange,
1016
McpToolCallStatus,
1117
PatchApplyStatus,
1218
ThreadItem,
1319
} from "./app-server/v2";
1420

15-
type CodexItemStatus = CommandExecutionStatus | PatchApplyStatus | McpToolCallStatus;
21+
type CodexItemStatus = CommandExecutionStatus | PatchApplyStatus | McpToolCallStatus | DynamicToolCallStatus;
1622
type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed";
1723

1824
function toAcpStatus(status: CodexItemStatus): AcpToolCallStatus {
@@ -86,6 +92,68 @@ export async function createMcpToolCallUpdate(
8692
};
8793
}
8894

95+
export async function createDynamicToolCallUpdate(
96+
item: ThreadItem & { type: "dynamicToolCall" }
97+
): Promise<UpdateSessionEvent> {
98+
return {
99+
sessionUpdate: "tool_call",
100+
toolCallId: item.id,
101+
kind: "execute",
102+
title: item.tool,
103+
status: toAcpStatus(item.status),
104+
rawInput: {
105+
arguments: item.arguments,
106+
},
107+
};
108+
}
109+
110+
export function fuzzyFileSearchToolCallId(sessionId: string): string {
111+
return `fuzzyFileSearch.${sessionId}`;
112+
}
113+
114+
export function createFuzzyFileSearchStartOrUpdate(
115+
event: FuzzyFileSearchSessionUpdatedNotification,
116+
started: boolean
117+
): UpdateSessionEvent {
118+
const toolCallId = fuzzyFileSearchToolCallId(event.sessionId);
119+
const title = createSearchTitle(event.query, null);
120+
const locations = event.files.map((file) => ({
121+
path: path.isAbsolute(file.path) ? file.path : path.join(file.root, file.path),
122+
}));
123+
124+
if (started) {
125+
return {
126+
sessionUpdate: "tool_call",
127+
toolCallId,
128+
kind: "search",
129+
title,
130+
status: "in_progress",
131+
locations,
132+
rawInput: {
133+
query: event.query,
134+
},
135+
};
136+
}
137+
138+
return {
139+
sessionUpdate: "tool_call_update",
140+
toolCallId,
141+
title,
142+
status: "in_progress",
143+
locations,
144+
};
145+
}
146+
147+
export function createFuzzyFileSearchComplete(
148+
event: FuzzyFileSearchSessionCompletedNotification
149+
): UpdateSessionEvent {
150+
return {
151+
sessionUpdate: "tool_call_update",
152+
toolCallId: fuzzyFileSearchToolCallId(event.sessionId),
153+
status: "completed",
154+
};
155+
}
156+
89157
function createCommandActionEvent(
90158
id: string,
91159
status: CommandExecutionStatus,

src/__tests__/CodexACPAgent/CodexAcpClient.test.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
1919
vi.clearAllMocks();
2020
});
2121

22-
const ignoredFields = ["thread", "cwd", "id", "createdAt", "path", "threadId", "userAgent", "sandbox", "conversationId", "origins", "supportedReasoningEfforts", "reasoningEffort", "model"];
22+
const ignoredFields = ["thread", "cwd", "id", "createdAt", "path", "threadId", "userAgent", "sandbox", "conversationId", "origins", "supportedReasoningEfforts", "reasoningEffort", "model", "readOnlyAccess"];
2323

2424
it.skip('should start conversation', async () => {
2525
const codexAcpAgent = fixture.getCodexAcpAgent();
@@ -127,6 +127,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
127127
upgrade: null,
128128
displayName: "gpt-5",
129129
description: "test model",
130+
hidden: false,
130131
supportedReasoningEfforts: [{ reasoningEffort: "medium", description: "balanced" }],
131132
defaultReasoningEffort: "medium",
132133
inputModalities: ["text"],
@@ -540,6 +541,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
540541
model: '5.2-codex',
541542
displayName: 'Codex 5.2',
542543
description: 'Coding model',
544+
hidden: false,
543545
supportedReasoningEfforts: [
544546
{ reasoningEffort: 'high', description: 'Deep' },
545547
{ reasoningEffort: 'medium', description: 'Balanced' }
@@ -555,6 +557,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
555557
model: '5.1',
556558
displayName: 'Standard 5.1',
557559
description: 'Standard model',
560+
hidden: false,
558561
supportedReasoningEfforts: [
559562
{ reasoningEffort: 'low', description: 'Fast' }
560563
],
@@ -677,6 +680,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
677680
limitId: "limit-1",
678681
limitName: "Standard",
679682
snapshot: {
683+
limitId: "limit-1",
684+
limitName: "Standard",
680685
primary: { usedPercent: 25, resetsAt: null, windowDurationMins: 60 },
681686
secondary: null,
682687
credits: null,
@@ -687,6 +692,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
687692
limitId: "limit-2",
688693
limitName: "Fast",
689694
snapshot: {
695+
limitId: "limit-2",
696+
limitName: "Fast",
690697
primary: { usedPercent: 80, resetsAt: null, windowDurationMins: 1440 },
691698
secondary: null,
692699
credits: null,
@@ -736,9 +743,9 @@ describe('ACP server test', { timeout: 40_000 }, () => {
736743
mockFixture.sendServerNotification({
737744
method: "account/rateLimits/updated",
738745
params: {
739-
limitId: "standard-limit",
740-
limitName: "Standard",
741746
rateLimits: {
747+
limitId: "standard-limit",
748+
limitName: "Standard",
742749
primary: { usedPercent: 30, resetsAt: null, windowDurationMins: 60 },
743750
secondary: null,
744751
credits: null,
@@ -750,9 +757,9 @@ describe('ACP server test', { timeout: 40_000 }, () => {
750757
mockFixture.sendServerNotification({
751758
method: "account/rateLimits/updated",
752759
params: {
753-
limitId: "fast-limit",
754-
limitName: "Fast",
755760
rateLimits: {
761+
limitId: "fast-limit",
762+
limitName: "Fast",
756763
primary: { usedPercent: 50, resetsAt: null, windowDurationMins: 1440 },
757764
secondary: null,
758765
credits: null,
@@ -767,6 +774,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
767774
limitId: "standard-limit",
768775
limitName: "Standard",
769776
snapshot: {
777+
limitId: "standard-limit",
778+
limitName: "Standard",
770779
primary: { usedPercent: 30, resetsAt: null, windowDurationMins: 60 },
771780
secondary: null,
772781
credits: null,
@@ -777,6 +786,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
777786
limitId: "fast-limit",
778787
limitName: "Fast",
779788
snapshot: {
789+
limitId: "fast-limit",
790+
limitName: "Fast",
780791
primary: { usedPercent: 50, resetsAt: null, windowDurationMins: 1440 },
781792
secondary: null,
782793
credits: null,

0 commit comments

Comments
 (0)