1
>
/*---------------------------------------------------------------------------------------------
codexAgent.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process';
7
>
import * as fs from 'fs';
8
>
import * as os from 'os';
9
>
import { CancellationError } from '../../../../base/common/errors.js';
10
>
import { raceTimeout } from '../../../../base/common/async.js';
11
>
import { fetchResourceMetadata } from '../../../../base/common/oauth.js';
12
>
import { Emitter } from '../../../../base/common/event.js';
13
>
import { Disposable } from '../../../../base/common/lifecycle.js';
14
>
import { type IObservable, observableValue } from '../../../../base/common/observable.js';
15
>
import { basename, dirname, isAbsolute, join, resolve, sep } from '../../../../base/common/path.js';
16
>
import { StopWatch } from '../../../../base/common/stopwatch.js';
17
>
import { URI } from '../../../../base/common/uri.js';
18
>
import { generateUuid } from '../../../../base/common/uuid.js';
19
>
import { IInstantiationService } from '../../../instantiation/common/instantiation.js';
20
>
import { localize } from '../../../../nls.js';
21
>
import { ILogService } from '../../../log/common/log.js';
22
>
import { IProductService } from '../../../product/common/productService.js';
23
>
import { createSchema, platformRootSchema, platformSessionSchema, schemaProperty, AgentHostMcpServersConfigKey, type ISchemaProperty, type SessionMode } from '../../common/agentHostSchema.js';
24
>
import { createPricingMetaFromBilling, normalizeCAPIBilling } from '../../common/agentModelPricing.js';
25
>
import { AgentHostConfigKey, agentHostCustomizationConfigSchema, type CodexUsageSource } from '../../common/agentHostCustomizationConfig.js';
26
>
import { getReasoningEffortDescription, getReasoningEffortLabel } from '../../common/reasoningEffort.js';
27
>
import { AgentHostCodexAgentBinaryArgsEnvVar, AgentHostCodexAgentCodexHomeEnvVar, AgentHostCodexAgentSdkRootEnvVar, AgentSession, AgentSignal, CODEX_AGENT_PROVIDER_ID, IActiveClient, IAgent, IAgentChats, IAgentCreateChatForkSource, IAgentCreateChatResult, IAgentCreateChatOptions, IAgentCreateSessionConfig, IAgentCreateSessionResult, IAgentDescriptor, IAgentMaterializeSessionEvent, IAgentModelInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IMcpNotification, type AgentProvider, type AuthenticateParams } from '../../common/agentService.js';
28
>
import { SessionConfigKey } from '../../common/sessionConfigKeys.js';
29
>
import { AHP_AUTH_REQUIRED, ProtocolError } from '../../common/state/sessionProtocol.js';
30
>
import { ActionType, isChatAction, type SessionAction, type ChatAction } from '../../common/state/sessionActions.js';
31
>
import type { ConfigSchema, ModelSelection, ProtectedResourceMetadata, ToolDefinition, AgentSelection } from '../../common/state/protocol/state.js';
32
>
import type { ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../../common/state/protocol/commands.js';
33
>
import { AuthRequiredReason, type AuthRequiredParams } from '../../common/state/protocol/common/notifications.js';
34
>
import { buildDefaultChatUri, parseChatUri, type ClientPluginCustomization, type DirectoryCustomization, type MessageAttachment, type PendingMessage, type ChatInputAnswer, ChatInputResponseKind, type PolicyState, type ToolCallResult, ToolResultContentType, type Turn, ResponsePartKind } from '../../common/state/sessionState.js';
35
>
import type { IAgentServerToolHost } from '../../common/agentServerTools.js';
36
>
import { ActiveClientToolSet } from '../activeClientState.js';
37
>
import { McpCustomizationController } from '../shared/mcpCustomizationController.js';
38
>
import { buildCodexMcpReadResult, codexMcpListToInventory, codexMcpServersFromConfig, codexMcpToolsChanged, codexStartupErrorNeedsAuth, injectCodexMcpAuthTokens, inventoryToSdkServers, normalizeCodexMcpResourceUrl, translateCodexMcpStartupState, type ICodexMcpServerConfigJson, type ICodexMcpServerEntry } from './codexMcpServers.js';
39
>
import { codexHooksToContainers, codexSkillsToContainers } from './codexCustomizations.js';
40
>
import { CodexClientCustomizationStore, codexMcpServersFromPlugins, codexSkillRootsFromPlugins, type ICodexClientPlugin } from './codexClientCustomizations.js';
41
>
import { buildElicitationRequest, cancelledElicitationResponse, declinedElicitationResponse, elicitationResponseFromAnswers } from './codexElicitationMapper.js';
42
>
import { McpAuthRequiredReason, McpServerStatus, type AhpMcpUiHostCapabilities, type Customization, type McpServerState } from '../../common/state/protocol/channels-session/state.js';
43
>
import { IAgentConfigurationService } from '../agentConfigurationService.js';
44
>
import { IFileService } from '../../../files/common/files.js';
45
>
import { INativeEnvironmentService } from '../../../environment/common/environment.js';
46
>
import { IAgentPluginManager, type ISyncedCustomization } from '../../common/agentPluginManager.js';
47
>
import { parsePlugin } from '../../../agentPlugins/common/pluginParsers.js';
48
>
import { IAgentHostGitHubEndpointService } from '../agentHostGitHubEndpointService.js';
49
>
import { ICopilotApiService } from '../shared/copilotApiService.js';
50
>
import { extractForwardedErrorInfo } from '../shared/forwardedChatError.js';
51
>
import { IAgentSdkDownloader, IAgentSdkPackage } from '../agentSdkDownloader.js';
52
>
import { CancellationToken } from '../../../../base/common/cancellation.js';
53
>
import { PendingRequestRegistry } from '../../common/pendingRequestRegistry.js';
54
>
import { CodexAppServerClient, JsonRpcError, transportFromChildProcess, type ICodexAppServerClient, type ServerRequestHandlerResult } from './codexAppServerClient.js';
55
>
import { ICodexProxyService, type ICodexProxyHandle } from './codexProxyService.js';
56
>
import { createCodexSessionMapState, extractUserInputText, mapAgentMessageDelta, mapCommandExecutionOutputDelta, mapFileChangeOutputDelta, mapFileChangePatchUpdated, mapItemCompleted, mapItemStarted, mapMcpToolCallProgress, mapReasoningSummaryPartAdded, mapReasoningSummaryTextDelta, mapReasoningTextDelta, mapTokenUsageUpdated, mapTurnCompleted, mapTurnStarted, resetCodexTurnMapState, type ICodexSessionMapState } from './codexMapAppServerEvents.js';
57
>
import { unwrapShellInvocation } from './codexShellCommand.js';
58
>
import { planForkedTurnIdMap, resolveForkBoundary } from './codexForkPlan.js';
59
>
import { resolveCodexInput } from './codexPromptResolver.js';
60
>
import { buildUserInputRequest, emptyUserInputResponse, userInputResponseFromAnswers } from './codexUserInputMapper.js';
61
>
import { replayThreadToTurns } from './codexReplayMapper.js';
62
>
import { CodexSessionMetadataStore } from './codexSessionMetadataStore.js';
63
>
import { buildCodexLaunchConfig, buildCodexResumeParams, isCodexThreadProviderCompatible } from './codexLaunchConfig.js';
64
>
import { codexAccountStateForUsageSource, codexAccountStateFromResponse, codexProtectedResourcesForUsageSource, resolveCodexUsageSourceAfterAccountRead, type ICodexAccountState } from './codexAccountState.js';
65
>
import { CodexSessionConfigKey, CODEX_DEFAULT_PERMISSIONS_PRESET, CODEX_PERMISSIONS_PRESETS, collaborationModeKind, migrateCodexPermissionValues, narrowAdditionalDirectories, narrowBoolean, narrowPersonality, narrowReasoningEffort, narrowReasoningSummary, narrowWebSearchMode, resolveCodexPermissions, type CodexApprovalPolicy, type CodexPermissionsPreset, type ICodexResolvedPermissions } from './codexSessionConfigKeys.js';
66
>
import type { ReasoningEffort } from './protocol/generated/ReasoningEffort.js';
67
>
import type { ReasoningSummary } from './protocol/generated/ReasoningSummary.js';
68
>
import type { Personality } from './protocol/generated/Personality.js';
69
>
import type { WebSearchMode } from './protocol/generated/WebSearchMode.js';
70
>
import type { SandboxMode } from './protocol/generated/v2/SandboxMode.js';
71
>
import type { SandboxPolicy } from './protocol/generated/v2/SandboxPolicy.js';
72
>
import type { CommandExecutionApprovalDecision } from './protocol/generated/v2/CommandExecutionApprovalDecision.js';
73
>
import type { CommandExecutionRequestApprovalParams } from './protocol/generated/v2/CommandExecutionRequestApprovalParams.js';
74
>
import type { CommandExecutionRequestApprovalResponse } from './protocol/generated/v2/CommandExecutionRequestApprovalResponse.js';
75
>
import type { FileChangeApprovalDecision } from './protocol/generated/v2/FileChangeApprovalDecision.js';
76
>
import type { FileChangeRequestApprovalParams } from './protocol/generated/v2/FileChangeRequestApprovalParams.js';
77
>
import type { FileChangeRequestApprovalResponse } from './protocol/generated/v2/FileChangeRequestApprovalResponse.js';
78
>
import type { PermissionsRequestApprovalParams } from './protocol/generated/v2/PermissionsRequestApprovalParams.js';
79
>
import type { PermissionsRequestApprovalResponse } from './protocol/generated/v2/PermissionsRequestApprovalResponse.js';
80
>
import type { DynamicToolSpec } from './protocol/generated/v2/DynamicToolSpec.js';
81
>
import type { DynamicToolCallParams } from './protocol/generated/v2/DynamicToolCallParams.js';
82
>
import type { DynamicToolCallResponse } from './protocol/generated/v2/DynamicToolCallResponse.js';
83
>
import type { DynamicToolCallOutputContentItem } from './protocol/generated/v2/DynamicToolCallOutputContentItem.js';
84
>
import type { ToolRequestUserInputParams } from './protocol/generated/v2/ToolRequestUserInputParams.js';
85
>
import type { ToolRequestUserInputQuestion } from './protocol/generated/v2/ToolRequestUserInputQuestion.js';
86
>
import type { ToolRequestUserInputResponse } from './protocol/generated/v2/ToolRequestUserInputResponse.js';
87
>
import type { JsonValue } from './protocol/generated/serde_json/JsonValue.js';
88
>
import type { GetAccountResponse } from './protocol/generated/v2/GetAccountResponse.js';
89
>
import type { ModelListResponse } from './protocol/generated/v2/ModelListResponse.js';
90
>
import type { Thread } from './protocol/generated/v2/Thread.js';
91
>
import type { ThreadListResponse } from './protocol/generated/v2/ThreadListResponse.js';
92
>
import type { ThreadReadResponse } from './protocol/generated/v2/ThreadReadResponse.js';
93
>
import type { ThreadForkResponse } from './protocol/generated/v2/ThreadForkResponse.js';
94
>
import type { TurnCompletedNotification } from './protocol/generated/v2/TurnCompletedNotification.js';
95
>
import type { TurnStartedNotification } from './protocol/generated/v2/TurnStartedNotification.js';
96
>
import type { ItemStartedNotification } from './protocol/generated/v2/ItemStartedNotification.js';
97
>
import type { ItemCompletedNotification } from './protocol/generated/v2/ItemCompletedNotification.js';
98
>
import type { TurnStartParams } from './protocol/generated/v2/TurnStartParams.js';
99
>
import type { UserInput } from './protocol/generated/v2/UserInput.js';
100
>
import type { ListMcpServerStatusResponse } from './protocol/generated/v2/ListMcpServerStatusResponse.js';
101
>
import type { McpServerToolCallResponse } from './protocol/generated/v2/McpServerToolCallResponse.js';
102
>
import type { McpResourceReadResponse } from './protocol/generated/v2/McpResourceReadResponse.js';
103
>
import type { McpServerStartupState } from './protocol/generated/v2/McpServerStartupState.js';
104
>
import type { McpServerElicitationRequestParams } from './protocol/generated/v2/McpServerElicitationRequestParams.js';
105
>
import type { McpServerElicitationRequestResponse } from './protocol/generated/v2/McpServerElicitationRequestResponse.js';
106
>
import type { SkillsListResponse } from './protocol/generated/v2/SkillsListResponse.js';
107
>
import type { HooksListResponse } from './protocol/generated/v2/HooksListResponse.js';
108
>
import type { ItemGuardianApprovalReviewCompletedNotification } from './protocol/generated/v2/ItemGuardianApprovalReviewCompletedNotification.js';
109
>
import type { GuardianWarningNotification } from './protocol/generated/v2/GuardianWarningNotification.js';
110
>
import type { ThreadApproveGuardianDeniedActionResponse } from './protocol/generated/v2/ThreadApproveGuardianDeniedActionResponse.js';
111
>
import type { ConfigReadResponse } from './protocol/generated/v2/ConfigReadResponse.js';
112
>
import type { ConfigWriteResponse } from './protocol/generated/v2/ConfigWriteResponse.js';
113
>
import { formatGuardianDenialNotification, summarizeGuardianReviewAction, toGuardianAssessmentEventJson } from './codexGuardianReview.js';
114
>
115
>
const CLIENT_INFO = {
116
>
name: 'vscode_agent_host',
117
>
title: 'VS Code Agent Host',
118
>
// The codex `clientInfo.version` is informational. Hardcoded to a
119
>
// non-empty placeholder; bumping it isn't required when our code
120
>
// changes.
121
>
version: '0.1.0',
122
>
};
123
>
124
>
const CODEX_THINKING_LEVEL_KEY = 'thinkingLevel';
125
>
126
>
/**
127
>
* User-agent prefix applied to the Codex agent's outbound CAPI calls (e.g. the
128
>
* model-list fetch) so the traffic is identifiable server-side. Mirrors
129
>
* `claudeAgent.ts` and the `vscode_codex` prefix used by `codexProxyService.ts`
130
>
* and `oaiLanguageModelServer.ts`.
131
>
*/
132
>
const USER_AGENT_PREFIX = 'vscode_codex';
133
>
134
>
const CODEX_REASONING_EFFORTS: readonly ReasoningEffort[] = ['minimal', 'low', 'medium', 'high'];
135
>
136
>
/**
137
>
* MCP App capabilities advertised on every codex MCP server. Mirrors
138
>
* {@link DEFAULT_MCP_APP_CAPABILITIES} but omits `sampling`: codex owns
139
>
* the model connection (through the `vscode-proxy` provider) and exposes
140
>
* no app-server RPC for App-initiated `sampling/createMessage`, so the
141
>
* host cannot serve that capability for codex.
142
>
*/
143
>
const CODEX_MCP_APP_CAPABILITIES: AhpMcpUiHostCapabilities = {
144
>
serverTools: { listChanged: true },
145
>
serverResources: {},
146
>
};
147
>
148
>
/**
149
>
* Codex surfaces an MCP tool-call approval as a `request_user_input`
150
>
* question whose id is `mcp_tool_call_approval_<callId>` (the `<callId>`
151
>
* matches the `mcpToolCall` item id). The host intercepts these and renders
152
>
* them on the normal tool-approval card instead of a chat-input question;
153
>
* see {@link CodexAgent._handleMcpToolApprovalViaCard}.
154
>
*
155
>
* Codex decodes the answer string back into a decision: `Allow` accepts the
156
>
* call, the synthetic `__codex_mcp_decline__` rejects it (anything else is
157
>
* treated as a cancel). These mirror the constants in codex
158
>
* `core/src/mcp_tool_call.rs`.
159
>
*/
160
>
const MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX = 'mcp_tool_call_approval_';
161
>
const MCP_TOOL_APPROVAL_ANSWER_ALLOW = 'Allow';
162
>
const MCP_TOOL_APPROVAL_ANSWER_DECLINE = '__codex_mcp_decline__';
163
>
164
>
/**
165
>
* `supported_endpoints` value (on a Copilot CAPI {@link CCAModel}) that marks
166
>
* a model as reachable through CAPI's OpenAI-shaped Responses endpoint. Codex
167
>
* only drives models via this endpoint (the `vscode-proxy` provider uses
168
>
* `wire_api="responses"`), so the model picker is filtered to models that
169
>
* advertise it. Confirmed against the live CAPI catalog: gpt-5.x / gpt-5*-codex
170
>
* / mai-code carry `/responses`; Anthropic models carry `/v1/messages` and
171
>
* chat-only models carry `/chat/completions` (neither is usable by codex).
172
>
*/
173
>
const CODEX_RESPONSES_ENDPOINT = '/responses';
174
>
175
>
/**
176
>
* Codex's Agent Mode schema, derived from the platform-generic Mode schema but
177
>
* with "Autopilot" removed. Codex has only two native collaboration modes —
178
>
* `plan` and `default` (see {@link ModeKind}) — so "Autopilot" would map to
179
>
* `default`, identical to "Interactive", and offering it in the picker would be
180
>
* a no-op duplicate. Labels and descriptions are sliced by index so they stay
181
>
* in sync with the platform schema.
182
>
*/
183
>
function createCodexModeSchema(): ISchemaProperty<SessionMode> {
184
>
const base = platformSessionSchema.definition[SessionConfigKey.Mode].protocol;
185
>
const kept = (base.enum ?? []).flatMap((value, index) => value === 'autopilot' ? [] : [index]);
186
>
return schemaProperty<SessionMode>({
187
>
...base,
188
>
enum: kept.map(index => base.enum![index]),
189
>
enumLabels: base.enumLabels && kept.map(index => base.enumLabels![index]),
190
>
enumDescriptions: base.enumDescriptions && kept.map(index => base.enumDescriptions![index]),
191
>
});
192
>
}
193
>
194
>
const codexSessionConfigSchema = createSchema({
195
>
[CodexSessionConfigKey.PermissionsPreset]: schemaProperty<CodexPermissionsPreset>({
196
>
type: 'string',
197
>
title: localize('codex.sessionConfig.permissionsPreset', "Approvals"),
198
>
description: localize('codex.sessionConfig.permissionsPresetDescription', "How much Codex can do on its own before asking for approval."),
199
>
enum: [...CODEX_PERMISSIONS_PRESETS],
200
>
enumLabels: [
201
>
localize('codex.sessionConfig.permissionsPreset.default', "Default Permissions"),
202
>
localize('codex.sessionConfig.permissionsPreset.autoReview', "Auto-Review"),
203
>
localize('codex.sessionConfig.permissionsPreset.fullAccess', "Full Access"),
204
>
],
205
>
enumDescriptions: [
206
>
localize('codex.sessionConfig.permissionsPreset.defaultDescription', "Codex can read and edit files in the workspace and run routine local commands. It asks before using the internet or going beyond the workspace."),
207
>
localize('codex.sessionConfig.permissionsPreset.autoReviewDescription', "Same workspace access as Default, but approval requests are routed through the auto-reviewer instead of prompting you."),
208
>
localize('codex.sessionConfig.permissionsPreset.fullAccessDescription', "Codex can edit files outside the workspace and use the internet without asking. Use only when you want full machine access."),
209
>
],
210
>
default: CODEX_DEFAULT_PERMISSIONS_PRESET,
211
>
sessionMutable: true,
212
>
}),
213
>
[CodexSessionConfigKey.ApprovalPolicy]: schemaProperty<CodexApprovalPolicy>({
214
>
type: 'string',
215
>
title: localize('codex.sessionConfig.approvalPolicy', "Approvals"),
216
>
description: localize('codex.sessionConfig.approvalPolicyDescription', "How Codex requests approval for tool calls."),
217
>
enum: ['never', 'on-request', 'on-failure', 'untrusted'],
218
>
enumLabels: [
219
>
localize('codex.sessionConfig.approvalPolicy.never', "No Escalations"),
220
>
localize('codex.sessionConfig.approvalPolicy.onRequest', "Ask When Needed"),
221
>
localize('codex.sessionConfig.approvalPolicy.onFailure', "Ask on Failure"),
222
>
localize('codex.sessionConfig.approvalPolicy.untrusted', "Ask More Often"),
223
>
],
224
>
enumDescriptions: [
225
>
localize('codex.sessionConfig.approvalPolicy.neverDescription', "Never ask for elevated permission; commands that cannot run in the sandbox are rejected."),
226
>
localize('codex.sessionConfig.approvalPolicy.onRequestDescription', "Ask only when Codex determines a command needs elevated permission."),
227
>
localize('codex.sessionConfig.approvalPolicy.onFailureDescription', "Try commands in the sandbox first, then ask to retry with elevated permission if the sandbox blocks them."),
228
>
localize('codex.sessionConfig.approvalPolicy.untrustedDescription', "Ask before more command categories so you can review actions more closely."),
229
>
],
230
>
default: 'on-request',
231
>
sessionMutable: true,
232
>
}),
233
>
[CodexSessionConfigKey.SandboxMode]: schemaProperty<SandboxMode>({
234
>
type: 'string',
235
>
title: localize('codex.sessionConfig.sandboxMode', "Sandbox"),
236
>
description: localize('codex.sessionConfig.sandboxModeDescription', "Filesystem and network restrictions applied to tool calls."),
237
>
enum: ['read-only', 'workspace-write', 'danger-full-access'],
238
>
enumLabels: [
239
>
localize('codex.sessionConfig.sandboxMode.readOnly', "Read-Only"),
240
>
localize('codex.sessionConfig.sandboxMode.workspaceWrite', "Workspace Write"),
241
>
localize('codex.sessionConfig.sandboxMode.dangerFullAccess', "Full Access (Dangerous)"),
242
>
],
243
>
enumDescriptions: [
244
>
localize('codex.sessionConfig.sandboxMode.readOnlyDescription', "Tool calls can read the workspace but cannot modify files."),
245
>
localize('codex.sessionConfig.sandboxMode.workspaceWriteDescription', "Tool calls can read and write within the workspace; network is controlled separately."),
246
>
localize('codex.sessionConfig.sandboxMode.dangerFullAccessDescription', "Tool calls have unrestricted disk and network access."),
247
>
],
248
>
default: 'workspace-write',
249
>
sessionMutable: true,
250
>
}),
251
>
[CodexSessionConfigKey.WebSearchMode]: schemaProperty<WebSearchMode>({
252
>
type: 'string',
253
>
title: localize('codex.sessionConfig.webSearchMode', "Web Search"),
254
>
description: localize('codex.sessionConfig.webSearchModeDescription', "Web-search tool availability for the model."),
255
>
enum: ['disabled', 'cached', 'live'],
256
>
enumLabels: [
257
>
localize('codex.sessionConfig.webSearchMode.disabled', "Disabled"),
258
>
localize('codex.sessionConfig.webSearchMode.cached', "Cached Only"),
259
>
localize('codex.sessionConfig.webSearchMode.live', "Live"),
260
>
],
261
>
default: 'disabled',
262
>
sessionMutable: false,
263
>
}),
264
>
[CodexSessionConfigKey.ModelReasoningEffort]: schemaProperty<ReasoningEffort>({
265
>
type: 'string',
266
>
title: localize('codex.sessionConfig.modelReasoningEffort', "Reasoning Effort"),
267
>
description: localize('codex.sessionConfig.modelReasoningEffortDescription', "Controls how much reasoning effort Codex uses."),
268
>
enum: [...CODEX_REASONING_EFFORTS],
269
>
enumLabels: CODEX_REASONING_EFFORTS.map(getReasoningEffortLabel),
270
>
enumDescriptions: CODEX_REASONING_EFFORTS.map(effort => getReasoningEffortDescription(effort) ?? ''),
271
>
default: 'medium',
272
>
sessionMutable: true,
273
>
}),
274
>
[SessionConfigKey.Mode]: createCodexModeSchema(),
275
>
[CodexSessionConfigKey.Personality]: schemaProperty<Personality>({
276
>
type: 'string',
277
>
title: localize('codex.sessionConfig.personality', "Personality"),
278
>
description: localize('codex.sessionConfig.personalityDescription', "Tone Codex uses when communicating."),
279
>
enum: ['none', 'friendly', 'pragmatic'],
280
>
enumLabels: [
281
>
localize('codex.sessionConfig.personality.none', "Default"),
282
>
localize('codex.sessionConfig.personality.friendly', "Friendly"),
283
>
localize('codex.sessionConfig.personality.pragmatic', "Pragmatic"),
284
>
],
285
>
enumDescriptions: [
286
>
localize('codex.sessionConfig.personality.noneDescription', "Use Codex's built-in default tone."),
287
>
localize('codex.sessionConfig.personality.friendlyDescription', "Warmer, more conversational tone."),
288
>
localize('codex.sessionConfig.personality.pragmaticDescription', "Terse, no-nonsense tone focused on actions."),
289
>
],
290
>
default: 'none',
291
>
sessionMutable: true,
292
>
}),
293
>
[CodexSessionConfigKey.ReasoningSummary]: schemaProperty<ReasoningSummary>({
294
>
type: 'string',
295
>
title: localize('codex.sessionConfig.reasoningSummary', "Reasoning Summary"),
296
>
description: localize('codex.sessionConfig.reasoningSummaryDescription', "How Codex summarizes its reasoning in the response stream."),
297
>
enum: ['auto', 'concise', 'detailed', 'none'],
298
>
enumLabels: [
299
>
localize('codex.sessionConfig.reasoningSummary.auto', "Auto"),
300
>
localize('codex.sessionConfig.reasoningSummary.concise', "Concise"),
301
>
localize('codex.sessionConfig.reasoningSummary.detailed', "Detailed"),
302
>
localize('codex.sessionConfig.reasoningSummary.none', "None"),
303
>
],
304
>
default: 'auto',
305
>
sessionMutable: true,
306
>
}),
307
>
[CodexSessionConfigKey.AdditionalDirectories]: schemaProperty<string[]>({
308
>
type: 'array',
309
>
title: localize('codex.sessionConfig.additionalDirectories', "Additional Writable Directories"),
310
>
description: localize('codex.sessionConfig.additionalDirectoriesDescription', "Absolute paths the sandbox is allowed to write to, in addition to the workspace. Only applies when Sandbox is Workspace Write."),
311
>
items: { type: 'string', title: localize('codex.sessionConfig.additionalDirectories.item', "Directory") },
312
>
enumDynamic: true,
313
>
default: [],
314
>
sessionMutable: true,
315
>
}),
316
>
[CodexSessionConfigKey.NetworkAccessEnabled]: schemaProperty<boolean>({
317
>
type: 'boolean',
318
>
title: localize('codex.sessionConfig.networkAccessEnabled', "Network"),
319
>
description: localize('codex.sessionConfig.networkAccessEnabledDescription', "Allow sandboxed tool calls to make outbound network requests. Only applies when Sandbox is Workspace Write."),
320
>
default: false,
321
>
sessionMutable: true,
322
>
}),
323
>
[SessionConfigKey.Permissions]: platformSessionSchema.definition[SessionConfigKey.Permissions],
324
>
});
325
>
326
>
const codexVisibleSessionConfigSchema = createSchema({
327
>
[SessionConfigKey.Mode]: codexSessionConfigSchema.definition[SessionConfigKey.Mode],
328
>
[CodexSessionConfigKey.PermissionsPreset]: codexSessionConfigSchema.definition[CodexSessionConfigKey.PermissionsPreset],
329
>
[SessionConfigKey.Permissions]: platformSessionSchema.definition[SessionConfigKey.Permissions],
330
>
});
331
>
332
>
interface ICodexSessionConfigDefaults {
333
>
readonly [CodexSessionConfigKey.PermissionsPreset]: CodexPermissionsPreset;
334
>
readonly [CodexSessionConfigKey.ApprovalPolicy]: CodexApprovalPolicy;
335
>
readonly [CodexSessionConfigKey.SandboxMode]: SandboxMode;
336
>
readonly [CodexSessionConfigKey.WebSearchMode]: WebSearchMode;
337
>
readonly [CodexSessionConfigKey.ModelReasoningEffort]: ReasoningEffort;
338
>
readonly [CodexSessionConfigKey.AdditionalDirectories]: string[];
339
>
readonly [CodexSessionConfigKey.NetworkAccessEnabled]: boolean;
340
>
readonly [SessionConfigKey.Mode]: SessionMode;
341
>
readonly [CodexSessionConfigKey.Personality]: Personality;
342
>
readonly [CodexSessionConfigKey.ReasoningSummary]: ReasoningSummary;
343
>
}
344
>
345
>
const codexSessionConfigDefaults: ICodexSessionConfigDefaults = {
346
>
[CodexSessionConfigKey.PermissionsPreset]: CODEX_DEFAULT_PERMISSIONS_PRESET,
347
>
[CodexSessionConfigKey.ApprovalPolicy]: 'on-request',
348
>
[CodexSessionConfigKey.SandboxMode]: 'workspace-write',
349
>
[CodexSessionConfigKey.WebSearchMode]: 'disabled',
350
>
[CodexSessionConfigKey.ModelReasoningEffort]: 'medium',
351
>
[CodexSessionConfigKey.AdditionalDirectories]: [],
352
>
[CodexSessionConfigKey.NetworkAccessEnabled]: false,
353
>
[SessionConfigKey.Mode]: 'interactive',
354
>
[CodexSessionConfigKey.Personality]: 'none',
355
>
[CodexSessionConfigKey.ReasoningSummary]: 'auto',
356
>
};
357
>
358
>
const CodexPrewarmTtlMs = 60_000;
359
>
360
>
/**
361
>
* Per-session bookkeeping. The codex thread is owned by the shared
362
>
* connection in {@link CodexAgent}; this struct only tracks what the
363
>
* `IAgent` surface needs.
364
>
*/
365
>
/** Resolved user-input answer captured from the client's `chat/inputCompleted`. */
366
>
interface ICodexUserInputResult {
367
>
readonly response: ChatInputResponseKind;
368
>
readonly answers?: Record<string, ChatInputAnswer>;
369
>
}
370
>
371
>
interface ICodexSession {
372
>
/** Caller-facing session id used in the `codex:/<id>` URI; may differ from the codex thread id. */
373
>
readonly sessionId: string;
374
>
/**
375
>
* Codex app-server thread id used in JSON-RPC `thread/*` and `turn/*` calls.
376
>
* Undefined until the session has been materialized (first `sendMessage`
377
>
* triggers `thread/start`). Decoupling materialization from
378
>
* `createSession` mirrors the Claude harness's provisional/materialize
379
>
* split and avoids spawning an orphan codex thread when the workbench
380
>
* rebinds a provisional URI after a chip-selection.
381
>
*/
382
>
threadId: string | undefined;
383
>
readonly sessionUri: URI;
384
>
/**
385
>
* Effective working directory. Starts as the folder the client passed to
386
>
* {@link CodexAgent.createSession}; at first materialization it is replaced
387
>
* with the host-resolved working directory (the isolated worktree for
388
>
* worktree-isolation sessions) before `thread/start` locks the codex
389
>
* subprocess `cwd`. When the client supplies none (e.g. an editor window
390
>
* with no workspace folder open), a managed temp folder is lazily created
391
>
* as a fallback at materialize time (tracked by
392
>
* {@link managedWorkingDirectory} for cleanup). Mutable so both the
393
>
* worktree swap and the lazy assignment can happen after the provisional
394
>
* `createSession`.
395
>
*/
396
>
workingDirectory: URI | undefined;
397
>
/**
398
>
* Set to the temp folder created for this session when no working
399
>
* directory was supplied, so {@link CodexAgent.disposeSession} can remove
400
>
* it. `undefined` when the client supplied a working directory.
401
>
*/
402
>
managedWorkingDirectory: URI | undefined;
403
>
readonly mapState: ICodexSessionMapState;
404
>
/**
405
>
* Phase 4: parked deferreds for `item/commandExecution/requestApproval`,
406
>
* keyed by the host-side toolCallId. Resolved by
407
>
* {@link CodexAgent.respondToPermissionRequest}.
408
>
*/
409
>
readonly pendingCommandApprovals: PendingRequestRegistry<CommandExecutionApprovalDecision>;
410
>
/**
411
>
* Per-session set of "accept for session" decisions. When the user
412
>
* picks Accept-for-Session in a previous approval, subsequent
413
>
* approval requests on the same session resolve automatically.
414
>
*/
415
>
readonly acceptedForSession: Set<string>;
416
>
/**
417
>
* Guardian (auto-review) `reviewId`s that have already been surfaced to
418
>
* the user as a denied-action approval card. Guards against acting twice
419
>
* on the same review if the completed notification is redelivered.
420
>
*/
421
>
readonly handledGuardianReviews: Set<string>;
422
>
/**
423
>
* Host-side toolCallIds of the synthetic "Approve anyway" cards created for
424
>
* guardian (auto-review) denials that are still awaiting a user decision.
425
>
* Unlike codex's blocking command approvals, these cards live inside the
426
>
* active turn but codex does *not* wait on them — so when the turn ends
427
>
* (often via the auto-review circuit-breaker interrupt) the reducer cancels
428
>
* the card. We use this set to unwind the parked deferred on turn end so the
429
>
* suspended {@link CodexAgent._handleGuardianReviewCompleted} frame doesn't
430
>
* leak.
431
>
*/
432
>
readonly pendingGuardianReviewCards: Set<string>;
433
>
/**
434
>
* Steering messages handed to codex via `turn/steer` that are awaiting
435
>
* the matching `userMessage` item echo, which promotes them into their
436
>
* own visible turn. Keyed by {@link PendingMessage.id}. Drained (with a
437
>
* `steering_consumed` signal) on turn completion, abort, dispose, or a
438
>
* `turn/steer` rejection so the chat UI's pending bubble never sticks.
439
>
*/
440
>
readonly pendingSteeringFlips: Map<string, PendingMessage>;
441
>
/**
442
>
* Client-provided tool definitions for this session, keyed by the
443
>
* contributing workbench client. The merged set is registered with codex
444
>
* as `dynamicTools` at `thread/start`. Empty until the first active client
445
>
* sets its tools.
446
>
*/
447
>
readonly clientToolSet: ActiveClientToolSet;
448
>
/**
449
>
* Parked deferreds for in-flight client-tool calls (codex
450
>
* `item/tool/call`), keyed by the host-side toolCallId. Resolved by
451
>
* {@link CodexAgent.onClientToolCallComplete}.
452
>
*/
453
>
readonly pendingClientToolCalls: PendingRequestRegistry<ToolCallResult>;
454
>
/**
455
>
* Parked deferreds for in-flight user-input requests (codex
456
>
* `item/tool/requestUserInput`, i.e. the model's `ask_user`), keyed by a
457
>
* host-generated requestId. Resolved by
458
>
* {@link CodexAgent.respondToUserInputRequest}.
459
>
*/
460
>
readonly pendingUserInputs: PendingRequestRegistry<ICodexUserInputResult>;
461
>
/**
462
>
* Signature of the {@link clientTools} the codex thread was started
463
>
* with. Codex only accepts `dynamicTools` at `thread/start`, so if the
464
>
* tools change before the first turn (e.g. the prewarmed thread started
465
>
* before {@link setClientTools} arrived) the thread is restarted to pick
466
>
* them up. `undefined` until materialized.
467
>
*/
468
>
materializedToolsSig: string | undefined;
469
>
/**
470
>
* Signature of the `mcp_servers` (root config + client plugins) the codex
471
>
* thread was started with. Codex only accepts `config.mcp_servers` at
472
>
* `thread/start`, so if the set changes before the first turn the thread is
473
>
* restarted to pick them up. `undefined` until materialized.
474
>
*/
475
>
materializedMcpSig: string | undefined;
476
>
/** True once a turn has been started on the (materialized) thread. */
477
>
firstTurnSent: boolean;
478
>
model: ModelSelection | undefined;
479
>
/** Workbench-facing turn id for the active turn. */
480
>
currentTurnId: string | undefined;
481
>
/** Local monotonic timer for the active workbench-facing turn. */
482
>
turnStopWatch: StopWatch | undefined;
483
>
/** Codex app-server turn id for the active turn. */
484
>
currentAppTurnId: string | undefined;
485
>
/** Codex app-server turn id -> workbench-facing turn id. */
486
>
readonly hostTurnIdByAppTurnId: Map<string, string>;
487
>
/**
488
>
* Workbench-facing turn id -> codex app-server turn id, retained across
489
>
* turn completion so {@link CodexAgent.truncateSession} can translate a
490
>
* live host turn id to a `thread/rollback` target.
491
>
*/
492
>
readonly codexTurnIdByHostTurnId: Map<string, string>;
493
>
/** Set when this session was restored (Phase 3) and needs `thread/resume` before the first `turn/start`. */
494
>
needsResume: boolean;
495
>
/** Most recent user prompt sent on this session — used as fallback userMessage text in `turn/started`. */
496
>
lastPromptText: string;
497
>
/** True once the workbench has disposed this session. Guards background prewarm continuations. */
498
>
disposed: boolean;
499
>
/** In-flight background or foreground materialization, shared across callers. */
500
>
materializePromise: Promise<void> | undefined;
501
>
/** Whether the workbench-facing materialize event has been emitted. */
502
>
materializedEventFired: boolean;
503
>
/** TTL timer for a materialized-but-unused prewarmed thread. */
504
>
prewarmTimer: ReturnType<typeof setTimeout> | undefined;
505
>
/** True once the prewarmed session has been claimed by a user turn. */
506
>
prewarmClaimed: boolean;
507
>
/** True once the agent host's server tools have been advertised on this session. */
508
>
serverToolsAdvertised: boolean;
509
>
/**
510
>
* Per-session MCP customization surface. Created lazily the first time
511
>
* the session needs to surface codex's MCP servers (either via
512
>
* {@link CodexAgent.getSessionCustomizations} or when the connection's
513
>
* MCP inventory is applied). Disposed when the session is removed.
514
>
*/
515
>
mcpController: McpCustomizationController | undefined;
516
>
/**
517
>
* Store of client-pushed ("Open Plugin") customizations synced to this
518
>
* session. Their MCP servers are attached per-thread at `thread/start`
519
>
* and their skills feed codex's process-global `skills/extraRoots/set`.
520
>
*/
521
>
readonly clientCustomizations: CodexClientCustomizationStore;
522
>
}
523
>
524
>
/**
525
>
* A live Codex collab-agent (subagent) child thread. Codex runs each spawned
526
>
* subagent as its OWN app-server thread that emits a full item/turn event
527
>
* stream (`turn/started`, `item/*`, `turn/completed`) under the child thread
528
>
* id — it is NOT flattened onto the parent thread. We render that stream in a
529
>
* read-only peer chat (the "agent team" pattern, mirroring Copilot/Claude) by
530
>
* routing the child thread's notifications through the shared mappers with an
531
>
* isolated {@link ICodexSession} and firing each resulting action tagged with
532
>
* the parent `spawnAgent` tool call as its `parentToolCallId`, so the shared
533
>
* orchestrator ({@link AgentSideEffects}) lands them in the subagent chat.
534
>
*/
535
>
interface ICodexSubagent {
536
>
/** Caller-facing sessionId of the parent session that spawned this subagent. */
537
>
readonly parentSessionId: string;
538
>
/** Host-side toolCallId of the parent `spawnAgent` collab tool call (routing key). */
539
>
readonly toolCallId: string;
540
>
/**
541
>
* Isolated session used to run the shared event mappers for the child
542
>
* thread. Shares the parent's `sessionUri` and `acceptedForSession` memo so
543
>
* side effects target the parent's working tree and the accept-for-session
544
>
* decision spans parent + subagents, but keeps its own map/turn state.
545
>
*/
546
>
readonly session: ICodexSession;
547
>
}
548
>
549
>
/**
550
>
* Connection state machine. The codex process is spawned lazily on first
551
>
* need (Decision 6) and stays alive for the agent's lifetime.
552
>
*/
553
>
type ConnectionState =
554
>
| { readonly kind: 'idle' }
555
>
| { readonly kind: 'starting'; readonly promise: Promise<IConnectionReady> }
556
>
| ({ readonly kind: 'ready' } & IConnectionReady);
557
>
558
>
interface IConnectionReady {
559
>
readonly client: ICodexAppServerClient;
560
>
readonly usageSource: CodexUsageSource;
561
>
readonly proxyHandle?: ICodexProxyHandle;
562
>
readonly child: ChildProcessWithoutNullStreams;
563
>
}
564
>
565
>
/**
566
>
* `IAgent` implementation backed by `codex app-server`.
567
>
*
568
>
* Phase 2 surface: createSession (blocks on `thread/start`), sendMessage
569
>
* (one `turn/start`, streams `agentMessage` deltas), setPendingMessages
570
>
* (steering via `turn/steer`), abortSession (`turn/interrupt`),
571
>
* disposeSession (`thread/unsubscribe`, no process kill).
572
>
*
573
>
* Decisions 3 (shared process), 6 (lazy spawn), 7 (session id == threadId),
574
>
* 10 (no cwd → reject), 15 (cancel, keep streamed content), 16 (steering),
575
>
* 17 (attachments), 18 (apikey auth).
576
>
*/
577
>
578
>
/**
579
>
* `@openai/codex` distribution descriptor. Lives in this file because it
580
>
* encodes Codex-specific knowledge — the env-var name and the fact that
581
>
* Codex's Linux binaries are statically musl-linked and ship as a single
582
>
* `linux-*` SKU regardless of host libc.
583
>
*/
584
>
export const CodexSdkPackage: IAgentSdkPackage = {
585
>
id: 'codex',
586
>
displayName: 'Codex',
587
>
devOverrideEnvVar: AgentHostCodexAgentSdkRootEnvVar,
588
>
hasSeparateMuslLinuxPackage: false,
589
>
};
590
>
591
>
/**
592
>
* Convert a workbench {@link ToolCallResult} into the codex
593
>
* {@link DynamicToolCallResponse} returned for an `item/tool/call` request.
594
>
* Text content maps to `inputText`; when there is no text content the
595
>
* tool's past-tense summary is used so codex never receives an empty body.
596
>
*/
597
function dynamicToolResponseFromResult(result: ToolCallResult): DynamicToolCallResponse {
598
const contentItems: DynamicToolCallOutputContentItem[] = [];