chatSessionsService.ts ×12

Frontier kind: Code frontier

unlabeled · c_c16d2f0eeed3

1133 tests · 6548 LOC · 32 files · introduces 0 tests · 1029 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
13 ranges1029 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
784 ranges6548 lines · 32 files · Browse complete extent
All tests (intent)
1133 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.

2 files ranked by introduced lines: 1029 introduced LOC across 13 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/chatSessionsService.ts 903 introduced LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatSessionsService.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 { CancellationToken } from '../../../../base/common/cancellation.js';
7 > import { Event } from '../../../../base/common/event.js';
8 > import { IMarkdownString } from '../../../../base/common/htmlContent.js';
9 > import { IDisposable } from '../../../../base/common/lifecycle.js';
10 > import { IObservable } from '../../../../base/common/observable.js';
11 > import { ThemeIcon } from '../../../../base/common/themables.js';
12 > import { URI } from '../../../../base/common/uri.js';
13 > import { IPosition } from '../../../../editor/common/core/position.js';
14 > import { isRemoteAgentHostSessionType } from '../../../../platform/agentHost/common/agentHostSessionType.js';
15 > import { createDecorator, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
16 > import { Registry } from '../../../../platform/registry/common/platform.js';
17 > import { LOCAL_AGENT_HOST_SCHEME_PREFIX } from '../../../../platform/agentHost/common/agentHostConnectionsService.js';
18 > import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './participants/chatAgents.js';
19 > import { IChatEditingSession } from './editing/chatEditingService.js';
20 > import { IChatRequestModeInstructions, IChatRequestVariableData, ISerializableChatModelInputState } from './model/chatModel.js';
21 > import { IChatProgress, IChatResponseErrorDetails, IChatSessionTiming } from './chatService/chatService.js';
22 > import { Target } from './promptSyntax/promptTypes.js';
23 >
24 > export const enum ChatSessionsExtensions {
25 > AsyncActivation = 'workbench.contrib.chatSessions.asyncActivation'
26 > }
27 >
28 > export interface IAsyncChatSessionActivationContribution {
29 > matchSessionType(sessionType: string): boolean;
30 > waitForActivation(accessor: ServicesAccessor, sessionType: string): Promise<boolean>;
31 > }
32 >
33 > export interface IAsyncChatSessionActivationRegistry {
34 > register(contribution: IAsyncChatSessionActivationContribution): IDisposable;
35 > getActivators(sessionType: string): readonly IAsyncChatSessionActivationContribution[];
36 > }
37 >
38 > class AsyncChatSessionActivationRegistry implements IAsyncChatSessionActivationRegistry {
39 > private readonly _contributions = new Set<IAsyncChatSessionActivationContribution>();
40 >
41 > register(contribution: IAsyncChatSessionActivationContribution): IDisposable {
42 this._contributions.add(contribution);
43 return {
45 };
46 }
48 > getActivators(sessionType: string): readonly IAsyncChatSessionActivationContribution[] {
49 return Array.from(this._contributions).filter(contribution => contribution.matchSessionType(sessionType));
50 }
52 >
53 > Registry.add(ChatSessionsExtensions.AsyncActivation, new AsyncChatSessionActivationRegistry());
54 >
55 > export const enum ChatSessionStatus {
56 > Failed = 0,
57 > Completed = 1,
58 > InProgress = 2,
59 > NeedsInput = 3
60 > }
61 >
62 > export interface IChatSessionCommandContribution {
63 > readonly name: string;
64 > readonly description: string;
65 > readonly when?: string;
66 > }
67 >
68 > export interface IChatSessionProviderOptionModelMetadata {
69 > readonly name: string;
70 > readonly id: string;
71 > readonly vendor?: string;
72 > readonly version?: string;
73 > readonly family?: string;
74 > readonly tooltip?: string;
75 > readonly pricing?: string;
76 > readonly multiplierNumeric?: number;
77 > readonly inputCost?: number;
78 > readonly outputCost?: number;
79 > readonly cacheCost?: number;
80 > readonly cacheWriteCost?: number;
81 > readonly longContextInputCost?: number;
82 > readonly longContextOutputCost?: number;
83 > readonly longContextCacheCost?: number;
84 > readonly longContextCacheWriteCost?: number;
85 > readonly priceCategory?: string;
86 > readonly promo?: {
87 > readonly id: string;
88 > readonly discountPercent: number;
89 > readonly endsAt: string;
90 > readonly message: string;
91 > };
92 > readonly maxInputTokens?: number;
93 > readonly maxOutputTokens?: number;
94 > readonly capabilities?: {
95 > readonly vision?: boolean;
96 > readonly toolCalling?: boolean;
97 > };
98 > }
99 >
100 > export interface IChatSessionProviderOptionItem {
101 > readonly id: string;
102 > readonly name: string;
103 > readonly description?: string;
104 > readonly detail?: string;
105 > readonly locked?: boolean;
106 > readonly icon?: ThemeIcon;
107 > readonly default?: boolean;
108 > readonly slashCommand?: string;
109 > readonly tooltip?: string;
110 > readonly modelMetadata?: IChatSessionProviderOptionModelMetadata;
111 > // [key: string]: any;
112 > }
113 >
114 > export interface IChatSessionProviderOptionGroupCommand {
115 > readonly command: string;
116 > readonly title: string;
117 > readonly tooltip?: string;
118 > readonly arguments?: readonly unknown[];
119 > }
120 >
121 > export interface IChatSessionProviderOptionGroup {
122 > readonly id: string;
123 > readonly name: string;
124 > readonly description?: string;
125 > readonly detail?: string;
126 > readonly selected?: IChatSessionProviderOptionItem;
127 > readonly items: readonly IChatSessionProviderOptionItem[];
128 > /**
129 > * A context key expression that controls visibility of this option group picker.
130 > * When specified, the picker is only visible when the expression evaluates to true.
131 > * The expression can reference other option group values via `chatSessionOption.<groupId>`.
132 > * Example: `"chatSessionOption.models == 'gpt-4'"`
133 > */
134 > readonly when?: string;
135 > readonly icon?: ThemeIcon;
136 > /**
137 > * Custom commands to show in the option group's picker UI.
138 > * These will be shown in a separate section at the end of the picker.
139 > */
140 > readonly commands?: readonly IChatSessionProviderOptionGroupCommand[];
141 > /**
142 > * Optional kind hint that controls how the group is presented.
143 > * - `'permissions'`: the group's items are surfaced inside the chat permission picker
144 > * instead of being rendered as a standalone picker. At most one group per provider
145 > * may use this kind; if multiple are declared, the first one (in declaration order)
146 > * wins. The group has no UI of its own — it is invisible when the permission
147 > * picker is hidden by its own `when` clauses.
148 > */
149 > readonly kind?: 'permissions';
150 > }
151 >
152 > export interface IChatSessionsExtensionPoint {
153 > readonly type: string;
154 > readonly name: string;
155 > readonly displayName: string;
156 > readonly description: string;
157 > readonly when?: string;
158 > readonly icon?: string | { light: string; dark: string };
159 > readonly order?: number;
160 > readonly alternativeIds?: string[];
161 > readonly welcomeTitle?: string;
162 > readonly welcomeMessage?: string;
163 > readonly welcomeTips?: string;
164 > readonly inputPlaceholder?: string;
165 > readonly capabilities?: IChatAgentAttachmentCapabilities;
166 > readonly commands?: IChatSessionCommandContribution[];
167 > readonly canDelegate?: boolean;
168 > readonly isReadOnly?: boolean;
169 > /**
170 > * When set, the chat session will show a filtered mode picker with custom agents
171 > * that have a matching `target` property. This enables contributed chat sessions
172 > * to reuse the standard agent/mode dropdown with filtered custom agents.
173 > * Custom agents without a `target` property are also shown in all filtered lists
174 > */
175 > readonly customAgentTarget?: Target;
176 > readonly requiresCustomModels?: boolean;
177 > /**
178 > * Whether this session type supports the synthetic "Auto" model fallback.
179 > * Defaults to true. When false and no models are available, the picker
180 > * shows a "No models available" state instead of "Auto".
181 > *
182 > * This is distinct from {@link requiresCustomModels}, which only controls
183 > * whether the picker is filtered to the session's own model pool — a
184 > * session can own a custom pool yet still support Auto (e.g. the Copilot
185 > * CLI agent host).
186 > */
187 > readonly supportsAutoModel?: boolean;
188 > /**
189 > * Logical Agent Host provider ID for Agent Host-backed chat sessions.
190 > * For example, both local `agent-host-copilotcli` and remote
191 > * `remote-{authority}-copilotcli` sessions use `copilotcli`.
192 > */
193 > readonly agentHostProviderId?: string;
194 > /**
195 > * Whether this type needs a GitHub Copilot account and so is unusable until the user signs in. Set by
196 > * Copilot-backed types (Copilot CLI / agent host, cloud agent) where BYOK isn't supported. Defaults to false, so
197 > * third-party types that don't depend on Copilot stay usable while signed out.
198 > */
199 > readonly requiresCopilotSignIn?: boolean;
200 > /**
201 > * When false, the delegation picker is hidden for this session type.
202 > * Defaults to true.
203 > */
204 > readonly supportsDelegation?: boolean;
205 > /**
206 > * Decides whether to automatically attach instruction files to chat requests
207 > * for this session type. Defaults to false when not specified.
208 > */
209 > readonly autoAttachReferences?: boolean;
210 > }
211 >
212 > export interface IChatSessionItem {
213 > readonly resource: URI;
214 > readonly label: string;
215 > readonly iconPath?: ThemeIcon;
216 > readonly badge?: string | IMarkdownString;
217 > readonly description?: string | IMarkdownString;
218 > readonly status?: ChatSessionStatus;
219 > readonly tooltip?: string | IMarkdownString;
220 > readonly timing: IChatSessionTiming;
221 > readonly changes?: {
222 > readonly files: number;
223 > readonly insertions: number;
224 > readonly deletions: number;
225 > } | readonly IChatSessionFileChange[] | readonly IChatSessionFileChange2[];
226 > readonly archived?: boolean;
227 > readonly metadata?: IChatSessionItemMetadata;
228 > /**
229 > * Resource identifier the item was previously known by. When set, host-stored
230 > * per-resource state (archive, pin, read) recorded under that URI is adopted
231 > * forward onto {@link resource} on first state read, and the legacy entry is
232 > * removed. Scheme must match {@link resource}'s scheme; otherwise ignored.
233 > */
234 > readonly legacyResource?: URI;
235 > }
236 >
237 > export interface IChatSessionItemMetadata {
238 > //#region Changes metadata (for sessions window)
239 > readonly repositoryPath?: string;
240 > readonly workingDirectoryPath?: string;
241 > readonly firstCheckpointRef?: string;
242 > readonly lastCheckpointRef?: string;
243 > readonly worktreePath?: string;
244 > readonly uncommittedChanges?: number;
245 > readonly baseRefOid?: string;
246 > readonly headRefOid?: string;
247 > readonly branchName?: string;
248 > readonly branch?: string;
249 > readonly baseBranchName?: string;
250 > readonly baseBranch?: string;
251 > readonly baseBranchProtected?: boolean;
252 > readonly hasGitHubRemote?: boolean;
253 > readonly upstreamBranchName?: string;
254 > readonly incomingChanges?: number;
255 > readonly outgoingChanges?: number;
256 > //#endregion
257 >
258 > readonly [key: string]: unknown;
259 > }
260 >
261 > export interface IChatSessionFileChange {
262 > readonly modifiedUri: URI;
263 > readonly originalUri?: URI;
264 > readonly insertions: number;
265 > readonly deletions: number;
266 > readonly reviewed?: boolean;
267 > }
268 >
269 > export interface IChatSessionFileChange2 {
270 > readonly uri: URI;
271 > readonly originalUri?: URI;
272 > readonly modifiedUri?: URI;
273 > readonly insertions: number;
274 > readonly deletions: number;
275 > readonly reviewed?: boolean;
276 > }
277 >
278 > export type IChatSessionHistoryItem = {
279 > id?: string;
280 > type: 'request';
281 > prompt: string;
282 > participant: string;
283 > command?: string;
284 > variableData?: IChatRequestVariableData;
285 > modelId?: string;
286 > timestamp?: number;
287 > modeInstructions?: IChatRequestModeInstructions;
288 > isSystemInitiated?: boolean;
289 > systemInitiatedLabel?: string;
290 > isTerminalRequest?: boolean;
291 > } | {
292 > type: 'response';
293 > parts: IChatProgress[];
294 > participant: string;
295 > details?: string;
296 > elapsedMs?: number;
297 > completedAt?: number;
298 > /**
299 > * Error details for a failed response. Rendered as a proper chat error
300 > * (including the quota-exceeded upgrade affordance), mirroring the live
301 > * agent result's `errorDetails`.
302 > */
303 > errorDetails?: IChatResponseErrorDetails;
304 > };
305 >
306 > export type IChatSessionRequestHistoryItem = Extract<IChatSessionHistoryItem, { type: 'request' }>;
307 >
308 > export interface IChatSessionServerRequest {
309 > readonly prompt: string;
310 > readonly variableData?: IChatRequestVariableData;
311 > readonly timestamp?: number;
312 > readonly isSystemInitiated?: boolean;
313 > readonly systemInitiatedLabel?: string;
314 > readonly isTerminalRequest?: boolean;
315 > }
316 >
317 > /**
318 > * Whether `text` runs as a terminal command for the given command `prefix`
319 > * (e.g. `!`) — it starts with the prefix and has a non-empty command after it.
320 > * Mirrors the agent host's bang parser, where a lone `!` (or `!` followed only
321 > * by whitespace) is forwarded to the agent rather than executed.
322 > */
323 > export function isTerminalCommandPrompt(text: string, prefix: string | undefined): boolean {
324 return !!prefix && text.startsWith(prefix) && text.slice(prefix.length).trim().length > 0;
325 }
327 > /**
328 > * A set of well-known session types
329 > */
330 > export namespace SessionType {
331 > export const CopilotCLI = 'copilotcli';
332 > export const CopilotCloud = 'copilot-cloud-agent';
333 > export const Local = 'local';
334 > export const ClaudeCode = 'claude-code';
335 > export const Codex = 'openai-codex';
336 > export const Growth = 'copilot-growth';
337 > export const AgentHostCopilot = 'agent-host-copilotcli';
338 > export const AgentHostClaude = 'agent-host-claude';
339 > export const AgentHostCodex = 'agent-host-codex';
340 > }
341 >
342 > /**
343 > * Returns whether the given session type is a local agent host target.
344 > */
345 > export function isLocalAgentHostTarget(target: string): boolean {
346 return target === SessionType.AgentHostCopilot ||
347 target.startsWith(LOCAL_AGENT_HOST_SCHEME_PREFIX);
348 }
350 > /**
351 > * Returns whether the given session type is a remote agent host target.
352 > *
353 > * Note: The `remote-` prefix convention is established by
354 > * `RemoteAgentHostContribution` which generates session types as
355 > * `remote-{sanitizedAddress}-{provider}`. If future remote providers that
356 > * are NOT agent hosts need a different prefix, this function must be updated.
357 > */
358 > export function isRemoteAgentHostTarget(target: string): boolean {
359 return isRemoteAgentHostSessionType(target);
360 }
362 > /**
363 > * Returns whether the given session type is an agent host target.
364 > * Matches the local agent host (`agent-host-*`) and remote agent hosts (`remote-*`).
365 > */
366 > export function isAgentHostTarget(target: string): boolean {
367 return isLocalAgentHostTarget(target) || isRemoteAgentHostTarget(target);
368 }
370 > /**
371 > * The session type used for local agent chat sessions.
372 > */
373 > export const localChatSessionType = SessionType.Local;
374 >
375 > export interface IChatSession extends IDisposable {
376 > readonly onWillDispose: Event<void>;
377 >
378 > readonly sessionResource: URI;
379 >
380 > readonly title?: string;
381 >
382 > readonly history: readonly IChatSessionHistoryItem[];
383 >
384 >
385 > readonly options?: ReadonlyChatSessionOptionsMap;
386 >
387 > readonly progressObs?: IObservable<IChatProgress[]>;
388 > readonly isCompleteObs?: IObservable<boolean>;
389 > readonly isReadOnly?: IObservable<boolean>;
390 > readonly interruptActiveResponseCallback?: () => Promise<boolean>;
391 >
392 > /**
393 > * Event fired when the server initiates a new request (e.g. from a consumed
394 > * queued message). The consumer should create a new request+response pair in
395 > * the model and prepare to receive progress via {@link progressObs}.
396 > */
397 > readonly onDidStartServerRequest?: Event<IChatSessionServerRequest>;
398 >
399 > /**
400 > * Editing session transferred from a previously-untitled chat session in `onDidCommitChatSessionItem`.
401 > */
402 > transferredState?: {
403 > readonly editingSession: IChatEditingSession | undefined;
404 > readonly inputState: ISerializableChatModelInputState | undefined;
405 > };
406 >
407 > requestHandler?: (
408 > request: IChatAgentRequest,
409 > progress: (progress: IChatProgress[]) => void,
410 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
411 > history: any[], // TODO: Nail down types
412 > token: CancellationToken
413 > ) => Promise<void>;
414 >
415 > /**
416 > * Forks the session from the given request point.
417 > * @param request The request history item to fork from, or undefined to fork from the end.
418 > * @param token Cancellation token.
419 > * @returns The forked session item. The promise is rejected if forking fails.
420 > */
421 > forkSession?: (request: IChatSessionRequestHistoryItem | undefined, token: CancellationToken) => Promise<IChatSessionItem>;
422 >
423 > /**
424 > * Renames the session.
425 > * @param title The new title for the session.
426 > * @param token Cancellation token.
427 > * @returns A promise that resolves once the rename has been dispatched. The promise is rejected if renaming fails.
428 > */
429 > renameSession?: (title: string, token: CancellationToken) => Promise<void>;
430 > }
431 >
432 > export interface IChatSessionContentProvider {
433 > provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise<IChatSession>;
434 >
435 > /** Resolves a parsed response Markdown URI before it is sanitized and rendered. */
436 > resolveChatResponseUri?(sessionResource: URI, href: string, kind: 'link' | 'image'): string;
437 >
438 > /**
439 > * Optional. Compute completion items for an input being composed in this
440 > * session. Returning `undefined` lets the workbench fall back to its
441 > * default in-process completion providers.
442 > */
443 > provideChatInputCompletions?(sessionResource: URI, params: IChatInputCompletionsParams, token: CancellationToken): Promise<IChatInputCompletionsResult | undefined>;
444 >
445 > /**
446 > * Optional. Trigger characters that, when typed in the chat input,
447 > * SHOULD cause the workbench to issue a `provideChatInputCompletions`
448 > * request. Used to register a Monaco completion provider scoped to
449 > * sessions handled by this content provider.
450 > */
451 > provideChatInputCompletionTriggerCharacters?(): Promise<readonly string[]>;
452 > }
453 >
454 > /**
455 > * Inputs for {@link IChatSessionContentProvider.provideChatInputCompletions}
456 > * and {@link IChatSessionsService.provideChatInputCompletions}.
457 > */
458 > export interface IChatInputCompletionsParams {
459 > /**
460 > * The complete text of the input being completed (e.g. the user message
461 > * the user is currently composing).
462 > */
463 > readonly text: string;
464 > /**
465 > * The character offset within {@link text} at which the completion is
466 > * requested, measured in UTF-16 code units. MUST satisfy
467 > * `0 <= offset <= text.length`.
468 > */
469 > readonly offset: number;
470 > }
471 >
472 > /**
473 > * A neutral completion-item shape returned by
474 > * {@link IChatSessionContentProvider.provideChatInputCompletions}. The
475 > * workbench-side completion glue maps these into Monaco completion items
476 > * and the corresponding chat-input attachment.
477 > */
478 > export interface IChatInputCompletionItem {
479 > /** Text inserted into the input when this item is accepted. */
480 > readonly insertText: string;
481 > /**
482 > * Optional display label shown in the completion picker. When omitted, the
483 > * workbench displays {@link insertText}. Set this when the inserted text
484 > * differs from the label — e.g. an action item that inserts nothing
485 > * (`insertText: ''`) but should still be shown to the user.
486 > */
487 > readonly label?: string;
488 > /**
489 > * Half-open range `[start, end)` in the *current* input text that
490 > * {@link insertText} replaces. Positions use 1-based `lineNumber` and
491 > * `column` to match Monaco. When omitted, the workbench replaces the
492 > * word at the cursor.
493 > */
494 > readonly start?: IPosition;
495 > readonly end?: IPosition;
496 > /** Attachment associated with the item. */
497 > readonly attachment: IChatInputCompletionResourceAttachment | IChatInputCompletionCommandAttachment | IChatInputCompletionSkillAttachment;
498 > }
499 >
500 > /**
501 > * Resource attachment associated with a completion item. The workbench
502 > * adds it to the input's variable model when the item is accepted.
503 > */
504 > export interface IChatInputCompletionResourceAttachment {
505 > readonly kind: 'resource';
506 > readonly uri: URI;
507 > readonly displayName?: string;
508 > readonly isDirectory?: boolean;
509 > /**
510 > * Implementation-defined metadata that MUST be preserved by the
511 > * workbench when the accepted completion is sent back as part of a
512 > * user message attachment.
513 > */
514 > readonly _meta?: Record<string, unknown>;
515 > }
516 >
517 > /**
518 > * Command attachment associated with a completion item.
519 > */
520 > export interface IChatInputCompletionCommandAttachment {
521 > readonly kind: 'command';
522 > readonly command: string;
523 > readonly description: string;
524 > /**
525 > * Implementation-defined metadata that MUST be preserved by the
526 > * workbench when the accepted completion is sent back as part of a
527 > * user message attachment.
528 > */
529 > readonly _meta?: Record<string, unknown>;
530 > }
531 >
532 > /**
533 > * Skill attachment associated with a completion item. The workbench
534 > * adds it to the input's variable model when the item is accepted.
535 > */
536 > export interface IChatInputCompletionSkillAttachment {
537 > readonly kind: 'skill';
538 > readonly uri: URI;
539 > readonly displayName?: string;
540 > readonly description?: string;
541 > /**
542 > * Implementation-defined metadata that MUST be preserved by the
543 > * workbench when the accepted completion is sent back as part of a
544 > * user message attachment.
545 > */
546 > readonly _meta?: Record<string, unknown>;
547 > }
548 >
549 > /**
550 > * Result of {@link IChatSessionContentProvider.provideChatInputCompletions}.
551 > */
552 > export interface IChatInputCompletionsResult {
553 > readonly items: readonly IChatInputCompletionItem[];
554 > }
555 >
556 > export interface IChatNewSessionRequest {
557 > readonly prompt: string;
558 > readonly command?: string;
559 >
560 > readonly initialSessionOptions?: ReadonlyChatSessionOptionsMap;
561 >
562 > /**
563 > * The chat-input session resource the user was typing into when this
564 > * request was issued. Set when the chat infrastructure is rewriting an
565 > * untitled session URI to a real one on first send. Controllers can use
566 > * this to bridge any pre-creation state they tracked under the old URI
567 > * (e.g. provisional agent-host sessions) to the new resource that the
568 > * controller returns.
569 > */
570 > readonly untitledResource?: URI;
571 > }
572 >
573 > export interface IChatSessionItemsDelta {
574 > readonly addedOrUpdated?: readonly IChatSessionItem[];
575 > readonly removed?: readonly URI[];
576 > }
577 >
578 > export interface IChatSessionItemController {
579 >
580 > readonly onDidChangeChatSessionItems: Event<IChatSessionItemsDelta>;
581 >
582 > get items(): readonly IChatSessionItem[];
583 >
584 > refresh(token: CancellationToken): Promise<void>;
585 >
586 > newChatSessionItem?(request: IChatNewSessionRequest, token: CancellationToken): Promise<IChatSessionItem | undefined>;
587 >
588 > getNewChatSessionInputState?(sessionResource: URI, token: CancellationToken): Promise<readonly IChatSessionProviderOptionGroup[] | undefined>;
589 >
590 > resolveChatSessionItem?(resource: URI, token: CancellationToken): Promise<IChatSessionItem | undefined>;
591 >
592 > /**
593 > * Permanently delete the session identified by `resource`. Implementations should tear down any backend state for
594 > * the session. The controller is expected to fire an `onDidChangeChatSessionItems` event with the removed resource
595 > * as a result of the deletion.
596 > */
597 > deleteChatSessionItem?(resource: URI, token: CancellationToken): Promise<void>;
598 >
599 > /**
600 > * Set the authoritative archived state for the session identified by `resource`.
601 > */
602 > setChatSessionItemArchived?(resource: URI, archived: boolean): void;
603 > }
604 >
605 > export interface IChatSessionOptionsChangeEvent {
606 > readonly sessionResource: URI;
607 > readonly updates: ReadonlyMap<string, string | IChatSessionProviderOptionItem | undefined>;
608 > }
609 >
610 > export type ResolvedChatSessionsExtensionPoint = Omit<IChatSessionsExtensionPoint, 'icon'> & {
611 > readonly icon: ThemeIcon | URI | undefined;
612 > };
613 >
614 > /**
615 > * Session options as key-value pairs.
616 > *
617 > * Keys correspond to option group IDs (e.g., 'models', 'subagents') and values are either the selected option item IDs (string) or full option items (for locked state).
618 > */
619 > export type ChatSessionOptionsMap = Map<string, string | IChatSessionProviderOptionItem>;
620 >
621 > export namespace ChatSessionOptionsMap {
622 > export function fromRecord(obj: { [key: string]: string | IChatSessionProviderOptionItem }): ChatSessionOptionsMap {
623 return new Map(Object.entries(obj));
624 }
626 > export function toRecord(map: ReadonlyChatSessionOptionsMap): Record<string, string | IChatSessionProviderOptionItem> {
627 const record: Record<string, string | IChatSessionProviderOptionItem> = Object.create(null);
628 const entries = ensureIterable(map);
632 return record;
633 }
635 > export function toStrValueArray(map: ReadonlyChatSessionOptionsMap | undefined): Array<{ optionId: string; value: string }> | undefined {
636 if (!map) {
637 return undefined;
640 return Array.from(entries, ([optionId, value]) => ({ optionId, value: typeof value === 'string' ? value : value.id }));
641 }
643 > /**
644 > * Ensures the input is iterable. If a plain object is passed (e.g. due to
645 > * serialization across process boundaries losing the Map prototype), it is
646 > * converted to Map entries on the fly.
647 > */
648 > function ensureIterable(map: ReadonlyChatSessionOptionsMap): Iterable<[string, string | IChatSessionProviderOptionItem]> {
649 if (map instanceof Map) {
650 return map;
653 return Object.entries(map as unknown as Record<string, string | IChatSessionProviderOptionItem>);
654 }
656 >
657 > /**
658 > * Readonly version of {@link ChatSessionOptionsMap}
659 > */
660 > export type ReadonlyChatSessionOptionsMap = ReadonlyMap<string, string | IChatSessionProviderOptionItem>;
661 >
662 > export interface IChatSessionCustomizationItem {
663 > readonly label: string;
664 > readonly description?: string;
665 > readonly uri: URI;
666 > readonly storageLocation: number;
667 > readonly icon?: ThemeIcon;
668 > }
669 >
670 > export interface IChatSessionCustomizationItemGroup {
671 > readonly id: string;
672 > readonly items: IChatSessionCustomizationItem[];
673 > readonly commands?: readonly { readonly id: string; readonly title: string; readonly arguments?: readonly unknown[] }[];
674 > readonly itemCommands?: readonly { readonly id: string; readonly title: string; readonly arguments?: readonly unknown[] }[];
675 > }
676 >
677 > export interface IChatSessionCustomizationsProvider {
678 > readonly onDidChangeCustomizations: Event<void>;
679 > provideCustomizations(token: CancellationToken): Promise<IChatSessionCustomizationItemGroup[] | undefined>;
680 > }
681 >
682 >
683 > export interface IChatSessionCommitEvent {
684 > /** The original (untitled) session resource. */
685 > readonly original: URI;
686 > /** The committed (real) session resource. */
687 > readonly committed: URI;
688 > }
689 >
690 > export const IChatSessionsService = createDecorator<IChatSessionsService>('chatSessionsService');
691 >
692 > export interface IChatSessionsService {
693 > readonly _serviceBrand: undefined;
694 >
695 > // #region Chat session item provider support
696 > readonly onDidChangeItemsProviders: Event<{ readonly chatSessionType: string }>;
697 > readonly onDidChangeSessionItems: Event<IChatSessionItemsDelta>;
698 >
699 > /**
700 > * Fired when an untitled session is committed (URI swapped to a real resource)
701 > * after the first turn completes.
702 > */
703 > readonly onDidCommitSession: Event<IChatSessionCommitEvent>;
704 >
705 > readonly onDidChangeAvailability: Event<void>;
706 > readonly onDidChangeInProgress: Event<void>;
707 >
708 > getChatSessionContribution(chatSessionType: string): ResolvedChatSessionsExtensionPoint | undefined;
709 > getAllChatSessionContributions(): ResolvedChatSessionsExtensionPoint[];
710 >
711 > /**
712 > * Programmatically register a chat session contribution (for internal session types
713 > * that don't go through the extension point).
714 > */
715 > registerChatSessionContribution(contribution: IChatSessionsExtensionPoint): IDisposable;
716 >
717 > registerChatSessionItemController(chatSessionType: string, controller: IChatSessionItemController): IDisposable;
718 > getRegisteredChatSessionItemProviders(): readonly string[];
719 > activateChatSessionItemProvider(chatSessionType: string): Promise<void>;
720 >
721 > /**
722 > * Get the list of current chat session items grouped by session type.
723 > *
724 > * @param providerTypeFilter If specified, only returns items from the given providers. If undefined, returns items from all providers.
725 > *
726 > * @returns An async iterable that produces the list of session items for each provider. The order is not guaranteed. Some provider may take a long time to resolve.
727 > */
728 > getChatSessionItems(providerTypeFilter: readonly string[] | undefined, token: CancellationToken): AsyncIterable<{ readonly chatSessionType: string; readonly items: readonly IChatSessionItem[] }>;
729 >
730 > /**
731 > * Forces the controllers to refresh their session items, optionally filtered by provider type.
732 > */
733 > refreshChatSessionItems(providerTypeFilter: readonly string[] | undefined, token: CancellationToken): Promise<void>;
734 >
735 > /** @deprecated Use `getChatSessionItems` */
736 > getInProgress(): { chatSessionType: string; count: number }[];
737 >
738 > /**
739 > * Lazily resolves a chat session item, filling in expensive details like timing, changes, and badge.
740 > * Returns the resolved item, or undefined if no resolve handler is available.
741 > */
742 > resolveChatSessionItem(chatSessionType: string, resource: URI, token: CancellationToken): Promise<IChatSessionItem | undefined>;
743 >
744 > /**
745 > * Whether the registered item controller owns archived state for the session.
746 > */
747 > canSetChatSessionItemArchived(sessionResource: URI): boolean;
748 >
749 > /**
750 > * Sets archived state by delegating to the registered item controller.
751 > */
752 > setChatSessionItemArchived(sessionResource: URI, archived: boolean): void;
753 >
754 > // #endregion
755 >
756 > // #region Content provider support
757 > readonly onDidChangeContentProviderSchemes: Event<{ readonly added: string[]; readonly removed: string[] }>;
758 >
759 > getContentProviderSchemes(): string[];
760 >
761 > registerChatSessionContentProvider(scheme: string, provider: IChatSessionContentProvider): IDisposable;
762 > canResolveChatSession(sessionType: string): Promise<boolean>;
763 > getOrCreateChatSession(sessionResource: URI, token: CancellationToken): Promise<IChatSession>;
764 > /** Resolves a parsed response Markdown URI through its session content provider. */
765 > resolveChatResponseUri(sessionResource: URI, href: string, kind: 'link' | 'image'): string;
766 >
767 > /**
768 > * Compute completion items for an input being composed in the chat
769 > * session identified by `sessionResource`. Delegates to the registered
770 > * {@link IChatSessionContentProvider} for the session, if it implements
771 > * {@link IChatSessionContentProvider.provideChatInputCompletions}.
772 > * Returns `undefined` when no provider is available, in which case the
773 > * workbench's default in-process providers should be used.
774 > */
775 > provideChatInputCompletions(sessionResource: URI, params: IChatInputCompletionsParams, token: CancellationToken): Promise<IChatInputCompletionsResult | undefined>;
776 >
777 > /**
778 > * Trigger characters announced by the content provider for the given
779 > * session type. Used to dynamically register Monaco completion
780 > * providers per content-provider scheme. Returns `undefined` when the
781 > * scheme has no content provider, or `[]` when the provider does not
782 > * announce any trigger characters.
783 > */
784 > getChatInputCompletionTriggerCharacters(sessionType: string): Promise<readonly string[] | undefined>;
785 >
786 > getSessionOptions(sessionResource: URI): ReadonlyChatSessionOptionsMap | undefined;
787 > getSessionOption(sessionResource: URI, optionId: string): string | IChatSessionProviderOptionItem | undefined;
788 > setSessionOption(sessionResource: URI, optionId: string, value: string | IChatSessionProviderOptionItem): boolean;
789 > updateSessionOptions(sessionResource: URI, updates: ReadonlyChatSessionOptionsMap): boolean;
790 >
791 > /**
792 > * Fired when options for a chat session change.
793 > */
794 > readonly onDidChangeSessionOptions: Event<IChatSessionOptionsChangeEvent>;
795 >
796 > /**
797 > * Get the capabilities for a specific session type
798 > */
799 > getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined;
800 >
801 > /**
802 > * Get the customAgentTarget for a specific session type.
803 > * When the Target is not `Target.Undefined`, the mode picker should show filtered custom agents matching this target.
804 > */
805 > getCustomAgentTargetForSessionType(chatSessionType: string): Target;
806 >
807 > /**
808 > * Returns whether the session type requires custom models. When true, the model picker should show filtered custom models.
809 > */
810 > requiresCustomModelsForSessionType(chatSessionType: string): boolean;
811 >
812 > /**
813 > * Returns whether the session type supports the synthetic "Auto" model
814 > * fallback. The built-in local chat always supports it; contributed session
815 > * types default to `false` unless they set `supportsAutoModel`. When false
816 > * and no models are available, the picker shows a "No models available"
817 > * state instead of "Auto".
818 > */
819 > supportsAutoModelForSessionType(chatSessionType: string): boolean;
820 >
821 > /**
822 > * Whether the session type needs a Copilot account and so is unusable until the user signs in (BYOK isn't
823 > * supported). Defaults to false, so third-party types stay usable while signed out.
824 > */
825 > requiresCopilotSignInForSessionType(chatSessionType: string): boolean;
826 >
827 > /**
828 > * Returns whether the session type supports delegation.
829 > * Defaults to true when not explicitly set.
830 > */
831 > supportsDelegationForSessionType(chatSessionType: string): boolean;
832 >
833 > /**
834 > * Returns whether the loaded session supports forking conversations.
835 > */
836 > sessionSupportsFork(sessionResource: URI): boolean;
837 >
838 > /**
839 > * Forks a contributed chat session from the given request point.
840 > * @param sessionResource The session resource to fork.
841 > * @param request The request history item to fork from, or undefined to fork from the end.
842 > * @param token Cancellation token.
843 > * @returns The forked session item, or undefined if forking failed.
844 > */
845 > forkChatSession(sessionResource: URI, request: IChatSessionRequestHistoryItem | undefined, token: CancellationToken): Promise<IChatSessionItem>;
846 >
847 > /**
848 > * Returns whether the loaded session supports renaming.
849 > */
850 > sessionSupportsRename(sessionResource: URI): boolean;
851 >
852 > /**
853 > * Renames a contributed chat session.
854 > * @param sessionResource The session resource to rename.
855 > * @param title The new title for the session.
856 > * @param token Cancellation token.
857 > */
858 > renameChatSession(sessionResource: URI, title: string, token: CancellationToken): Promise<void>;
859 >
860 > readonly onDidChangeOptionGroups: Event<string>;
861 >
862 > getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined;
863 > setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: readonly IChatSessionProviderOptionGroup[]): void;
864 >
865 > /**
866 > * Get the default options for new sessions of this type, derived from option groups'
867 > * `selected` or `default` items.
868 > */
869 > getNewChatSessionInputState(chatSessionType: string, sessionResource: URI): Promise<readonly IChatSessionProviderOptionGroup[] | undefined>;
870 >
871 > /**
872 > * Creates a new chat session item using the controller's newChatSessionItemHandler.
873 > * Returns undefined if the controller doesn't have a handler or if no controller is registered.
874 > */
875 > createNewChatSessionItem(chatSessionType: string, request: IChatNewSessionRequest, token: CancellationToken): Promise<IChatSessionItem | undefined>;
876 >
877 > /**
878 > * Permanently deletes a chat session item by delegating to the registered controller's `deleteChatSessionItem`
879 > * handler. Throws if the controller does not implement `deleteChatSessionItem`.
880 > */
881 > deleteChatSessionItem(sessionResource: URI, token: CancellationToken): Promise<void>;
882 >
883 > /**
884 > * Records the inverse `real → untitled` alias so option lookups for the real
885 > * session resolve to the untitled session's entry (e.g. {@link updateSessionOptions}).
886 > *
887 > * Call this BEFORE the real session loads, and never remove it — the real
888 > * session keeps reading its options through this alias even after the untitled
889 > * model is disposed. (Only the forward mapping is cleared, via
890 > * {@link clearMaterializedSessionResource}.) Publishing the forward mapping is a
891 > * separate step; see {@link setMaterializedSessionResource}.
892 > */
893 > registerSessionResourceAlias(untitledResource: URI, realResource: URI): void;
894 >
895 > /**
896 > * Records the forward `untitled → real` mapping (read via
897 > * {@link getMaterializedSessionResource}) so a late send still addressed to the
898 > * untitled resource re-targets the real session. Call this only AFTER the real
899 > * session has loaded.
900 > *
901 > * Kept separate from {@link registerSessionResourceAlias} on purpose: the
902 > * inverse alias must exist BEFORE the load (for option lookups), but this
903 > * forward mapping must appear only AFTER the real session exists — published
904 > * earlier, a failed or still-loading session would be re-targeted before it
905 > * exists (a later send would throw "Unknown session").
906 > */
907 > setMaterializedSessionResource(untitledResource: URI, realResource: URI): void;
908 >
909 > /**
910 > * Returns the real session resource that `untitledResource` materialized
911 > * into (via {@link setMaterializedSessionResource}), or `undefined` if it has
912 > * not materialized or the mapping was already cleared.
913 > */
914 > getMaterializedSessionResource(untitledResource: URI): URI | undefined;
915 >
916 > /**
917 > * Clears the forward `untitled → real` mapping for `sessionResource` (passed
918 > * either the untitled key or the real value), so {@link getMaterializedSessionResource}
919 > * stops re-targeting once the session is disposed. Does NOT remove the inverse
920 > * alias, which is intentionally permanent (see {@link registerSessionResourceAlias}).
921 > */
922 > clearMaterializedSessionResource(sessionResource: URI): void;
923 >
924 > /**
925 > * Fires {@link onDidCommitSession} to notify listeners that an untitled
926 > * session has been committed with a real resource URI.
927 > */
928 > fireSessionCommitted(original: URI, committed: URI): void;
929 >
930 > // #region Customizations provider support
931 > readonly onDidChangeCustomizations: Event<{ readonly chatSessionType: string }>;
932 > registerCustomizationsProvider(chatSessionType: string, provider: IChatSessionCustomizationsProvider): IDisposable;
933 > hasCustomizationsProvider(chatSessionType: string): boolean;
934 > getCustomizations(chatSessionType: string, token: CancellationToken): Promise<IChatSessionCustomizationItemGroup[] | undefined>;
935 > // #endregion
936 > }
937 >
938 > export function isSessionInProgressStatus(state: ChatSessionStatus): boolean {
939 return state === ChatSessionStatus.InProgress || state === ChatSessionStatus.NeedsInput;
940 }
942 > export function isIChatSessionFileChange2(obj: unknown): obj is IChatSessionFileChange2 {
943 const candidate = obj as IChatSessionFileChange2;
944 return candidate && candidate.uri instanceof URI && typeof candidate.insertions === 'number' && typeof candidate.deletions === 'number';
src/vs/platform/agentHost/common/agentHostConnectionsService.ts 126 introduced LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentHostConnectionsService.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 { Event } from '../../../base/common/event.js';
7 > import type { URI } from '../../../base/common/uri.js';
8 > import { createDecorator } from '../../instantiation/common/instantiation.js';
9 > import type { IAgentConnection } from './agentService.js';
10 >
11 > /**
12 > * Chat-session resource scheme prefix for the window's ambient/local agent
13 > * host: `agent-host-<provider>`.
14 > *
15 > * Remote agent host session schemes (`remote-<authority>-<provider>`) are
16 > * handled by `agentHostSessionType.ts` (`isRemoteAgentHostSessionType` and
17 > * friends), which also owns the authority disambiguation.
18 > */
19 > export const LOCAL_AGENT_HOST_SCHEME_PREFIX = 'agent-host-';
20 >
21 > /**
22 > * Reserved connection authority for the window's ambient/primary agent host.
23 > *
24 > * NOTE: "ambient" is not the same as "local". In a local window the ambient
25 > * host is the in-process utility agent host; in a window attached to a remote
26 > * authority the ambient host is itself remote (the `EditorRemoteAgentHostServiceClient`).
27 > * The `'local'` string is reserved/canonical for this ambient connection — it
28 > * is already used as the agent-host URI authority for in-window resources
29 > * (see `toAgentHostUri` in `agentHostUri.ts`).
30 > */
31 > export const AMBIENT_AGENT_HOST_AUTHORITY = 'local';
32 >
33 > /**
34 > * A descriptor for a single agent-host connection exposed by
35 > * {@link IAgentHostConnectionsService}. Covers both the window's ambient host
36 > * and every connected remote host with one uniform shape.
37 > */
38 > export interface IAgentHostConnectionInfo {
39 > /**
40 > * Sanitized connection authority. The ambient host uses
41 > * {@link AMBIENT_AGENT_HOST_AUTHORITY}; remotes use the authority derived
42 > * from their address via `agentHostAuthority`.
43 > */
44 > readonly authority: string;
45 > /** Raw remote address. `undefined` for the ambient host. */
46 > readonly address: string | undefined;
47 > /** Human-readable label for the connection. */
48 > readonly name: string;
49 > /**
50 > * `true` for the window's ambient/primary host. Remember this host may
51 > * itself be remote in a remote window — see {@link AMBIENT_AGENT_HOST_AUTHORITY}.
52 > */
53 > readonly isAmbient: boolean;
54 > /** The live connection, or `undefined` when not currently connected. */
55 > readonly connection: IAgentConnection | undefined;
56 > }
57 >
58 > /**
59 > * The result of resolving a chat-session resource to its backing agent host:
60 > * the owning {@link IAgentConnection} and the canonical backend agent-session
61 > * URI (`<provider>:/<rawId>`) used for protocol operations on that connection.
62 > */
63 > export interface IAgentHostSessionResolution {
64 > readonly connection: IAgentConnection;
65 > readonly backendSession: URI;
66 > }
67 >
68 > export const IAgentHostConnectionsService = createDecorator<IAgentHostConnectionsService>('agentHostConnectionsService');
69 >
70 > /**
71 > * A thin, read-only facade over the window's ambient agent host
72 > * (`IAgentHostService`) and the registry of remote agent hosts
73 > * (`IRemoteAgentHostService`), so consumers can enumerate and resolve
74 > * {@link IAgentConnection}s without branching on local-vs-remote or
75 > * fanning out over "1 ambient + N remote" themselves.
76 > *
77 > * This service deliberately does NOT expose lifecycle/management operations:
78 > * ambient-process concerns (restart, inspect, auth-pending) stay on
79 > * `IAgentHostService`, and remote-registry mutations (add/remove/reconnect/
80 > * upgrade) stay on `IRemoteAgentHostService`. This facade only answers
81 > * "which connections exist?" and "give me the connection for X".
82 > */
83 > export interface IAgentHostConnectionsService {
84 > readonly _serviceBrand: undefined;
85 >
86 > /** Fires when the set of connections changes (ambient lifecycle or remotes added/removed). */
87 > readonly onDidChangeConnections: Event<void>;
88 >
89 > /**
90 > * All known connections as `[ambient, ...remotes]`. The ambient entry is
91 > * always present with a live `connection`; only remote entries may have
92 > * `connection: undefined` (e.g. while connecting/disconnected). Remote
93 > * entries reflect the current `IRemoteAgentHostService` registry.
94 > */
95 > readonly connections: readonly IAgentHostConnectionInfo[];
96 >
97 > /** The window's ambient/primary connection (local, or the window-remote bridge). */
98 > readonly ambientConnection: IAgentConnection;
99 >
100 > /**
101 > * Resolves a live connection by sanitized authority, including
102 > * {@link AMBIENT_AGENT_HOST_AUTHORITY} for the ambient host. Returns
103 > * `undefined` when no connected host matches.
104 > */
105 > getConnectionByAuthority(authority: string): IAgentConnection | undefined;
106 >
107 > /**
108 > * Resolves a live remote connection by raw address. The ambient host has no
109 > * address and is never returned here — use {@link ambientConnection} or
110 > * {@link getConnectionByAuthority} with {@link AMBIENT_AGENT_HOST_AUTHORITY}.
111 > */
112 > getConnectionByAddress(address: string): IAgentConnection | undefined;
113 >
114 > /**
115 > * Resolves an agent-host chat-session resource to its owning connection and
116 > * backend session URI. Handles both local schemes
117 > * (`agent-host-<provider>`) — backed by the ambient connection — and remote
118 > * schemes (`remote-<authority>-<provider>`) — resolved against the live
119 > * remote registry. Returns `undefined` when the resource is not an
120 > * agent-host session, or when the matching remote host is not connected.
121 > *
122 > * NOTE: provisional/untitled sessions are a workbench concern and are NOT
123 > * handled here — callers that support them should resolve those first.
124 > */
125 > resolveSessionResource(sessionResource: URI): IAgentHostSessionResolution | undefined;
126 > }