Skip to content

Commit 76f9ea2

Browse files
feat: LLM-25394 support Codex persist approval
1 parent 6cc08d5 commit 76f9ea2

28 files changed

Lines changed: 734 additions & 136 deletions

package-lock.json

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ApprovalOptionId.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
export const ApprovalOptionId = {
22
AllowOnce: "allow_once",
3-
AllowAlways: "allow_always",
3+
AllowForSession: "allow_for_session",
4+
AllowPersist: "allow_persist",
5+
AllowCommandPrefixRule: "allow_command_prefix_rule",
6+
ApplyNetworkPolicyAmendment: "apply_network_policy_amendment",
47
RejectOnce: "reject_once",
58
} as const;
69

src/CodexAcpClient.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,10 @@ export class CodexAcpClient {
201201

202202
async resumeSession(request: acp.ResumeSessionRequest): Promise<SessionMetadata> {
203203
await this.refreshSkills(request.cwd, request._meta);
204+
const sessionConfig = await this.createSessionConfig(request.cwd, request.mcpServers ?? []);
204205

205206
const response = await this.codexClient.threadResume({
206-
config: await this.createSessionConfig(request.cwd, request.mcpServers ?? []),
207+
config: sessionConfig.config,
207208
cwd: request.cwd,
208209
modelProvider: this.getResumeModelProvider(),
209210
threadId: request.sessionId,
@@ -215,12 +216,14 @@ export class CodexAcpClient {
215216
currentModelId: currentModelId,
216217
models: codexModels,
217218
currentServiceTier: response.serviceTier ?? null,
219+
configBackedMcpServerNames: sessionConfig.configBackedMcpServerNames,
218220
}
219221
}
220222

221223
async loadSession(request: acp.LoadSessionRequest): Promise<SessionMetadataWithThread> {
224+
const sessionConfig = await this.createSessionConfig(request.cwd, request.mcpServers ?? []);
222225
const response = await this.codexClient.threadResume({
223-
config: await this.createSessionConfig(request.cwd, request.mcpServers ?? []),
226+
config: sessionConfig.config,
224227
cwd: request.cwd,
225228
modelProvider: this.getResumeModelProvider(),
226229
threadId: request.sessionId,
@@ -232,15 +235,17 @@ export class CodexAcpClient {
232235
currentModelId: currentModelId,
233236
models: codexModels,
234237
currentServiceTier: response.serviceTier ?? null,
238+
configBackedMcpServerNames: sessionConfig.configBackedMcpServerNames,
235239
thread: response.thread,
236240
};
237241
}
238242

239243
async newSession(request: acp.NewSessionRequest): Promise<SessionMetadata> {
240244
await this.refreshSkills(request.cwd, request._meta);
245+
const sessionConfig = await this.createSessionConfig(request.cwd, request.mcpServers);
241246

242247
const response = await this.codexClient.threadStart({
243-
config: await this.createSessionConfig(request.cwd, request.mcpServers),
248+
config: sessionConfig.config,
244249
modelProvider: this.getModelProvider(),
245250
cwd: request.cwd,
246251
});
@@ -255,6 +260,7 @@ export class CodexAcpClient {
255260
currentModelId: currentModelId,
256261
models: codexModels,
257262
currentServiceTier: response.serviceTier ?? null,
263+
configBackedMcpServerNames: sessionConfig.configBackedMcpServerNames,
258264
};
259265
}
260266

@@ -266,7 +272,7 @@ export class CodexAcpClient {
266272
return this.codexClient.getMcpServerStartupVersion();
267273
}
268274

269-
private async createSessionConfig(projectPath: string, mcpServers: Array<McpServer>): Promise<JsonObject> {
275+
private async createSessionConfig(projectPath: string, mcpServers: Array<McpServer>): Promise<SessionConfig> {
270276
const mergedConfig = {
271277
...mergeGatewayConfig(this.config, this.gatewayConfig),
272278
projects: {
@@ -275,21 +281,30 @@ export class CodexAcpClient {
275281
}
276282
},
277283
};
284+
const configuredMcpServerNames = await this.getConfigMcpServerNames(projectPath);
278285
if (mcpServers.length === 0) {
279-
return mergedConfig;
286+
return {
287+
config: mergedConfig,
288+
configBackedMcpServerNames: configuredMcpServerNames,
289+
};
280290
}
281291

282292
// Deduplicates new servers against existing config to prevent Codex from deep-merging
283293
// incompatible field types (e.g., mixing url and stdio schemas).
284-
const existingNames = await this.getConfigMcpServerNames(projectPath);
285-
const uniqueServers = mcpServers.filter(mcp => !existingNames.has(mcp.name));
294+
const uniqueServers = mcpServers.filter(mcp => !configuredMcpServerNames.has(mcp.name));
286295
if (uniqueServers.length === 0) {
287-
return mergedConfig;
296+
return {
297+
config: mergedConfig,
298+
configBackedMcpServerNames: configuredMcpServerNames,
299+
};
288300
}
289301

290302
return {
291-
...mergedConfig,
292-
"mcp_servers": Object.fromEntries(uniqueServers.map(mcp => [mcp.name, this.createMcpSeverConfig(mcp)])),
303+
config: {
304+
...mergedConfig,
305+
"mcp_servers": Object.fromEntries(uniqueServers.map(mcp => [mcp.name, this.createMcpSeverConfig(mcp)])),
306+
},
307+
configBackedMcpServerNames: configuredMcpServerNames,
293308
};
294309
}
295310

@@ -569,12 +584,18 @@ export type SessionMetadata = {
569584
currentModelId: string,
570585
models: Model[],
571586
currentServiceTier?: ServiceTier | null,
587+
configBackedMcpServerNames?: Set<string>,
572588
}
573589

574590
export type SessionMetadataWithThread = SessionMetadata & {
575591
thread: Thread,
576592
}
577593

594+
type SessionConfig = {
595+
config: JsonObject,
596+
configBackedMcpServerNames: Set<string>,
597+
}
598+
578599
function buildPromptItems(prompt: acp.ContentBlock[]): UserInput[] {
579600
return prompt.map((block): UserInput | null => {
580601
switch (block.type) {

src/CodexAcpServer.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export interface SessionState {
5757
cwd: string;
5858
fastModeEnabled: boolean;
5959
currentModelSupportsFast: boolean;
60-
sessionMcpServers?: Array<string>;
60+
sessionMcpServers: Array<string>;
61+
configBackedMcpServerNames?: Set<string>;
6162
}
6263

6364
interface PendingMcpStartupSession {
@@ -126,7 +127,6 @@ export class CodexAcpServer implements acp.Agent {
126127
list: { }
127128
},
128129
mcpCapabilities: {
129-
acp: false,
130130
http: true,
131131
sse: false
132132
}
@@ -185,7 +185,6 @@ export class CodexAcpServer implements acp.Agent {
185185

186186
const account = await this.getActiveAccount();
187187
const {sessionId, currentModelId, models} = sessionMetadata;
188-
const sessionMcpServers = this.resolveSessionMcpServers(requestedMcpServers, "sessionId" in request);
189188
const currentModel = this.findCurrentModel(models, currentModelId);
190189
const currentModelSupportsFast = modelSupportsFast(currentModel);
191190
const sessionState: SessionState = {
@@ -203,7 +202,8 @@ export class CodexAcpServer implements acp.Agent {
203202
cwd: request.cwd,
204203
fastModeEnabled: sessionMetadata.currentServiceTier === "fast",
205204
currentModelSupportsFast: currentModelSupportsFast,
206-
sessionMcpServers: sessionMcpServers,
205+
sessionMcpServers: this.createSessionMcpServers(requestedMcpServers, "sessionId" in request),
206+
configBackedMcpServerNames: sessionMetadata.configBackedMcpServerNames ?? new Set(),
207207
}
208208
this.sessions.set(sessionId, sessionState);
209209

@@ -452,7 +452,6 @@ export class CodexAcpServer implements acp.Agent {
452452

453453
const account = await this.getActiveAccount();
454454
const {sessionId, currentModelId, models, thread} = sessionMetadata;
455-
const sessionMcpServers = this.resolveSessionMcpServers(requestedMcpServers, true);
456455
const currentModel = this.findCurrentModel(models, currentModelId);
457456
const currentModelSupportsFast = modelSupportsFast(currentModel);
458457
const sessionState: SessionState = {
@@ -470,7 +469,8 @@ export class CodexAcpServer implements acp.Agent {
470469
cwd: request.cwd,
471470
fastModeEnabled: sessionMetadata.currentServiceTier === "fast",
472471
currentModelSupportsFast: currentModelSupportsFast,
473-
sessionMcpServers: sessionMcpServers,
472+
sessionMcpServers: this.createSessionMcpServers(requestedMcpServers, true),
473+
configBackedMcpServerNames: sessionMetadata.configBackedMcpServerNames ?? new Set(),
474474
};
475475
this.sessions.set(sessionId, sessionState);
476476

@@ -724,14 +724,13 @@ export class CodexAcpServer implements acp.Agent {
724724
return sessionState;
725725
}
726726

727-
private resolveSessionMcpServers(
727+
private createSessionMcpServers(
728728
mcpServers: Array<acp.McpServer>,
729729
recoverFromStartup: boolean,
730730
): Array<string> {
731731
// Explicit MCP servers from the request are the primary source of truth for the session.
732-
const requestedServerNames = getRequestedMcpServerNames(mcpServers);
733-
if (requestedServerNames.length > 0) {
734-
return requestedServerNames;
732+
if (mcpServers.length > 0) {
733+
return mcpServers.map(server => server.name);
735734
}
736735
// Fresh sessions without MCP config should not inherit any session MCP state.
737736
if (!recoverFromStartup) {
@@ -955,7 +954,3 @@ export class CodexAcpServer implements acp.Agent {
955954
}
956955
}
957956
}
958-
959-
function getRequestedMcpServerNames(mcpServers: Array<acp.McpServer>): Array<string> {
960-
return Array.from(new Set(mcpServers.map(server => server.name)));
961-
}

src/CodexAppServerClient.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import type {
88
import type {
99
ConfigReadParams,
1010
ConfigReadResponse,
11+
ConfigBatchWriteParams,
12+
ConfigWriteResponse,
1113
GetAccountParams,
1214
GetAccountResponse,
1315
ListMcpServerStatusParams,

0 commit comments

Comments
 (0)