codexMapAppServerEvents.ts ×32

Frontier kind: Code frontier

unlabeled · c_57421609e467

59 tests · 21550 LOC · 93 files · introduces 0 tests · 247 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
32 ranges247 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1717 ranges21550 lines · 93 files · Browse complete extent
All tests (intent)
59 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 247 introduced LOC across 32 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/codex/codexMapAppServerEvents.ts 247 introduced LOC · 32 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- codexMapAppServerEvents.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 { generateUuid } from '../../../../base/common/uuid.js';
7 > import { toToolCallMeta } from '../../common/meta/agentToolCallMeta.js';
8 > import { ActionType, type SessionAction, type ChatAction } from '../../common/state/sessionActions.js';
9 > import { MessageKind, ResponsePartKind, ToolCallConfirmationReason, ToolCallContributorKind, ToolResultContentType, TurnState } from '../../common/state/sessionState.js';
10 > import { extractForwardedErrorInfo } from '../shared/forwardedChatError.js';
11 > import { getServerToolDisplay } from '../shared/serverToolGroups.js';
12 > import { ActiveClientToolSet } from '../activeClientState.js';
13 > import { unwrapShellInvocation } from './codexShellCommand.js';
14 > import type { AgentMessageDeltaNotification } from './protocol/generated/v2/AgentMessageDeltaNotification.js';
15 > import type { CommandExecutionOutputDeltaNotification } from './protocol/generated/v2/CommandExecutionOutputDeltaNotification.js';
16 > import type { FileChangeOutputDeltaNotification } from './protocol/generated/v2/FileChangeOutputDeltaNotification.js';
17 > import type { FileChangePatchUpdatedNotification } from './protocol/generated/v2/FileChangePatchUpdatedNotification.js';
18 > import type { FileUpdateChange } from './protocol/generated/v2/FileUpdateChange.js';
19 > import type { ItemCompletedNotification } from './protocol/generated/v2/ItemCompletedNotification.js';
20 > import type { ItemStartedNotification } from './protocol/generated/v2/ItemStartedNotification.js';
21 > import type { McpToolCallProgressNotification } from './protocol/generated/v2/McpToolCallProgressNotification.js';
22 > import type { McpToolCallResult } from './protocol/generated/v2/McpToolCallResult.js';
23 > import type { ReasoningSummaryPartAddedNotification } from './protocol/generated/v2/ReasoningSummaryPartAddedNotification.js';
24 > import type { ReasoningSummaryTextDeltaNotification } from './protocol/generated/v2/ReasoningSummaryTextDeltaNotification.js';
25 > import type { ReasoningTextDeltaNotification } from './protocol/generated/v2/ReasoningTextDeltaNotification.js';
26 > import type { ThreadTokenUsageUpdatedNotification } from './protocol/generated/v2/ThreadTokenUsageUpdatedNotification.js';
27 > import type { TurnCompletedNotification } from './protocol/generated/v2/TurnCompletedNotification.js';
28 > import type { TurnStartedNotification } from './protocol/generated/v2/TurnStartedNotification.js';
29 > import type { UserInput } from './protocol/generated/v2/UserInput.js';
30 > import type { WebSearchAction } from './protocol/generated/v2/WebSearchAction.js';
31 > import type { DynamicToolCallOutputContentItem } from './protocol/generated/v2/DynamicToolCallOutputContentItem.js';
32 > import type { JsonValue } from './protocol/generated/serde_json/JsonValue.js';
33 > import type { CollabAgentTool } from './protocol/generated/v2/CollabAgentTool.js';
34 > import type { CollabAgentState } from './protocol/generated/v2/CollabAgentState.js';
35 >
36 > /**
37 > * Per-session mutable state held by the mapper. Carries the bookkeeping
38 > * needed to glue codex's item-stream (each `agentMessage` item has its
39 > * own id) to the agent host protocol (each markdown part has its own id).
40 > *
41 > * Phase 2 tracks only `itemId → partId` for agent messages. Phase 4
42 > * extends this with tool-call correlation; Phase 6 adds reasoning parts.
43 > */
44 > export interface ICodexSessionMapState {
45 > /** Stable codex `itemId` → our markdown response part id. */
46 > readonly itemToPartId: Map<string, string>;
47 > /**
48 > * Stable codex `itemId` → tool-call bookkeeping. Phase 4 tracks
49 > * `commandExecution` here so completion/approval handlers can find
50 > * the right toolCallId/turnId for each item.
51 > */
52 > readonly itemToToolCall: Map<string, ICodexToolCallEntry>;
53 > /** Stable codex reasoning item/index → our reasoning response part id. */
54 > readonly itemToReasoningPartId: Map<string, string>;
55 > /** Current turn id (per `turn/started`). */
56 > currentTurnId: string | undefined;
57 > /**
58 > * Live registry of the session's client-provided (`dynamicTools`) tools,
59 > * keyed by contributing workbench client. A `dynamicToolCall` tool-call
60 > * start is stamped with the owning client (so the workbench routes
61 > * execution back to it) resolved via {@link ActiveClientToolSet.ownerOf}.
62 > */
63 > clientToolSet: ActiveClientToolSet;
64 > /**
65 > * Names of the agent host's server tools (executed in-process). A
66 > * `dynamicToolCall` for one of these omits the `Client` contributor so the
67 > * workbench does not try to route execution to a client — the agent host
68 > * answers the `item/tool/call` directly.
69 > */
70 > serverToolNames: ReadonlySet<string>;
71 > /**
72 > * Server name → customization id for the session's MCP servers, used to
73 > * stamp the {@link ToolCallContributorKind.MCP} contributor on `mcpToolCall`
74 > * starts so clients can correlate the call with its originating server
75 > * customization. Owned and populated by the agent (mirrors
76 > * {@link clientToolSet}); empty until the agent first applies the inventory.
77 > */
78 > readonly mcpCustomizationIds: Map<string, string>;
79 > /**
80 > * Tool call ids the host declined at the approval prompt. Codex reports the
81 > * resulting `item/completed` as a generic failure, so the completion handler
82 > * consults this set to emit a `userCancelled` (`error.code = 'denied'`)
83 > * result instead. Drained on completion and cleared per turn.
84 > */
85 > readonly declinedToolCalls: Set<string>;
86 > /**
87 > * A `commandExecution` that completed successfully with NO output is
88 > * potentially a sandbox pre-flight. When Codex runs a network (or otherwise
89 > * escalated) command under `on-request` + `workspace-write` it first attempts
90 > * it inside the sandbox — which completes instantly with no output because
91 > * the sandbox blocked it — then re-runs the SAME command as a separate
92 > * `commandExecution` item guarded by an approval request. Rendering both
93 > * items draws the command box twice. To coalesce them we defer the
94 > * pre-flight's completion here: if the next `commandExecution` in the turn
95 > * re-runs the same command it reuses this (still-open) tool call for a single
96 > * box; otherwise the deferred completion is flushed (on the next item or at
97 > * turn end) so a genuinely output-less command still finalizes.
98 > */
99 > pendingPreflight: ICodexPendingPreflight | undefined;
100 > }
101 >
102 > /**
103 > * A deferred `commandExecution` completion held back to coalesce a sandbox
104 > * pre-flight with its approval-guarded re-run. See
105 > * {@link ICodexSessionMapState.pendingPreflight}.
106 > */
107 > interface ICodexPendingPreflight {
108 > readonly toolCallId: string;
109 > readonly turnId: string;
110 > /** Unwrapped command text, used to match the re-run. */
111 > readonly command: string;
112 > /** The `ChatToolCallComplete` action to emit if the pre-flight is not reused. */
113 > readonly completion: (SessionAction | ChatAction)[];
114 > }
115 >
116 > export interface ICodexToolCallEntry {
117 > readonly toolCallId: string;
118 > readonly turnId: string;
119 > readonly toolName: string;
120 > output: string;
121 > }
122 >
123 > export function createCodexSessionMapState(serverToolNames: ReadonlySet<string> = new Set(), clientToolSet: ActiveClientToolSet = new ActiveClientToolSet()): ICodexSessionMapState {
124 return {
125 itemToPartId: new Map(),
134 };
135 }
137 > /**
138 > * Clear the per-turn bookkeeping maps so streamed parts, tool-calls, and
139 > * reasoning parts from a finished (or preempted) turn don't bleed into the
140 > * next one. Does NOT touch {@link ICodexSessionMapState.currentTurnId},
141 > * which tracks the codex app-server turn id and is owned by the
142 > * turn/started + turn/completed handlers.
143 > */
144 > export function resetCodexTurnMapState(state: ICodexSessionMapState): void {
145 state.itemToPartId.clear();
146 state.itemToToolCall.clear();
149 state.pendingPreflight = undefined;
150 }
152 > /**
153 > * Emit and clear any deferred sandbox pre-flight completion (see
154 > * {@link ICodexSessionMapState.pendingPreflight}). Returns `[]` when nothing is
155 > * pending, so callers can unconditionally prepend the result.
156 > */
157 function flushPendingPreflight(state: ICodexSessionMapState): (SessionAction | ChatAction)[] {
158 const pending = state.pendingPreflight;
163 return pending.completion;
164 }
166 > /**
167 > * Collect the plain-text portions of a codex `userMessage` item's
168 > * `content` (an array of {@link UserInput}). Non-text inputs (images,
169 > * skills, mentions) are ignored. Multiple text parts are joined with a
170 > * blank line, mirroring {@link mapTurnStarted}'s reconstruction.
171 > */
172 > export function extractUserInputText(content: readonly UserInput[]): string {
173 const collected: string[] = [];
174 for (const c of content) {
179 return collected.join('\n\n');
180 }
182 function reasoningKey(itemId: string, kind: 'summary' | 'text', index: number): string {
183 return `${itemId}:${kind}:${index}`;
184 }
186 function ensureReasoningPart(state: ICodexSessionMapState, turnId: string, key: string): { readonly partId: string; readonly actions: (SessionAction | ChatAction)[] } {
187 const existing = state.itemToReasoningPartId.get(key);
200 };
201 }
203 > export function describeWebSearch(query: string, action: WebSearchAction | null): string {
204 if (action?.type === 'search') {
205 return action.queries?.join(', ') ?? action.query ?? query;
213 return query;
214 }
216 > export function describeFileChange(changes: readonly FileUpdateChange[]): string {
217 return changes.map(change => {
218 const kind = change.kind.type === 'update' && change.kind.move_path
222 }).join('\n');
223 }
225 > export function fileChangeOutput(changes: readonly FileUpdateChange[]): string {
226 return changes.map(change => `${describeFileChange([change])}\n${change.diff}`.trim()).join('\n\n');
227 }
229 function jsonValueToText(value: JsonValue): string {
230 return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
231 }
233 function toolInputText(value: JsonValue): string {
234 return JSON.stringify(value, null, 2);
235 }
237 function dynamicToolOutput(contentItems: readonly DynamicToolCallOutputContentItem[] | null): string {
238 return contentItems?.map(item => item.type === 'inputText' ? item.text : item.imageUrl).join('\n') ?? '';
239 }
241 function mcpToolOutput(result: McpToolCallResult | null, errorMessage?: string): string {
242 if (errorMessage) {
250 return [content, structuredContent].filter(Boolean).join('\n');
251 }
253 > /**
254 > * Human labels for a Codex collab-agent (subagent) tool call, mirroring the
255 > * reference client's phrasing. Codex surfaces subagent orchestration as
256 > * `collabAgentToolCall` items on the parent thread, but each spawned agent
257 > * ALSO runs as its own child thread that emits a full `turn/*` + `item/*`
258 > * event stream. The host ({@link CodexAgent}) renders that child stream in a
259 > * read-only peer chat and attaches a discovery block to the parent
260 > * `spawnAgent` tool call; the lifecycle collab tools (`wait`, `closeAgent`,
261 > * `sendInput`, …) render as plain tool calls in the parent chat.
262 > */
263 function collabAgentToolLabels(tool: CollabAgentTool): { readonly displayName: string; readonly present: string; readonly past: string } {
264 switch (tool) {
271 }
272 }
274 > /** One-line summary of a spawned agent's state — the subagent's result. */
275 function collabAgentStateSummary(state: CollabAgentState): string {
276 switch (state.status) {
285 }
286 }
288 > /**
289 > * Render the per-agent result block for a completed collab tool call. Prefers
290 > * the receiver order, then appends any other agents present in `agentsStates`.
291 > * The completed message carries the subagent's actual output.
292 > */
293 function collabAgentResultOutput(receiverThreadIds: readonly string[], agentsStates: { readonly [key: string]: CollabAgentState | undefined }): string {
294 const seen = new Set<string>();
318 return states.map((state, index) => `Agent ${index + 1}: ${collabAgentStateSummary(state)}`).join('\n');
319 }
321 > /**
322 > * Translate `turn/started` into a `ChatTurnStarted` action.
323 > *
324 > * Codex's `turn/started.turn.items[0]` SHOULD be the userMessage that
325 > * kicked off the turn; we reconstruct the user message from it. If
326 > * codex didn't include items (it may not), we synthesize an empty user
327 > * message so the agent host can still create the turn shell — the actual
328 > * prompt text was sent via `turn/start` and is already known by the host
329 > * via the prior `sendMessage` call.
330 > */
331 > export function mapTurnStarted(
332 state: ICodexSessionMapState,
333 params: TurnStartedNotification,
353 ];
354 }
356 > export function mapReasoningSummaryPartAdded(
357 state: ICodexSessionMapState,
358 params: ReasoningSummaryPartAddedNotification,
360 return ensureReasoningPart(state, params.turnId, reasoningKey(params.itemId, 'summary', params.summaryIndex)).actions;
361 }
363 > export function mapReasoningSummaryTextDelta(
364 state: ICodexSessionMapState,
365 params: ReasoningSummaryTextDeltaNotification,
371 ];
372 }
374 > export function mapReasoningTextDelta(
375 state: ICodexSessionMapState,
376 params: ReasoningTextDeltaNotification,
382 ];
383 }
385 > export function clearReasoningForItem(state: ICodexSessionMapState, itemId: string): void {
386 for (const key of [...state.itemToReasoningPartId.keys()]) {
387 if (key.startsWith(`${itemId}:`)) {
390 }
391 }
393 > export function mapTokenUsageUpdated(params: ThreadTokenUsageUpdatedNotification): (SessionAction | ChatAction)[] {
394 const last = params.tokenUsage.last;
395 return [{
407 }];
408 }
410 > /**
411 > * `item/started` for an `agentMessage` becomes a `ChatResponsePart`
412 > * action with an empty `MarkdownResponsePart` shell. Subsequent
413 > * `item/agentMessage/delta` notifications append to that part.
414 > *
415 > * Other item types are ignored in Phase 2 — they'll be picked up by
416 > * Phase 6's tool-call mapper.
417 > */
418 > export function mapItemStarted(
419 state: ICodexSessionMapState,
420 params: ItemStartedNotification,
445 return flushed.length === 0 ? body : [...flushed, ...body];
446 }
448 function mapItemStartedBody(
449 state: ICodexSessionMapState,
731 return [];
732 }
734 > export function mapCommandExecutionOutputDelta(
735 state: ICodexSessionMapState,
736 params: CommandExecutionOutputDeltaNotification,
748 }];
749 }
751 > export function mapFileChangePatchUpdated(
752 state: ICodexSessionMapState,
753 params: FileChangePatchUpdatedNotification,
765 }];
766 }
768 > export function mapFileChangeOutputDelta(
769 state: ICodexSessionMapState,
770 params: FileChangeOutputDeltaNotification,
782 }];
783 }
785 > export function mapMcpToolCallProgress(
786 state: ICodexSessionMapState,
787 params: McpToolCallProgressNotification,
799 }];
800 }
802 > export function mapAgentMessageDelta(
803 state: ICodexSessionMapState,
804 params: AgentMessageDeltaNotification,
820 ];
821 }
823 > /**
824 > * `item/completed` for an `agentMessage` — the part is finalized server
825 > * side. For Phase 2 we don't need to emit an extra action: the deltas
826 > * already updated the part's content. We just drop the mapping so the
827 > * memory pressure stays bounded.
828 > *
829 > * For `commandExecution`, emit a synthetic `ChatToolCallReady`
830 > * (auto-confirmed; the codex server already decided to run the command
831 > * — any host-side approval was settled via the `requestApproval`
832 > * server-request handler before we got here) followed by a
833 > * `ChatToolCallComplete` carrying the aggregated output.
834 > */
835 > export function mapItemCompleted(
836 state: ICodexSessionMapState,
837 params: ItemCompletedNotification,
976 return [];
977 }
979 > /**
980 > * `turn/completed` translates to either a normal complete signal or, when
981 > * the turn ended with `status: 'failed'`, an error followed by the
982 > * complete signal so consumers can react to both.
983 > */
984 > export function mapTurnCompleted(
985 state: ICodexSessionMapState,
986 params: TurnCompletedNotification,
1042 return [...preflightFlush, ...orphanedToolCallActions, { type: ActionType.ChatTurnComplete, turnId, duration }];
1043 }
1045 > /**
1046 > * Build a {@link TurnState} from a codex `Turn.status`. Mostly useful
1047 > * for replay (Phase 3).
1048 > */
1049 > export function turnStateFromStatus(status: string): TurnState {
1050 switch (status) {
1051 case 'completed':