src/vs/platform/agentHost/node/copilot/copilotNonPtyShellTerminals.ts
196 LOC · 188 covered · 8 uncovered · 45 ranges · 980 concepts · 21 introducers · 462 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
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 related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
mapSessionEvents.ts ×12
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { AgentSession } from '../../common/agentService.js';
import { TerminalClaimKind, type TerminalCommandResult, type TerminalSessionClaim } from '../../common/state/protocol/state.js';
import { IAgentHostTerminalManager } from '../agentHostTerminalManager.js';
/**
* Builds the terminal channel URI for a runtime-executed (non-pty) shell tool
* call. The session owns the terminal namespace and each tool call addresses a
* distinct child terminal, keeping the URI stable across live streaming and
* history replay without colliding with other sessions or tool calls.
*/
export function buildNonPtyShellTerminalUri(session: URI | string, toolCallId: string): string {
return `agenthost-terminal://shell/${encodeURIComponent(AgentSession.id(session))}/${encodeURIComponent(toolCallId)}`;
copilotNonPtyShellTerminals.ts ×1
}
interface INonPtyShellStream {
readonly uri: string;
readonly title: string;
created: boolean;
/** The last cumulative snapshot written to the channel. */
lastEmitted: string;
finalized: boolean;
}
/**
* Extracts the command result from the runtime's stable text fallback. The
* external SDK bridge currently removes the equivalent `shell_exit` content
* block for compatibility with older SDK clients.
*/
function parseCompletedShell(text: string | undefined): TerminalCommandResult | undefined {
copilotNonPtyShellTerminals.ts ×1
const match = text && /<shellId: ([^>\r\n]+) completed with exit code (-?\d+)>\s*$/.exec(text);
if (!match) {
}
exitCode: Number(match[2]),
preview: text.slice(0, match.index),
};
}
export interface INonPtyShellToolCompletion {
readonly uri: string;
readonly result?: TerminalCommandResult;
readonly shouldRetire: boolean;
}
/**
* Streams output of SDK-runtime-executed shell tool calls into output-only
* AHP terminal channels. The runtime reports ANSI-stripped plain-text output
* via `tool.execution_partial_result` as throttled cumulative snapshots that
* may be rewritten once output is truncated (a trailing truncation marker
* under the emit cap, a rolling tail past the large-output threshold); this
* class emits only the unseen suffix as `terminal/data` while the snapshot
* grows in place, and resets the channel when the snapshot was rewritten, so
* subscribed clients receive live plain-text output (`isPty: false` — no VT
* parsing needed).
*
* Created once per session and disposed with it, matching the pty-backed
* `ShellManager` lifecycle.
*/
export class NonPtyShellTerminalStreams extends Disposable {
private readonly _streams = new Map<string, INonPtyShellStream>();
constructor(
@IAgentHostTerminalManager private readonly _terminalManager: IAgentHostTerminalManager,
) {
super();
this._register(toDisposable(() => {
for (const stream of this._streams.values()) {
}
}));
}
/**
* Appends the unseen suffix of `cumulativeOutput` to the tool call's
* output terminal, creating the channel on first call. Returns the channel
* URI and whether this call created it (so the caller can attach the
* terminal content block exactly once).
*/
track(toolCallId: string, title: string): void {
this._streams.set(toolCallId, {
uri: buildNonPtyShellTerminalUri(this._sessionUri, toolCallId),
title,
lastEmitted: '',
finalized: false,
created: false,
});
}
}
append(toolCallId: string, cumulativeOutput: string): { uri: string; created: boolean } | undefined {
if (!stream) {
return undefined;
}
if (created) {
}
if (stream.finalized || cumulativeOutput === stream.lastEmitted) {
copilotNonPtyShellTerminals.ts ×4
}
this._terminalManager.appendOutputTerminalData(stream.uri, cumulativeOutput.slice(stream.lastEmitted.length));
} else {
// The snapshot no longer extends what we emitted — the runtime
copilotNonPtyShellTerminals.ts ×1
// rewrote it after truncation (marker under the emit cap, rolling
// tail past the large-output threshold). Start the channel over.
this._terminalManager.resetOutputTerminal(stream.uri);
this._terminalManager.appendOutputTerminalData(stream.uri, cumulativeOutput);
}
return { uri: stream.uri, created };
/**
* Records the process lifecycle information carried by tool completion.
* A structured shell exit settles the channel.
*/
completeToolCall(toolCallId: string, toolOutput: string | undefined, shellExit: { shellId: string; result: TerminalCommandResult } | undefined): INonPtyShellToolCompletion | undefined {
if (!stream) {
return undefined;
}
const result = shellExit?.result ?? parseCompletedShell(toolOutput);
if (!result) {
return undefined;
}
}
}
}
this._finalize(stream, result.exitCode);
}
return {
uri: stream.uri,
result,
shouldRetire: stream.finalized && result.preview !== undefined,
}
/**
* Releases the live output resource after its static completion has been
* published. Repeated calls are safe and do not dispose the resource twice.
*/
retire(toolCallId: string): void {
if (!stream) {
return;
}
if (stream.created) {
this._terminalManager.disposeTerminal(stream.uri);
}
}
private _finalize(stream: INonPtyShellStream, exitCode: number): void {
return;
}
this._terminalManager.finalizeOutputTerminal(stream.uri, exitCode);
}
private _createTerminal(toolCallId: string, stream: INonPtyShellStream): void {
kind: TerminalClaimKind.Session,
session: this._sessionUri.toString(),
toolCallId,
};
this._terminalManager.createOutputTerminal(stream.uri, { title: stream.title, claim });
stream.created = true;
}