1
>
/*---------------------------------------------------------------------------------------------
agentSideEffects.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 { getErrorCode } from '../../../base/common/errors.js';
7
>
import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js';
8
>
import { NKeyMap } from '../../../base/common/map.js';
9
>
import { equals } from '../../../base/common/objects.js';
10
>
import { autorun, IObservable, IReader } from '../../../base/common/observable.js';
11
>
import { StopWatch } from '../../../base/common/stopwatch.js';
12
>
import { hasKey } from '../../../base/common/types.js';
13
>
import { URI } from '../../../base/common/uri.js';
14
>
import { generateUuid } from '../../../base/common/uuid.js';
15
>
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
16
>
import { ILogService } from '../../log/common/log.js';
17
>
import { IAgentHostChangesetService } from '../common/agentHostChangesetService.js';
18
>
import { IAgentHostCheckpointService } from '../common/agentHostCheckpointService.js';
19
>
import { readAgentModelByokIdentifier } from '../common/agentModelByokMeta.js';
20
>
import { AgentSession, AgentSignal, IAgent, IAgentToolPendingConfirmationSignal } from '../common/agentService.js';
21
>
import { readToolCallMeta, toToolCallMeta } from '../common/meta/agentToolCallMeta.js';
22
>
23
>
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
24
>
import { ISessionDataService } from '../common/sessionDataService.js';
25
>
import { SessionConfigKey } from '../common/sessionConfigKeys.js';
26
>
import { resolveChatAttachment } from '../common/state/chatAttachmentContext.js';
27
>
import { SessionInputRequestKind, ToolCallContributorKind, type AgentInfo, type SessionInputRequest } from '../common/state/protocol/state.js';
28
>
import { ActionType, isChatAction, StateAction, type ChatAction, type ChatToolCallCompleteAction } from '../common/state/sessionActions.js';
29
>
import {
30
>
buildSubagentChatUri,
31
>
getToolFileEdits,
32
>
isAhpChatChannel,
33
>
isDefaultChatUri,
34
>
isSubagentChatUri,
35
>
isChatReadOnly,
36
>
AH_META_IS_ARCHIVED_DB_KEY,
37
>
MessageAttachmentKind,
38
>
MessageKind,
39
>
parseChatUri,
40
>
parseRequiredSessionUriFromChatUri,
41
>
PendingMessageKind,
42
>
ResponsePartKind,
43
>
ROOT_STATE_URI,
44
>
SessionLifecycle,
45
>
SessionStatus,
46
>
ToolCallStatus,
47
>
ToolResultContentType,
48
>
type ErrorInfo,
49
>
type ISessionWithDefaultChat,
50
>
type Message,
51
>
type MessageAttachment,
52
>
type URI as ProtocolURI,
53
>
type SessionState,
54
>
type ToolCallState,
55
>
type ToolCallResult,
56
>
type ToolResultContent,
57
>
type Turn
58
>
} from '../common/state/sessionState.js';
59
>
import { AgentHostLocalTurns } from './agentHostLocalTurns.js';
60
>
import { AgentHostSessionTitleController } from './agentHostSessionTitleController.js';
61
>
import { AgentHostStateManager } from './agentHostStateManager.js';
62
>
import { AgentHostTelemetryReporter, type AgentHostModelTelemetryKind, type AgentHostTurnFailureStage, type IAgentHostTurnFailure } from './agentHostTelemetryReporter.js';
63
>
import { AgentHostToolCallTracker } from './agentHostToolCallTracker.js';
64
>
import { updateAgentHostTelemetryLevelFromConfig } from './agentHostTelemetryService.js';
65
>
import { AgentHostTurnTracker } from './agentHostTurnTracker.js';
66
>
import { AgentHostLocalCommands } from './localCommands/localChatCommand.js';
67
>
import './localCommands/localChatCommands.contribution.js';
68
>
import { SessionPermissionManager } from './sessionPermissions.js';
69
>
import type { ICopilotApiService } from './shared/copilotApiService.js';
70
>
import { stripProxyErrorMarker, toChatErrorMeta, tryParseForwardedChatError } from './shared/forwardedChatError.js';
71
>
import { persistSessionMetadata } from './shared/persistSessionMetadata.js';
72
>
import type { WorktreeIsolation } from './shared/worktreeIsolation.js';
73
>
74
>
/**
75
>
* Options for constructing an {@link AgentSideEffects} instance.
76
>
*/
77
>
export interface IAgentSideEffectsOptions {
78
>
/** Resolve the agent responsible for a given session URI. */
79
>
readonly getAgent: (session: ProtocolURI) => IAgent | undefined;
80
>
/** Observable set of registered agents. Triggers `root/agentsChanged` when it changes. */
81
>
readonly agents: IObservable<readonly IAgent[]>;
82
>
/** Session data service for cleaning up per-session data on disposal. */
83
>
readonly sessionDataService: ISessionDataService;
84
>
/** Registry that persists host-injected `/rename` and `!command` turns. */
85
>
readonly localTurns: AgentHostLocalTurns;
86
>
/** Get the GitHub token used for Copilot utility title generation. */
87
>
readonly getGitHubCopilotToken?: () => string | undefined;
88
>
/** CAPI service used for Copilot utility title generation. */
89
>
readonly copilotApiService?: ICopilotApiService;
90
>
/**
91
>
* Host-owned working-directory resolution hook, awaited before the agent's
92
>
* first send so the session's working directory (an isolated worktree created
93
>
* on the first send, or the picked folder) is resolved before the agent
94
>
* materializes and its cwd is locked. Resolves to the working directory to
95
>
* hand the agent, or `undefined` for workspace-less sessions. Provided by
96
>
* {@link AgentService}.
97
>
*/
98
>
readonly resolveWorkingDirectoryBeforeSend?: (params: { session: ProtocolURI; chat: ProtocolURI; turnId: string; prompt: string }) => Promise<URI | undefined>;
99
>
/** Resolves a referenced chat's turns, hydrating its owning session when needed. */
100
>
readonly resolveChatAttachmentTurns?: (resource: ProtocolURI) => Promise<readonly Turn[]>;
101
>
/**
102
>
* Called after each top-level session turn completes so git state can be
103
>
* refreshed and published via `SessionMetaChanged`. Subagent turns are
104
>
* excluded — only the parent session URI is passed.
105
>
*/
106
>
readonly onTurnComplete: (session: ProtocolURI) => void;
107
>
}
108
>
109
>
/** A signal that was deferred because its subagent session does not exist yet. */
110
>
interface IPendingSubagentSignal {
111
>
readonly signal: AgentSignal;
112
>
readonly agent: IAgent;
113
>
}
114
>
115
>
interface ISubagentSessionRef {
116
>
readonly parentChatUri: ProtocolURI;
117
>
readonly toolCallId: string;
118
>
readonly sessionUri: ProtocolURI;
119
>
readonly chatUri: ProtocolURI;
120
>
readonly turnStopWatch: StopWatch;
121
>
}
122
>
123
>
type AgentSignalTurnIdRouting = 'preserve' | 'remap';
124
>
125
>
/**
126
>
* Shared implementation of agent side-effect handling.
127
>
*
128
>
* Routes client-dispatched actions to the correct agent backend,
129
>
* restores sessions from previous lifetimes, handles filesystem
130
>
* operations (browse/fetch/write), tracks pending permission requests,
131
>
* and wires up agent progress events to the state manager.
132
>
*
133
>
* Session create/dispose/list and auth are handled by {@link AgentService}.
134
>
*/
135
>
export class AgentSideEffects extends Disposable {
136
>
137
>
/** Maps tool call IDs to the agent that owns them, for routing confirmations. */
138
>
private readonly _toolCallAgents = new Map<string, string>();
139
>
private _lastAgentInfos: readonly AgentInfo[] = [];
140
>
141
>
private readonly _permissionManager: SessionPermissionManager;
142
>
143
>
/** Registry-driven dispatcher for host-handled `/rename` / `!command` etc. */
144
>
private readonly _localCommands: AgentHostLocalCommands;
145
>
146
>
private readonly _subagentChats = new NKeyMap<ISubagentSessionRef, [ProtocolURI, string]>();
147
>
private readonly _cancelledTurnIds = new Map<ProtocolURI, Set<string>>();
148
>
149
>
/**
150
>
* Buffers signals whose `parentToolCallId` references a subagent
151
>
* whose `subagent_started` signal has not yet been processed. The SDK is
152
>
* not strict about ordering: an inner `tool_start` can arrive before the
153
>
* `subagent_started` that creates the child session. Without buffering,
154
>
* those signals would be dispatched against the parent session and the
155
>
* UI would render the inner tool calls flat at the top level rather than
156
>
* grouping them under the subagent. Drained by `_handleSubagentStarted`.
157
>
*
158
>
*/
159
>
private readonly _pendingSubagentSignals = new NKeyMap<IPendingSubagentSignal[], [ProtocolURI, string]>();
160
>
private readonly _telemetryReporter: AgentHostTelemetryReporter;
161
>
private readonly _turnTracker: AgentHostTurnTracker;
162
>
private readonly _toolCallTracker: AgentHostToolCallTracker;
163
>
private readonly _titleController: AgentHostSessionTitleController;
164
>
/** Host-owned worktree isolation controller; injected post-construction. */
165
>
private _worktree: WorktreeIsolation | undefined;
166
>
167
>
constructor(
168
private readonly _stateManager: AgentHostStateManager,
169
private readonly _options: IAgentSideEffectsOptions,