src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts
829 LOC · 737 covered · 92 uncovered · 178 ranges · 980 concepts · 52 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 type { AssistantMessageToolRequest, Attachment, SessionEvent, ToolExecutionCompleteContent, ToolExecutionCompleteData } from '@github/copilot-sdk';
import { decodeBase64 } from '../../../../base/common/buffer.js';
import { basename } from '../../../../base/common/path.js';
import { isString } from '../../../../base/common/types.js';
import { URI } from '../../../../base/common/uri.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { AgentSession } from '../../common/agentService.js';
import { stripRedundantCdPrefix } from '../../common/commandLineHelpers.js';
import { toToolCallMeta, type IToolCallUiMeta } from '../../common/meta/agentToolCallMeta.js';
import { IFileEditRecord, ISessionDatabase } from '../../common/sessionDataService.js';
import { MessageAttachmentKind, type MessageAttachment } from '../../common/state/protocol/state.js';
import { MessageKind, ResponsePartKind, ToolCallConfirmationReason, ToolCallContributorKind, ToolCallStatus, ToolResultContentType, TurnState, buildSubagentSessionUri, type AgentSelection, type Message, type ModelSelection, type ResponsePart, type StringOrMarkdown, type TerminalCommandResult, type ToolCallCompletedState, type ToolResultContent, type ToolResultTerminalContent, type Turn, type UsageInfo } from '../../common/state/sessionState.js';
import { buildNonPtyShellTerminalUri } from './copilotNonPtyShellTerminals.js';
import { getInvocationMessage, getPastTenseMessage, getShellIntention, getShellLanguage, getSubagentMetadata, getTaskCompleteMarkdown, getToolDisplayName, getToolInputString, getToolKind, isEditTool, isHiddenTool, isTaskCompleteTool, synthesizeSkillToolCall } from './copilotToolDisplay.js';
import { buildSessionDbUri } from '../shared/fileEditTracker.js';
import { getMediaMime } from '../../../../base/common/mime.js';
import { buildCopilotSystemNotification } from './copilotSystemNotification.js';
import { buildMcpChannel, buildMcpTopLevelCustomizationId } from '../shared/mcpCustomizationController.js';
import { readSimpleAttachmentDisplayKindFromMimeType } from './copilotAttachmentUtils.js';
try {
return JSON.stringify(value);
} catch {
return undefined;
}
/**
* Returns true if the event is a SDK-injected `user.message` that should not
* be shown to the user (e.g. skill-content injection).
*
* The SDK marks these via a non-`'user'` `source` field. Older sessions
* persisted before `source` existed will not be filtered; that is accepted
* leakage rather than guessed-at content sniffing.
*/
if (event.type !== 'user.message') {
return false;
}
return !!source && source.toLowerCase() !== 'user';
}
/**
* Converts SDK `tool.execution_complete` content blocks into AHP tool result
* content. A `shell_exit` block becomes {@link TerminalCommandResult} data on
* the tool call's terminal content block; when no terminal block exists yet
* (e.g. history replay, where no live channel survives) and `terminal` is
* provided, a non-pty terminal block is synthesized so the outcome still
* renders from `result.preview`. Returns the `shell_exit` outcome, if any, so
* the live path can settle the non-pty output channel from it.
*/
export interface ISdkShellExit {
readonly shellId: string;
readonly result: TerminalCommandResult;
}
export function appendSdkToolResultContent(content: ToolResultContent[], sdkContents: readonly ToolExecutionCompleteContent[] | undefined, terminal?: { session: URI | string; toolCallId: string; title: string }): ISdkShellExit | undefined {
for (const sdkContent of sdkContents ?? []) {
case 'shell_exit': {
const result: TerminalCommandResult = {
exitCode: sdkContent.exitCode,
...(sdkContent.outputPreview !== undefined ? { preview: sdkContent.outputPreview } : {}),
...(sdkContent.outputTruncated !== undefined ? { truncated: sdkContent.outputTruncated } : {}),
};
shellExit = { shellId: sdkContent.shellId, result };
const terminalIndex = content.findIndex(c => c.type === ToolResultContentType.Terminal);
if (terminalIndex !== -1) {
const terminalBlock = content[terminalIndex] as ToolResultTerminalContent;
mapSessionEvents.ts ×1
content[terminalIndex] = { ...terminalBlock, result };
type: ToolResultContentType.Terminal,
resource: buildNonPtyShellTerminalUri(terminal.session, terminal.toolCallId),
title: terminal.title,
isPty: false,
result,
});
}
}
}
}
}
// =============================================================================
// Single-pass turn builder
// =============================================================================
/** Per-tool-call info captured from `tool.execution_start` and reused at `tool.execution_complete`. */
interface IToolStartInfo {
readonly toolName: string;
readonly displayName: string;
readonly invocationMessage: StringOrMarkdown;
readonly toolInput?: string;
readonly toolKind?: 'terminal' | 'subagent' | 'search';
readonly language?: string;
/** Intention (why the command runs) for shell tools, from their `description` argument. */
readonly intention?: string;
readonly subagentAgentName?: string;
readonly subagentDescription?: string;
readonly parameters: Record<string, unknown> | undefined;
readonly parentToolCallId?: string;
readonly mcpServerName?: string;
readonly mcpToolName?: string;
readonly mcpUiResourceUri?: string;
}
/** Subagent metadata seen via `subagent.started`, applied to the parent tool call's content at `tool.execution_complete`. */
interface ISubagentInfo {
readonly agentName: string;
readonly agentDisplayName: string;
readonly agentDescription?: string;
}
/**
* Mutable per-turn state used while iterating events. The parent session
* has one builder; each subagent turn (one per `parentToolCallId`) has its
* own builder so inner events route there directly.
*/
interface ITurnBuilder {
id: string;
message: Message;
readonly responseParts: ResponsePart[];
usage: UsageInfo | undefined;
/** Tool starts seen but not yet completed in this turn, keyed by toolCallId. */
readonly pendingTools: Map<string, IToolStartInfo>;
}
export interface IMapSessionEventsOptions {
readonly workingDirectory?: URI;
readonly model?: ModelSelection;
readonly agent?: AgentSelection;
}
function newTurnBuilder(id: string, text: string, options?: { attachments?: MessageAttachment[]; model?: ModelSelection; agent?: AgentSelection; origin?: MessageKind }): ITurnBuilder {
mapSessionEvents.ts ×12
const message: Message = {
text,
origin: { kind: options?.origin ?? MessageKind.User },
...(options?.attachments?.length ? { attachments: options.attachments } : {}),
...(options?.model ? { model: options.model } : {}),
...(options?.agent ? { agent: options.agent } : {}),
};
return { id, message, responseParts: [], usage: undefined, pendingTools: new Map() };
}
function readStringProperty(source: unknown, key: string): string | undefined {
mapSessionEvents.ts ×12
if (!source || typeof source !== 'object' || Array.isArray(source)) {
return undefined;
}
return typeof value === 'string' && value.length > 0 ? value : undefined;
}
if (!source || typeof source !== 'object' || Array.isArray(source)) {
return undefined;
}
const toolDescription = (source as Record<string, unknown>)['toolDescription'];
mapSessionEvents.ts ×12
if (!toolDescription || typeof toolDescription !== 'object' || Array.isArray(toolDescription)) {
}
return undefined;
}
return undefined;
}
}
function makeToolStartInfo(toolName: string, rawArguments: unknown, parentToolCallId: string | undefined, workingDirectory: URI | undefined, source: unknown): IToolStartInfo | undefined {
mapSessionEvents.ts ×12
if (isHiddenTool(toolName)) {
}
const rawArgs = rawArguments !== undefined ? tryStringify(rawArguments) : undefined;
mapSessionEvents.ts ×12
let parameters: Record<string, unknown> | undefined;
if (rawArgs) {
try { parameters = JSON.parse(rawArgs) as Record<string, unknown>; } catch { /* ignore */ }
mapSessionEvents.ts ×3
}
// return value. We re-stringify only when it changed something so
// `getToolInputString` sees the cleaned command line.
const cleaned = stripRedundantCdPrefix(toolName, parameters, workingDirectory) ? tryStringify(parameters) : undefined;
const toolArgs = cleaned ?? rawArgs;
const toolKind = getToolKind(toolName);
const subagentMeta = toolKind === 'subagent' ? getSubagentMetadata(parameters) : undefined;
const displayName = getToolDisplayName(toolName);
return {
toolName,
displayName,
invocationMessage: getInvocationMessage(toolName, displayName, parameters),
toolInput: getToolInputString(toolName, parameters, toolArgs),
toolKind,
language: toolKind === 'terminal' ? getShellLanguage(toolName) : undefined,
intention: getShellIntention(toolName, parameters),
subagentAgentName: subagentMeta?.agentName,
subagentDescription: subagentMeta?.description,
parameters,
parentToolCallId,
mcpServerName: readStringProperty(source, 'mcpServerName'),
mcpToolName: readStringProperty(source, 'mcpToolName'),
mcpUiResourceUri: readMcpUiResourceUri(source),
};
}
function finalizeTurn(builder: ITurnBuilder, state: TurnState): Turn {
mapSessionEvents.ts ×12
return {
id: builder.id,
message: builder.message,
responseParts: builder.responseParts,
usage: builder.usage,
state,
};
}
/**
* Maps raw SDK session events directly into agent-protocol {@link Turn}s
* for the parent session and any subagent child sessions, restoring stored
* file-edit metadata from the session database when available.
*
* Subagent inner events are routed to per-`parentToolCallId` turn builders
* so they appear under their own session view rather than polluting the
* parent transcript. Each subagent's tool calls are returned via
* {@link mapSessionEventsToTurns.subagentTurnsByToolCallId} so callers can
* expose `getSubagentMessages` cheaply.
*
* If `workingDirectory` is provided, redundant `cd <workingDirectory> &&`
* (or PowerShell equivalent) prefixes are stripped from shell tool
* commands so clients see the simplified form.
*/
session: URI,
db: ISessionDatabase | undefined,
events: readonly SessionEvent[],
options: URI | IMapSessionEventsOptions | undefined = undefined,
): Promise<{ turns: Turn[]; subagentTurnsByToolCallId: ReadonlyMap<string, Turn[]> }> {
const workingDirectory = options instanceof URI ? options : options?.workingDirectory;
let currentModel = options instanceof URI ? undefined : options?.model;
let currentAgent = options instanceof URI ? undefined : options?.agent;
// First pass: collect tool-arg info and identify edit tool calls so we
// can batch-load their stored file edits before the second pass needs
// them at `tool.execution_complete` time. We also build the
// `agentId` -> parent tool call id map here so the second pass can route
// sub-agent events without depending on event ordering.
const toolInfoByCallId = new Map<string, IToolStartInfo>();
const editToolCallIds: string[] = [];
const completionsByCallId = new Map<string, ToolExecutionCompleteData>();
// The SDK tags events that originate from a sub-agent with an
// envelope-level `agentId` (the deprecated `data.parentToolCallId` is no
// longer populated). `subagent.started` carries both the sub-agent's
// `agentId` and the parent tool call id it was spawned from, so we map
// one to the other and resolve every later sub-agent event through it.
const parentToolCallIdByAgentId = new Map<string, string>();
const resolveParentToolCallId = (agentId: string | undefined, deprecatedParentToolCallId: string | undefined): string | undefined => {
const mapped = agentId ? parentToolCallIdByAgentId.get(agentId) : undefined;
mapSessionEvents.ts ×20
return mapped ?? deprecatedParentToolCallId;
};
for (const e of events) {
}
}
const parentToolCallId = resolveParentToolCallId(e.agentId, d.parentToolCallId);
const info = makeToolStartInfo(d.toolName, d.arguments, parentToolCallId, workingDirectory, d);
if (!info) {
continue;
}
const command = isString(info.parameters?.command) ? info.parameters.command : undefined;
if (isEditTool(d.toolName, command)) {
editToolCallIds.push(d.toolCallId);
}
// Pre-load stored file-edit metadata for all edit tool calls.
let storedEdits: Map<string, IFileEditRecord[]> | undefined;
if (db && editToolCallIds.length > 0) {
try {
const records = await db.getFileEdits(editToolCallIds);
if (records.length > 0) {
storedEdits = new Map();
for (const r of records) {
let list = storedEdits.get(r.toolCallId);
if (!list) {
list = [];
storedEdits.set(r.toolCallId, list);
}
list.push(r);
}
}
} catch {
// Database may not exist yet for new sessions — that's fine.
}
}
const sessionUriStr = session.toString();
const providerId = session.scheme;
const rawSessionId = AgentSession.id(session);
const turns: Turn[] = [];
// Subagent state. Each subagent has its own active turn builder; only
// the most recent turn per subagent is built (subagents currently emit
// at most one turn per invocation).
const subagentBuilders = new Map<string, ITurnBuilder>();
const subagentTurnStates = new Map<string, TurnState>();
const subagentTurns = new Map<string, Turn[]>();
const subagentInfoByToolCallId = new Map<string, ISubagentInfo>();
let parentBuilder: ITurnBuilder | undefined;
let parentTurnState = TurnState.Cancelled;
let parentTurnAborted = false;
let rootAssistantTurnActive = false;
let pendingAutoModeResolved: Extract<SessionEvent, { type: 'session.auto_mode_resolved' }>['data'] | undefined;
const flushParent = (): void => {
if (!parentBuilder) {
return;
}
parentBuilder = undefined;
parentTurnState = TurnState.Cancelled;
parentTurnAborted = false;
const flushSubagent = (parentToolCallId: string): void => {
if (!builder) {
return;
}
const state = subagentTurnStates.get(parentToolCallId) ?? TurnState.Complete;
if (builder.responseParts.length === 0) {
return;
}
subagentTurns.set(parentToolCallId, list);
};
const ensureSubagentBuilder = (parentToolCallId: string): ITurnBuilder => {
if (!builder) {
builder = newTurnBuilder(generateUuid(), '');
subagentBuilders.set(parentToolCallId, builder);
if (!subagentTurnStates.has(parentToolCallId)) {
}
return builder;
};
const targetBuilderFor = (parentToolCallId: string | undefined): ITurnBuilder | undefined => {
}
};
for (const e of events) {
case 'assistant.turn_start':
rootAssistantTurnActive = true;
}
break;
rootAssistantTurnActive = false;
}
break;
break;
}
pendingAutoModeResolved = e.data;
}
break;
}
if (!e.agentId) {
currentAgent = undefined;
}
break;
}
}
const messageId = d.interactionId ?? '';
const content = d.content ?? '';
const attachments = sdkAttachmentsToProtocol(d.attachments);
// User messages carry no deprecated `parentToolCallId`; route
// sub-agent user messages by the envelope `agentId` only.
const parentToolCallId = resolveParentToolCallId(e.agentId, undefined);
if (e.agentId && !parentToolCallId) {
}
builder.message = {
...builder.message,
text: content,
...(attachments?.length ? { attachments } : {}),
};
// A new top-level user message starts a new parent turn.
// Use the SDK envelope id (the same value
// `setTurnEventId` records as `event_id`) so the restored
// turn id round-trips back to the SDK boundary id that
// fork / truncate RPCs operate on.
flushParent();
const turnId = e.id ?? messageId;
parentBuilder = newTurnBuilder(turnId, content, { attachments, model: currentModel, agent: currentAgent });
if (pendingAutoModeResolved) {
model: pendingAutoModeResolved.chosenModel,
_meta: { autoModeResolved: pendingAutoModeResolved },
};
pendingAutoModeResolved = undefined;
}
break;
}
const messageId = d.messageId ?? d.interactionId ?? '';
const content = d.content ?? '';
const reasoningText = d.reasoningText;
const hasToolRequests = !!d.toolRequests && d.toolRequests.length > 0;
const parentToolCallId = resolveParentToolCallId(e.agentId, d.parentToolCallId);
if (!content && !reasoningText && !hasToolRequests) {
parentTurnState = TurnState.Complete;
}
break;
}
// yet), seed the builder with the SDK envelope id so the
// turn id matches `turns.event_id` for fork/truncate
// lookups. See the matching note in the `user.message`
// branch above.
const fallbackTurnId = e.id ?? messageId;
const builder = targetBuilderFor(parentToolCallId)
?? (parentBuilder = newTurnBuilder(fallbackTurnId, ''));
kind: ResponsePartKind.Reasoning,
id: generateUuid(),
content: reasoningText,
});
}
kind: ResponsePartKind.Markdown,
id: generateUuid(),
content,
});
}
if (!parentToolCallId && builder === parentBuilder && !parentTurnAborted) {
mapSessionEvents.ts ×6
parentTurnState = hasToolRequests ? TurnState.Cancelled : TurnState.Complete;
}
if (d.toolRequests?.length) {
}
}
if (!notification) {
break;
}
kind: ResponsePartKind.SystemNotification,
content: notification.messageText,
});
parentBuilder = newTurnBuilder(e.id, notification.messageText, { origin: MessageKind.SystemNotification });
}
}
subagentInfoByToolCallId.set(d.toolCallId, {
agentName: d.agentName,
agentDisplayName: d.agentDisplayName,
agentDescription: d.agentDescription,
});
break;
}
const parentToolCallId = resolveParentToolCallId(e.agentId, e.data.parentToolCallId);
mapSessionEvents.ts ×6
if (!parentToolCallId && parentBuilder) {
}
}
const info = toolInfoByCallId.get(d.toolCallId);
if (!info) {
continue;
}
const parentToolCallId = resolveParentToolCallId(e.agentId, d.parentToolCallId);
if (isTaskCompleteTool(info.toolName)) {
if (!builder) {
}
const summary = getTaskCompleteMarkdown(info.parameters, d.error?.message ?? d.result?.content);
mapSessionEvents.ts ×3
if (summary) {
kind: ResponsePartKind.Markdown,
id: generateUuid(),
content: summary,
});
}
if (!parentToolCallId && d.success && builder === parentBuilder && !parentTurnAborted) {
mapSessionEvents.ts ×3
}
continue;
}
if (!builder) {
// No active turn to attach this completion to.
continue;
}
const completedPart = makeCompletedToolCallPart(d, info, sessionUriStr, providerId, rawSessionId, storedEdits, subagentInfoByToolCallId.get(d.toolCallId));
mapSessionEvents.ts ×3
builder.responseParts.push(completedPart);
// When a parent tool call that spawned a subagent completes,
// flush the subagent's accumulated turn.
if (!parentToolCallId && subagentInfoByToolCallId.has(d.toolCallId)) {
mapSessionEvents.ts ×12
}
}
const parentToolCallId = resolveParentToolCallId(e.agentId, undefined);
const builder = targetBuilderFor(parentToolCallId)
?? (parentBuilder = newTurnBuilder(generateUuid(), ''));
parentTurnState = TurnState.Cancelled;
}
kind: ResponsePartKind.ToolCall,
toolCall: {
status: ToolCallStatus.Completed,
toolCallId: synth.toolCallId,
toolName: synth.toolName,
displayName: synth.displayName,
invocationMessage: synth.invocationMessage,
success: true,
pastTenseMessage: synth.pastTenseMessage,
confirmed: ToolCallConfirmationReason.NotNeeded,
} satisfies ToolCallCompletedState,
});
break;
}
const parentToolCallId = resolveParentToolCallId(e.agentId, undefined);
mapSessionEvents.ts ×3
if (parentToolCallId) {
if (parentBuilder) {
parentTurnState = TurnState.Cancelled;
parentTurnAborted = true;
}
}
}
}
flushParent();
for (const parentToolCallId of [...subagentBuilders.keys()]) {
flushSubagent(parentToolCallId);
}
return { turns, subagentTurnsByToolCallId: subagentTurns };
function appendFallbackToolRequests(builder: ITurnBuilder, toolRequests: readonly AssistantMessageToolRequest[], parentToolCallId: string | undefined): void {
const completion = completionsByCallId.get(request.toolCallId);
if (completion && toolInfoByCallId.has(request.toolCallId)) {
}
?? makeToolStartInfo(request.name, request.arguments, parentToolCallId, workingDirectory, request);
}
const summary = getTaskCompleteMarkdown(info.parameters, completion?.error?.message ?? completion?.result?.content);
mapSessionEvents.ts ×1
if (summary) {
builder.responseParts.push({
kind: ResponsePartKind.Markdown,
id: generateUuid(),
content: summary,
});
}
if (!parentToolCallId && completion?.success && builder === parentBuilder && !parentTurnAborted) {
parentTurnState = TurnState.Complete;
}
continue;
}
completion ?? { toolCallId: request.toolCallId, success: true },
sessionUriStr,
providerId,
rawSessionId,
storedEdits,
subagentInfoByToolCallId.get(request.toolCallId),
));
}
}
/**
* Translates the SDK's `UserMessageAttachment[]` payload back into the
* agent-protocol {@link MessageAttachment} shape. Text blob attachments
* surface as {@link MessageAttachmentKind.Simple}; other blobs surface as
* inline {@link MessageAttachmentKind.EmbeddedResource} payloads.
* File/directory/selection variants reconstruct local `Resource`
* attachments. We don't try to re-link these to the on-disk snapshots
* produced by the agent host's attachment rewriter — the SDK keeps a
* copy of the bytes / paths it actually saw on send, which is the
* authoritative record for replay.
*/
attachments: readonly Attachment[] | undefined,
): MessageAttachment[] | undefined {
if (!attachments?.length) {
}
for (const a of attachments) {
const converted = sdkAttachmentToProtocol(a);
if (converted) {
out.push(converted);
}
}
}
attachment: Attachment,
): MessageAttachment | undefined {
switch (attachment.type) {
case 'file': {
type: MessageAttachmentKind.Resource,
uri: URI.file(attachment.path).toString(),
label: attachment.displayName || basename(attachment.path),
displayKind: getMediaMime(attachment.path)?.startsWith('image/') ? 'image' : 'document',
};
}
return {
type: MessageAttachmentKind.Resource,
uri: URI.file(attachment.path).toString(),
label: attachment.displayName || basename(attachment.path),
displayKind: 'directory',
};
}
return {
type: MessageAttachmentKind.Resource,
uri: URI.file(attachment.filePath).toString(),
label: attachment.displayName,
displayKind: 'selection',
selection: { range: attachment.selection! },
};
}
return undefined;
}
const displayKind = readSimpleAttachmentDisplayKindFromMimeType(attachment.mimeType);
return {
type: MessageAttachmentKind.Simple,
label: attachment.displayName ?? 'attachment',
modelRepresentation: decodeBase64(attachment.data ?? '').toString(),
...(displayKind !== undefined ? { displayKind } : {}),
};
}
const displayKind = attachment.mimeType.startsWith('image/') ? 'image' : undefined;
return {
type: MessageAttachmentKind.EmbeddedResource,
label: attachment.displayName ?? 'attachment',
data: attachment.data ?? '',
contentType: attachment.mimeType,
displayKind,
};
}
return undefined;
}
/**
* Builds a {@link ToolCallCompletedState}-shaped response part from an
* SDK `tool.execution_complete` event. Restores file-edit content
* references from `storedEdits` and merges subagent metadata when the
* tool call spawned a child session.
*/
d: ToolExecutionCompleteData,
info: IToolStartInfo,
sessionUriStr: string,
providerId: string,
rawSessionId: string,
storedEdits: Map<string, IFileEditRecord[]> | undefined,
subagent: ISubagentInfo | undefined,
): ResponsePart {
const toolOutput = d.error?.message ?? d.result?.content;
const content: ToolResultContent[] = [];
if (toolOutput !== undefined) {
}
appendSdkToolResultContent(content, d.result?.contents, { session: sessionUriStr, toolCallId: d.toolCallId, title: info.displayName });
mapSessionEvents.ts ×5
// Restore file edit content references from the database.
const edits = storedEdits?.get(d.toolCallId);
if (edits) {
for (const edit of edits) {
const beforeUri = edit.kind === 'rename' && edit.originalPath
? URI.file(edit.originalPath).toString()
: URI.file(edit.filePath).toString();
const afterUri = URI.file(edit.filePath).toString();
const hasBefore = edit.kind !== 'create';
const hasAfter = edit.kind !== 'delete';
content.push({
type: ToolResultContentType.FileEdit,
before: hasBefore ? {
uri: beforeUri,
content: { uri: buildSessionDbUri(sessionUriStr, edit.toolCallId, edit.filePath, 'before') },
} : undefined,
after: hasAfter ? {
uri: afterUri,
content: { uri: buildSessionDbUri(sessionUriStr, edit.toolCallId, edit.filePath, 'after') },
} : undefined,
diff: (edit.addedLines !== undefined || edit.removedLines !== undefined)
? { added: edit.addedLines, removed: edit.removedLines }
: undefined,
});
}
}
if (subagent) {
type: ToolResultContentType.Subagent,
resource: buildSubagentSessionUri(sessionUriStr, d.toolCallId),
title: subagent.agentDisplayName,
agentName: subagent.agentName,
description: subagent.agentDescription,
});
}
const mcpServerName = info.mcpServerName ?? readStringProperty(d, 'mcpServerName');
const mcpToolName = info.mcpToolName ?? readStringProperty(d, 'mcpToolName');
const mcpUiResourceUri = info.mcpUiResourceUri ?? readMcpUiResourceUri(d);
const mcpUi: IToolCallUiMeta | undefined = mcpUiResourceUri
resourceUri: mcpUiResourceUri,
...(mcpServerName ? { channel: buildMcpChannel(providerId, rawSessionId, mcpServerName) } : {}),
}
const tc: ToolCallCompletedState = {
status: ToolCallStatus.Completed,
toolCallId: d.toolCallId,
toolName: info.toolName,
displayName: info.displayName,
intention: info.intention,
...(mcpServerName ? { contributor: { kind: ToolCallContributorKind.MCP, customizationId: buildMcpTopLevelCustomizationId(providerId, rawSessionId, mcpServerName) } } : {}),
invocationMessage: info.invocationMessage,
toolInput: info.toolInput,
success: d.success,
pastTenseMessage: getPastTenseMessage(info.toolName, info.displayName, info.parameters, d.success, d.success ? toolOutput : undefined),
content: content.length > 0 ? content : undefined,
error: d.error,
confirmed: ToolCallConfirmationReason.NotNeeded,
_meta: toToolCallMeta({
toolKind: info.toolKind,
language: info.language,
subagentDescription: info.subagentDescription,
subagentAgentName: info.subagentAgentName,
mcpServerName,
mcpToolName,
ui: mcpUi,
}),
};
return { kind: ResponsePartKind.ToolCall, toolCall: tc };
}