Path: blob/main/src/vs/workbench/contrib/chat/common/chatService/chatService.ts
5257 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IAction } from '../../../../../base/common/actions.js';6import { DeferredPromise } from '../../../../../base/common/async.js';7import { CancellationToken } from '../../../../../base/common/cancellation.js';8import { Event } from '../../../../../base/common/event.js';9import { IMarkdownString } from '../../../../../base/common/htmlContent.js';10import { DisposableStore, IReference } from '../../../../../base/common/lifecycle.js';11import { autorun, autorunSelfDisposable, IObservable, IReader } from '../../../../../base/common/observable.js';12import { ThemeIcon } from '../../../../../base/common/themables.js';13import { hasKey } from '../../../../../base/common/types.js';14import { URI, UriComponents } from '../../../../../base/common/uri.js';15import { IRange, Range } from '../../../../../editor/common/core/range.js';16import { HookTypeValue } from '../promptSyntax/hookSchema.js';17import { ISelection } from '../../../../../editor/common/core/selection.js';18import { Command, Location, TextEdit } from '../../../../../editor/common/languages.js';19import { FileType } from '../../../../../platform/files/common/files.js';20import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';21import { IAutostartResult } from '../../../mcp/common/mcpTypes.js';22import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js';23import { IWorkspaceSymbol } from '../../../search/common/search.js';24import { IChatAgentCommand, IChatAgentData, IChatAgentResult, UserSelectedTools } from '../participants/chatAgents.js';25import { IChatEditingSession } from '../editing/chatEditingService.js';26import { IChatModel, IChatRequestModeInfo, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData } from '../model/chatModel.js';27import { IParsedChatRequest } from '../requestParser/chatParserTypes.js';28import { IChatParserContext } from '../requestParser/chatRequestParser.js';29import { IChatRequestVariableEntry } from '../attachments/chatVariableEntries.js';30import { IChatRequestVariableValue } from '../attachments/chatVariables.js';31import { ChatAgentLocation } from '../constants.js';32import { IPreparedToolInvocation, IToolConfirmationMessages, IToolResult, IToolResultInputOutputDetails, ToolDataSource } from '../tools/languageModelToolsService.js';3334export interface IChatRequest {35message: string;36variables: Record<string, IChatRequestVariableValue[]>;37}3839export enum ChatErrorLevel {40Info = 0,41Warning = 1,42Error = 243}4445export interface IChatResponseErrorDetailsConfirmationButton {46// eslint-disable-next-line @typescript-eslint/no-explicit-any47data: any;48label: string;49isSecondary?: boolean;50}5152export interface IChatResponseErrorDetails {53message: string;54responseIsIncomplete?: boolean;55responseIsFiltered?: boolean;56responseIsRedacted?: boolean;57isQuotaExceeded?: boolean;58isRateLimited?: boolean;59level?: ChatErrorLevel;60confirmationButtons?: IChatResponseErrorDetailsConfirmationButton[];61code?: string;62}6364export interface IChatResponseProgressFileTreeData {65label: string;66uri: URI;67type?: FileType;68children?: IChatResponseProgressFileTreeData[];69}7071export type IDocumentContext = {72uri: URI;73version: number;74ranges: IRange[];75};7677export function isIDocumentContext(obj: unknown): obj is IDocumentContext {78return (79!!obj &&80typeof obj === 'object' &&81'uri' in obj && obj.uri instanceof URI &&82'version' in obj && typeof obj.version === 'number' &&83'ranges' in obj && Array.isArray(obj.ranges) && obj.ranges.every(Range.isIRange)84);85}8687export interface IChatUsedContext {88documents: IDocumentContext[];89kind: 'usedContext';90}9192export function isIUsedContext(obj: unknown): obj is IChatUsedContext {93return (94!!obj &&95typeof obj === 'object' &&96'documents' in obj &&97Array.isArray(obj.documents) &&98obj.documents.every(isIDocumentContext)99);100}101102export interface IChatContentVariableReference {103variableName: string;104value?: URI | Location;105}106107export function isChatContentVariableReference(obj: unknown): obj is IChatContentVariableReference {108return !!obj &&109typeof obj === 'object' &&110typeof (obj as IChatContentVariableReference).variableName === 'string';111}112113export enum ChatResponseReferencePartStatusKind {114Complete = 1,115Partial = 2,116Omitted = 3117}118119export enum ChatResponseClearToPreviousToolInvocationReason {120NoReason = 0,121FilteredContentRetry = 1,122CopyrightContentRetry = 2,123}124125export interface IChatContentReference {126reference: URI | Location | IChatContentVariableReference | string;127iconPath?: ThemeIcon | { light: URI; dark?: URI };128options?: {129status?: { description: string; kind: ChatResponseReferencePartStatusKind };130diffMeta?: { added: number; removed: number };131originalUri?: URI;132isDeletion?: boolean;133};134kind: 'reference';135}136137export interface IChatCodeCitation {138value: URI;139license: string;140snippet: string;141kind: 'codeCitation';142}143144export interface IChatUsagePromptTokenDetail {145category: string;146label: string;147percentageOfPrompt: number;148}149150export interface IChatUsage {151promptTokens: number;152completionTokens: number;153promptTokenDetails?: readonly IChatUsagePromptTokenDetail[];154kind: 'usage';155}156157export interface IChatContentInlineReference {158resolveId?: string;159inlineReference: URI | Location | IWorkspaceSymbol;160name?: string;161kind: 'inlineReference';162}163164export interface IChatMarkdownContent {165kind: 'markdownContent';166content: IMarkdownString;167inlineReferences?: Record<string, IChatContentInlineReference>;168}169170export interface IChatTreeData {171treeData: IChatResponseProgressFileTreeData;172kind: 'treeData';173}174export interface IMultiDiffResource {175originalUri?: URI;176modifiedUri?: URI;177goToFileUri?: URI;178added?: number;179removed?: number;180}181182export interface IChatMultiDiffInnerData {183title: string;184resources: IMultiDiffResource[];185}186187export interface IChatMultiDiffData {188multiDiffData: IChatMultiDiffInnerData | IObservable<IChatMultiDiffInnerData>;189kind: 'multiDiffData';190collapsed?: boolean;191readOnly?: boolean;192toJSON(): IChatMultiDiffDataSerialized;193}194195export interface IChatMultiDiffDataSerialized {196multiDiffData: IChatMultiDiffInnerData;197kind: 'multiDiffData';198collapsed?: boolean;199readOnly?: boolean;200}201202export class ChatMultiDiffData implements IChatMultiDiffData {203public readonly kind = 'multiDiffData';204public readonly collapsed?: boolean | undefined;205public readonly readOnly?: boolean | undefined;206public readonly multiDiffData: IChatMultiDiffData['multiDiffData'];207208constructor(opts: {209multiDiffData: IChatMultiDiffInnerData | IObservable<IChatMultiDiffInnerData>;210collapsed?: boolean;211readOnly?: boolean;212}) {213this.readOnly = opts.readOnly;214this.collapsed = opts.collapsed;215this.multiDiffData = opts.multiDiffData;216}217218toJSON(): IChatMultiDiffDataSerialized {219return {220kind: this.kind,221multiDiffData: hasKey(this.multiDiffData, { title: true }) ? this.multiDiffData : this.multiDiffData.get(),222collapsed: this.collapsed,223readOnly: this.readOnly,224};225}226}227228export interface IChatProgressMessage {229content: IMarkdownString;230kind: 'progressMessage';231}232233export interface IChatTask extends IChatTaskDto {234deferred: DeferredPromise<string | void>;235progress: (IChatWarningMessage | IChatContentReference)[];236readonly onDidAddProgress: Event<IChatWarningMessage | IChatContentReference>;237add(progress: IChatWarningMessage | IChatContentReference): void;238239complete: (result: string | void) => void;240task: () => Promise<string | void>;241isSettled: () => boolean;242toJSON(): IChatTaskSerialized;243}244245export interface IChatUndoStop {246kind: 'undoStop';247id: string;248}249250export interface IChatExternalEditsDto {251kind: 'externalEdits';252undoStopId: string;253start: boolean; /** true=start, false=stop */254resources: UriComponents[];255}256257export interface IChatTaskDto {258content: IMarkdownString;259kind: 'progressTask';260}261262export interface IChatTaskSerialized {263content: IMarkdownString;264progress: (IChatWarningMessage | IChatContentReference)[];265kind: 'progressTaskSerialized';266}267268export interface IChatTaskResult {269content: IMarkdownString | void;270kind: 'progressTaskResult';271}272273export interface IChatWarningMessage {274content: IMarkdownString;275kind: 'warning';276}277278export interface IChatAgentVulnerabilityDetails {279title: string;280description: string;281}282283export interface IChatResponseCodeblockUriPart {284kind: 'codeblockUri';285uri: URI;286isEdit?: boolean;287undoStopId?: string;288subAgentInvocationId?: string;289}290291export interface IChatAgentMarkdownContentWithVulnerability {292content: IMarkdownString;293vulnerabilities: IChatAgentVulnerabilityDetails[];294kind: 'markdownVuln';295}296297export interface IChatCommandButton {298command: Command;299kind: 'command';300additionalCommands?: Command[]; // rendered as secondary buttons301}302303export interface IChatMoveMessage {304uri: URI;305range: IRange;306kind: 'move';307}308309export interface IChatTextEdit {310uri: URI;311edits: TextEdit[];312kind: 'textEdit';313done?: boolean;314isExternalEdit?: boolean;315}316317export interface IChatClearToPreviousToolInvocation {318kind: 'clearToPreviousToolInvocation';319reason: ChatResponseClearToPreviousToolInvocationReason;320}321322export interface IChatNotebookEdit {323uri: URI;324edits: ICellEditOperation[];325kind: 'notebookEdit';326done?: boolean;327isExternalEdit?: boolean;328}329330export interface IChatWorkspaceFileEdit {331oldResource?: URI;332newResource?: URI;333}334335export interface IChatWorkspaceEdit {336kind: 'workspaceEdit';337edits: IChatWorkspaceFileEdit[];338}339340export interface IChatConfirmation {341title: string;342message: string | IMarkdownString;343// eslint-disable-next-line @typescript-eslint/no-explicit-any344data: any;345buttons?: string[];346isUsed?: boolean;347kind: 'confirmation';348}349350/**351* Represents an individual question in a question carousel.352*/353export interface IChatQuestion {354id: string;355type: 'text' | 'singleSelect' | 'multiSelect';356title: string;357message?: string | IMarkdownString;358options?: { id: string; label: string; value: unknown }[];359defaultValue?: string | string[];360allowFreeformInput?: boolean;361}362363/**364* A carousel for presenting multiple questions inline in the chat response.365* Users can navigate between questions and submit their answers.366*/367export interface IChatQuestionCarousel {368questions: IChatQuestion[];369allowSkip: boolean;370/** Unique identifier for resolving the carousel answers back to the extension */371resolveId?: string;372/** Storage for collected answers when user submits */373data?: Record<string, unknown>;374/** Whether the carousel has been submitted/skipped */375isUsed?: boolean;376kind: 'questionCarousel';377}378379export const enum ElicitationState {380Pending = 'pending',381Accepted = 'accepted',382Rejected = 'rejected',383}384385export interface IChatElicitationRequest {386kind: 'elicitation2'; // '2' because initially serialized data used the same kind387title: string | IMarkdownString;388message: string | IMarkdownString;389acceptButtonLabel: string;390rejectButtonLabel: string | undefined;391subtitle?: string | IMarkdownString;392source?: ToolDataSource;393state: IObservable<ElicitationState>;394acceptedResult?: Record<string, unknown>;395moreActions?: IAction[];396accept(value: IAction | true): Promise<void>;397reject?: () => Promise<void>;398isHidden?: IObservable<boolean>;399hide?(): void;400toJSON(): IChatElicitationRequestSerialized;401}402403export interface IChatElicitationRequestSerialized {404kind: 'elicitationSerialized';405title: string | IMarkdownString;406message: string | IMarkdownString;407subtitle: string | IMarkdownString | undefined;408source: ToolDataSource | undefined;409state: ElicitationState.Accepted | ElicitationState.Rejected;410isHidden: boolean;411acceptedResult?: Record<string, unknown>;412}413414export interface IChatThinkingPart {415kind: 'thinking';416value?: string | string[];417id?: string;418// eslint-disable-next-line @typescript-eslint/no-explicit-any419metadata?: { readonly [key: string]: any };420generatedTitle?: string;421}422423/**424* A progress part representing the execution result of a hook.425* Aligned with the hook output JSON structure: { stopReason, systemMessage, hookSpecificOutput }.426* If {@link stopReason} is set, the hook blocked/denied the operation.427*/428export interface IChatHookPart {429kind: 'hook';430/** The type of hook that was executed */431hookType: HookTypeValue;432/** If set, the hook blocked processing. This message is shown to the user. */433stopReason?: string;434/** Warning/system message from the hook, shown to the user */435systemMessage?: string;436metadata?: { readonly [key: string]: unknown };437}438439export interface IChatTerminalToolInvocationData {440kind: 'terminal';441commandLine: {442original: string;443userEdited?: string;444toolEdited?: string;445// command to show in the chat UI (potentially different from what is actually run in the terminal)446forDisplay?: string;447};448/** The working directory URI for the terminal */449cwd?: UriComponents;450/**451* Pre-computed confirmation display data (localization must happen at source).452* Contains the command line to show in confirmation (potentially without cd prefix)453* and the formatted cwd label if a cd prefix was extracted.454*/455confirmation?: {456/** The command line to display in the confirmation editor */457commandLine: string;458/** The formatted cwd label to show in title (if cd was extracted) */459cwdLabel?: string;460/** The cd prefix to prepend back when user edits */461cdPrefix?: string;462};463/**464* Overrides to apply to the presentation of the tool call only, but not actually change the465* command that gets run. For example, python -c "print('hello')" can be presented as just466* the Python code with Python syntax highlighting.467*/468presentationOverrides?: {469/** The command line to display in the UI */470commandLine: string;471/** The language for syntax highlighting */472language?: string;473};474/** Message for model recommending the use of an alternative tool */475alternativeRecommendation?: string;476language: string;477terminalToolSessionId?: string;478/** The predefined command ID that will be used for this terminal command */479terminalCommandId?: string;480/** Whether the terminal command was started as a background execution */481isBackground?: boolean;482/** Serialized URI for the command that was executed in the terminal */483terminalCommandUri?: UriComponents;484/** Serialized output of the executed command */485terminalCommandOutput?: {486text: string;487truncated?: boolean;488lineCount?: number;489};490/** Stored theme colors at execution time to style detached output */491terminalTheme?: {492background?: string;493foreground?: string;494};495/** Stored command state to restore decorations after reload */496terminalCommandState?: {497exitCode?: number;498timestamp?: number;499duration?: number;500};501/** Whether the user chose to continue in background for this tool invocation */502didContinueInBackground?: boolean;503autoApproveInfo?: IMarkdownString;504}505506/**507* @deprecated This is the old API shape, we should support this for a while before removing it so508* we don't break existing chats509*/510export interface ILegacyChatTerminalToolInvocationData {511kind: 'terminal';512command: string;513language: string;514}515516export function isLegacyChatTerminalToolInvocationData(data: unknown): data is ILegacyChatTerminalToolInvocationData {517return !!data && typeof data === 'object' && 'command' in data && 'language' in data;518}519520export interface IChatToolInputInvocationData {521kind: 'input';522// eslint-disable-next-line @typescript-eslint/no-explicit-any523rawInput: any;524/** Optional MCP App UI metadata for rendering during and after tool execution */525mcpAppData?: {526/** URI of the UI resource for rendering (e.g., "ui://weather-server/dashboard") */527resourceUri: string;528/** Reference to the server definition for reconnection */529serverDefinitionId: string;530/** Reference to the collection containing the server */531collectionId: string;532};533}534535export const enum ToolConfirmKind {536Denied,537ConfirmationNotNeeded,538Setting,539LmServicePerTool,540UserAction,541Skipped542}543544export type ConfirmedReason =545| { type: ToolConfirmKind.Denied }546| { type: ToolConfirmKind.ConfirmationNotNeeded; reason?: string | IMarkdownString }547| { type: ToolConfirmKind.Setting; id: string }548| { type: ToolConfirmKind.LmServicePerTool; scope: 'session' | 'workspace' | 'profile' }549| { type: ToolConfirmKind.UserAction; selectedButton?: string }550| { type: ToolConfirmKind.Skipped };551552export interface IChatToolInvocation {553readonly presentation: IPreparedToolInvocation['presentation'];554readonly toolSpecificData?: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatPullRequestContent | IChatTodoListContent | IChatSubagentToolInvocationData | IChatSimpleToolInvocationData | IChatToolResourcesInvocationData;555readonly originMessage: string | IMarkdownString | undefined;556readonly invocationMessage: string | IMarkdownString;557readonly pastTenseMessage: string | IMarkdownString | undefined;558readonly source: ToolDataSource;559readonly toolId: string;560readonly toolCallId: string;561readonly subAgentInvocationId?: string;562readonly state: IObservable<IChatToolInvocation.State>;563generatedTitle?: string;564565kind: 'toolInvocation';566567toJSON(): IChatToolInvocationSerialized;568}569570export namespace IChatToolInvocation {571export const enum StateKind {572/** Tool call is streaming partial input from the LM */573Streaming,574WaitingForConfirmation,575Executing,576WaitingForPostApproval,577Completed,578Cancelled,579}580581interface IChatToolInvocationStateBase {582type: StateKind;583}584585export interface IChatToolInvocationStreamingState extends IChatToolInvocationStateBase {586type: StateKind.Streaming;587/** Observable partial input from the LM stream */588readonly partialInput: IObservable<unknown>;589/** Custom invocation message from handleToolStream */590readonly streamingMessage: IObservable<string | IMarkdownString | undefined>;591}592593/** Properties available after streaming is complete */594interface IChatToolInvocationPostStreamState {595readonly parameters: unknown;596readonly confirmationMessages?: IToolConfirmationMessages;597}598599interface IChatToolInvocationWaitingForConfirmationState extends IChatToolInvocationStateBase, IChatToolInvocationPostStreamState {600type: StateKind.WaitingForConfirmation;601confirm(reason: ConfirmedReason): void;602}603604interface IChatToolInvocationPostConfirmState extends IChatToolInvocationPostStreamState {605confirmed: ConfirmedReason;606}607608interface IChatToolInvocationExecutingState extends IChatToolInvocationStateBase, IChatToolInvocationPostConfirmState {609type: StateKind.Executing;610progress: IObservable<{ message?: string | IMarkdownString; progress: number | undefined }>;611}612613interface IChatToolInvocationPostExecuteState extends IChatToolInvocationPostConfirmState {614resultDetails: IToolResult['toolResultDetails'];615}616617interface IChatToolWaitingForPostApprovalState extends IChatToolInvocationStateBase, IChatToolInvocationPostExecuteState {618type: StateKind.WaitingForPostApproval;619confirm(reason: ConfirmedReason): void;620contentForModel: IToolResult['content'];621}622623interface IChatToolInvocationCompleteState extends IChatToolInvocationStateBase, IChatToolInvocationPostExecuteState {624type: StateKind.Completed;625postConfirmed: ConfirmedReason | undefined;626contentForModel: IToolResult['content'];627}628629interface IChatToolInvocationCancelledState extends IChatToolInvocationStateBase, IChatToolInvocationPostStreamState {630type: StateKind.Cancelled;631reason: ToolConfirmKind.Denied | ToolConfirmKind.Skipped;632/** Optional message explaining why the tool was cancelled (e.g., from hook denial) */633reasonMessage?: string | IMarkdownString;634}635636export type State =637| IChatToolInvocationStreamingState638| IChatToolInvocationWaitingForConfirmationState639| IChatToolInvocationExecutingState640| IChatToolWaitingForPostApprovalState641| IChatToolInvocationCompleteState642| IChatToolInvocationCancelledState;643644export function executionConfirmedOrDenied(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): ConfirmedReason | undefined {645if (invocation.kind === 'toolInvocationSerialized') {646if (invocation.isConfirmed === undefined || typeof invocation.isConfirmed === 'boolean') {647return { type: invocation.isConfirmed ? ToolConfirmKind.UserAction : ToolConfirmKind.Denied };648}649return invocation.isConfirmed;650}651652const state = invocation.state.read(reader);653if (state.type === StateKind.Streaming || state.type === StateKind.WaitingForConfirmation) {654return undefined; // don't know yet655}656if (state.type === StateKind.Cancelled) {657return { type: state.reason };658}659660return state.confirmed;661}662663export function awaitConfirmation(invocation: IChatToolInvocation, token?: CancellationToken): Promise<ConfirmedReason> {664const reason = executionConfirmedOrDenied(invocation);665if (reason) {666return Promise.resolve(reason);667}668669const store = new DisposableStore();670return new Promise<ConfirmedReason>(resolve => {671if (token) {672store.add(token.onCancellationRequested(() => {673resolve({ type: ToolConfirmKind.Denied });674}));675}676677store.add(autorun(reader => {678const reason = executionConfirmedOrDenied(invocation, reader);679if (reason) {680store.dispose();681resolve(reason);682}683}));684}).finally(() => {685store.dispose();686});687}688689function postApprovalConfirmedOrDenied(invocation: IChatToolInvocation, reader?: IReader): ConfirmedReason | undefined {690const state = invocation.state.read(reader);691if (state.type === StateKind.Completed) {692return state.postConfirmed || { type: ToolConfirmKind.ConfirmationNotNeeded };693}694if (state.type === StateKind.Cancelled) {695return { type: state.reason };696}697698return undefined;699}700701export function confirmWith(invocation: IChatToolInvocation | undefined, reason: ConfirmedReason) {702const state = invocation?.state.get();703if (state?.type === StateKind.WaitingForConfirmation || state?.type === StateKind.WaitingForPostApproval) {704state.confirm(reason);705return true;706}707return false;708}709710export function awaitPostConfirmation(invocation: IChatToolInvocation, token?: CancellationToken): Promise<ConfirmedReason> {711const reason = postApprovalConfirmedOrDenied(invocation);712if (reason) {713return Promise.resolve(reason);714}715716const store = new DisposableStore();717return new Promise<ConfirmedReason>(resolve => {718if (token) {719store.add(token.onCancellationRequested(() => {720resolve({ type: ToolConfirmKind.Denied });721}));722}723724store.add(autorun(reader => {725const reason = postApprovalConfirmedOrDenied(invocation, reader);726if (reason) {727store.dispose();728resolve(reason);729}730}));731}).finally(() => {732store.dispose();733});734}735736export function resultDetails(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader) {737if (invocation.kind === 'toolInvocationSerialized') {738return invocation.resultDetails;739}740741const state = invocation.state.read(reader);742if (state.type === StateKind.Completed || state.type === StateKind.WaitingForPostApproval) {743return state.resultDetails;744}745746return undefined;747}748749export function isComplete(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): boolean {750if (invocation.kind === 'toolInvocationSerialized') {751return true; // always cancelled or complete752}753754const state = invocation.state.read(reader);755return state.type === StateKind.Completed || state.type === StateKind.Cancelled;756}757758export function isStreaming(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): boolean {759if (invocation.kind === 'toolInvocationSerialized') {760return false;761}762763const state = invocation.state.read(reader);764return state.type === StateKind.Streaming;765}766767/**768* Get parameters from invocation. Returns undefined during streaming state.769*/770export function getParameters(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): unknown | undefined {771if (invocation.kind === 'toolInvocationSerialized') {772return undefined; // serialized invocations don't store parameters773}774775const state = invocation.state.read(reader);776if (state.type === StateKind.Streaming) {777return undefined;778}779780return state.parameters;781}782783/**784* Get confirmation messages from invocation. Returns undefined during streaming state.785*/786export function getConfirmationMessages(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): IToolConfirmationMessages | undefined {787if (invocation.kind === 'toolInvocationSerialized') {788return undefined; // serialized invocations don't store confirmation messages789}790791const state = invocation.state.read(reader);792if (state.type === StateKind.Streaming) {793return undefined;794}795796return state.confirmationMessages;797}798}799800801export interface IToolResultOutputDetailsSerialized {802output: {803type: 'data';804mimeType: string;805base64Data: string;806};807}808809/**810* This is a IChatToolInvocation that has been serialized, like after window reload, so it is no longer an active tool invocation.811*/812export interface IChatToolInvocationSerialized {813presentation: IPreparedToolInvocation['presentation'];814toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatPullRequestContent | IChatTodoListContent | IChatSubagentToolInvocationData | IChatSimpleToolInvocationData | IChatToolResourcesInvocationData;815invocationMessage: string | IMarkdownString;816originMessage: string | IMarkdownString | undefined;817pastTenseMessage: string | IMarkdownString | undefined;818resultDetails?: Array<URI | Location> | IToolResultInputOutputDetails | IToolResultOutputDetailsSerialized;819/** boolean used by pre-1.104 versions */820isConfirmed: ConfirmedReason | boolean | undefined;821isComplete: boolean;822toolCallId: string;823toolId: string;824source: ToolDataSource | undefined; // undefined on pre-1.104 versions825readonly subAgentInvocationId?: string;826generatedTitle?: string;827kind: 'toolInvocationSerialized';828}829830export interface IChatExtensionsContent {831extensions: string[];832kind: 'extensions';833}834835export interface IChatPullRequestContent {836/**837* @deprecated use `command` instead838*/839uri?: URI;840command: Command;841title: string;842description: string;843author: string;844linkTag: string;845kind: 'pullRequest';846}847848export interface IChatSubagentToolInvocationData {849kind: 'subagent';850description?: string;851agentName?: string;852prompt?: string;853result?: string;854modelName?: string;855}856857/**858* Progress type for external tool invocation updates from extensions.859* When isComplete is false, creates or updates a tool invocation.860* When isComplete is true, completes an existing tool invocation.861*/862export interface IChatExternalToolInvocationUpdate {863kind: 'externalToolInvocationUpdate';864toolCallId: string;865toolName: string;866isComplete: boolean;867errorMessage?: string;868invocationMessage?: string | IMarkdownString;869pastTenseMessage?: string | IMarkdownString;870toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatTodoListContent | IChatSubagentToolInvocationData;871subagentInvocationId?: string;872}873874export interface IChatTodoListContent {875kind: 'todoList';876todoList: Array<{877id: string;878title: string;879status: 'not-started' | 'in-progress' | 'completed';880}>;881}882883export interface IChatSimpleToolInvocationData {884kind: 'simpleToolInvocation';885input: string;886output: string;887}888889export interface IChatToolResourcesInvocationData {890readonly kind: 'resources';891readonly values: Array<URI | Location>;892}893894export interface IChatMcpServersStarting {895readonly kind: 'mcpServersStarting';896readonly state?: IObservable<IAutostartResult>; // not hydrated when serialized897didStartServerIds?: string[];898toJSON(): IChatMcpServersStartingSerialized;899}900901export interface IChatMcpServersStartingSerialized {902readonly kind: 'mcpServersStarting';903readonly state?: undefined;904didStartServerIds?: string[];905}906907export class ChatMcpServersStarting implements IChatMcpServersStarting {908public readonly kind = 'mcpServersStarting';909910public didStartServerIds?: string[] = [];911912public get isEmpty() {913const s = this.state.get();914return !s.working && s.serversRequiringInteraction.length === 0;915}916917constructor(public readonly state: IObservable<IAutostartResult>) { }918919wait() {920return new Promise<IAutostartResult>(resolve => {921autorunSelfDisposable(reader => {922const s = this.state.read(reader);923if (!s.working) {924reader.dispose();925resolve(s);926}927});928});929}930931toJSON(): IChatMcpServersStartingSerialized {932return { kind: 'mcpServersStarting', didStartServerIds: this.didStartServerIds };933}934}935936export type IChatProgress =937| IChatMarkdownContent938| IChatAgentMarkdownContentWithVulnerability939| IChatTreeData940| IChatMultiDiffData941| IChatMultiDiffDataSerialized942| IChatUsedContext943| IChatContentReference944| IChatContentInlineReference945| IChatCodeCitation946| IChatProgressMessage947| IChatTask948| IChatTaskResult949| IChatCommandButton950| IChatWarningMessage951| IChatTextEdit952| IChatNotebookEdit953| IChatWorkspaceEdit954| IChatMoveMessage955| IChatResponseCodeblockUriPart956| IChatConfirmation957| IChatQuestionCarousel958| IChatClearToPreviousToolInvocation959| IChatToolInvocation960| IChatToolInvocationSerialized961| IChatExtensionsContent962| IChatPullRequestContent963| IChatUndoStop964| IChatThinkingPart965| IChatTaskSerialized966| IChatElicitationRequest967| IChatElicitationRequestSerialized968| IChatMcpServersStarting969| IChatMcpServersStartingSerialized970| IChatHookPart971| IChatExternalToolInvocationUpdate;972973export interface IChatFollowup {974kind: 'reply';975message: string;976agentId: string;977subCommand?: string;978title?: string;979tooltip?: string;980}981982export function isChatFollowup(obj: unknown): obj is IChatFollowup {983return (984!!obj &&985(obj as IChatFollowup).kind === 'reply' &&986typeof (obj as IChatFollowup).message === 'string' &&987typeof (obj as IChatFollowup).agentId === 'string'988);989}990991export enum ChatAgentVoteDirection {992Down = 0,993Up = 1994}995996export enum ChatAgentVoteDownReason {997IncorrectCode = 'incorrectCode',998DidNotFollowInstructions = 'didNotFollowInstructions',999IncompleteCode = 'incompleteCode',1000MissingContext = 'missingContext',1001PoorlyWrittenOrFormatted = 'poorlyWrittenOrFormatted',1002RefusedAValidRequest = 'refusedAValidRequest',1003OffensiveOrUnsafe = 'offensiveOrUnsafe',1004Other = 'other',1005WillReportIssue = 'willReportIssue'1006}10071008export interface IChatVoteAction {1009kind: 'vote';1010direction: ChatAgentVoteDirection;1011reason: ChatAgentVoteDownReason | undefined;1012}10131014export enum ChatCopyKind {1015// Keyboard shortcut or context menu1016Action = 1,1017Toolbar = 21018}10191020export interface IChatCopyAction {1021kind: 'copy';1022codeBlockIndex: number;1023copyKind: ChatCopyKind;1024copiedCharacters: number;1025totalCharacters: number;1026copiedText: string;1027totalLines: number;1028copiedLines: number;1029modelId: string;1030languageId?: string;1031}10321033export interface IChatInsertAction {1034kind: 'insert';1035codeBlockIndex: number;1036totalCharacters: number;1037totalLines: number;1038languageId?: string;1039modelId: string;1040newFile?: boolean;1041}10421043export interface IChatApplyAction {1044kind: 'apply';1045codeBlockIndex: number;1046totalCharacters: number;1047totalLines: number;1048languageId?: string;1049modelId: string;1050newFile?: boolean;1051codeMapper?: string;1052editsProposed: boolean;1053}105410551056export interface IChatTerminalAction {1057kind: 'runInTerminal';1058codeBlockIndex: number;1059languageId?: string;1060}10611062export interface IChatCommandAction {1063kind: 'command';1064commandButton: IChatCommandButton;1065}10661067export interface IChatFollowupAction {1068kind: 'followUp';1069followup: IChatFollowup;1070}10711072export interface IChatBugReportAction {1073kind: 'bug';1074}10751076export interface IChatInlineChatCodeAction {1077kind: 'inlineChat';1078action: 'accepted' | 'discarded';1079}108010811082export interface IChatEditingSessionAction {1083kind: 'chatEditingSessionAction';1084uri: URI;1085hasRemainingEdits: boolean;1086outcome: 'accepted' | 'rejected' | 'userModified';1087}10881089export interface IChatEditingHunkAction {1090kind: 'chatEditingHunkAction';1091uri: URI;1092lineCount: number;1093linesAdded: number;1094linesRemoved: number;1095outcome: 'accepted' | 'rejected';1096hasRemainingEdits: boolean;1097modeId?: string;1098modelId?: string;1099languageId?: string;1100}11011102export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatApplyAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction | IChatBugReportAction | IChatInlineChatCodeAction | IChatEditingSessionAction | IChatEditingHunkAction;11031104export interface IChatUserActionEvent {1105action: ChatUserAction;1106agentId: string | undefined;1107command: string | undefined;1108sessionResource: URI;1109requestId: string;1110result: IChatAgentResult | undefined;1111modelId?: string | undefined;1112modeId?: string | undefined;1113}11141115export interface IChatDynamicRequest {1116/**1117* The message that will be displayed in the UI1118*/1119message: string;11201121/**1122* Any extra metadata/context that will go to the provider.1123*/1124// eslint-disable-next-line @typescript-eslint/no-explicit-any1125metadata?: any;1126}11271128export interface IChatCompleteResponse {1129message: string | ReadonlyArray<IChatProgress>;1130result?: IChatAgentResult;1131followups?: IChatFollowup[];1132}11331134export interface IChatSessionStats {1135fileCount: number;1136added: number;1137removed: number;1138}11391140export type IChatSessionTiming = {1141/**1142* Timestamp when the session was created in milliseconds elapsed since January 1, 1970 00:00:00 UTC.1143*/1144created: number;11451146/**1147* Timestamp when the most recent request started in milliseconds elapsed since January 1, 1970 00:00:00 UTC.1148*1149* Should be undefined if no requests have been made yet.1150*/1151lastRequestStarted: number | undefined;11521153/**1154* Timestamp when the most recent request completed in milliseconds elapsed since January 1, 1970 00:00:00 UTC.1155*1156* Should be undefined if the most recent request is still in progress or if no requests have been made yet.1157*/1158lastRequestEnded: number | undefined;1159};11601161interface ILegacyChatSessionTiming {1162startTime: number;1163endTime?: number;1164}11651166export function convertLegacyChatSessionTiming(timing: IChatSessionTiming | ILegacyChatSessionTiming): IChatSessionTiming {1167if (hasKey(timing, { created: true })) {1168return timing;1169}1170return {1171created: timing.startTime,1172lastRequestStarted: timing.startTime,1173lastRequestEnded: timing.endTime,1174};1175}11761177export const enum ResponseModelState {1178Pending,1179Complete,1180Cancelled,1181Failed,1182NeedsInput,1183}11841185export interface IChatDetail {1186sessionResource: URI;1187title: string;1188lastMessageDate: number;1189// Also support old timing format for backwards compatibility with persisted data1190timing: IChatSessionTiming | ILegacyChatSessionTiming;1191isActive: boolean;1192stats?: IChatSessionStats;1193lastResponseState: ResponseModelState;1194}11951196export interface IChatProviderInfo {1197id: string;1198}11991200export interface IChatSendRequestResponseState {1201responseCreatedPromise: Promise<IChatResponseModel>;1202responseCompletePromise: Promise<void>;1203}12041205export interface IChatSendRequestData extends IChatSendRequestResponseState {1206agent: IChatAgentData;1207slashCommand?: IChatAgentCommand;1208}12091210/**1211* Result of a sendRequest call - a discriminated union of possible outcomes.1212*/1213export type ChatSendResult =1214| ChatSendResultRejected1215| ChatSendResultSent1216| ChatSendResultQueued;12171218export interface ChatSendResultRejected {1219readonly kind: 'rejected';1220readonly reason: string;1221}12221223export interface ChatSendResultSent {1224readonly kind: 'sent';1225readonly data: IChatSendRequestData;1226}12271228export interface ChatSendResultQueued {1229readonly kind: 'queued';1230/**1231* Promise that resolves when the queued message is actually processed.1232* Will resolve to a 'sent' or 'rejected' result.1233*/1234readonly deferred: Promise<ChatSendResult>;1235}12361237export namespace ChatSendResult {1238export function isSent(result: ChatSendResult): result is ChatSendResultSent {1239return result.kind === 'sent';1240}12411242export function isRejected(result: ChatSendResult): result is ChatSendResultRejected {1243return result.kind === 'rejected';1244}12451246export function isQueued(result: ChatSendResult): result is ChatSendResultQueued {1247return result.kind === 'queued';1248}12491250/** Assertion function for tests - asserts that the result is a sent result */1251export function assertSent(result: ChatSendResult): asserts result is ChatSendResultSent {1252if (result.kind !== 'sent') {1253throw new Error(`Expected ChatSendResult to be 'sent', but was '${result.kind}'`);1254}1255}1256}12571258export interface IChatEditorLocationData {1259type: ChatAgentLocation.EditorInline;1260id: string;1261document: URI;1262selection: ISelection;1263wholeRange: IRange;1264}12651266export interface IChatNotebookLocationData {1267type: ChatAgentLocation.Notebook;1268sessionInputUri: URI;1269}12701271export interface IChatTerminalLocationData {1272type: ChatAgentLocation.Terminal;1273// TBD1274}12751276export type IChatLocationData = IChatEditorLocationData | IChatNotebookLocationData | IChatTerminalLocationData;12771278/**1279* The kind of queue request.1280*/1281export const enum ChatRequestQueueKind {1282/** Request is queued to be sent after current request completes */1283Queued = 'queued',1284/** Request is queued and signals the active request to yield */1285Steering = 'steering'1286}12871288export interface IChatSendRequestOptions {1289modeInfo?: IChatRequestModeInfo;1290userSelectedModelId?: string;1291userSelectedTools?: IObservable<UserSelectedTools>;1292location?: ChatAgentLocation;1293locationData?: IChatLocationData;1294parserContext?: IChatParserContext;1295attempt?: number;1296noCommandDetection?: boolean;1297// eslint-disable-next-line @typescript-eslint/no-explicit-any1298acceptedConfirmationData?: any[];1299// eslint-disable-next-line @typescript-eslint/no-explicit-any1300rejectedConfirmationData?: any[];1301attachedContext?: IChatRequestVariableEntry[];13021303/** The target agent ID can be specified with this property instead of using @ in 'message' */1304agentId?: string;1305/** agentId, but will not add a @ name to the request */1306agentIdSilent?: string;1307slashCommand?: string;13081309/**1310* The label of the confirmation action that was selected.1311*/1312confirmation?: string;13131314/**1315* When set, queues this message to be sent after the current request completes.1316* If Steering, also sets yieldRequested on any active request to signal it should wrap up.1317*/1318queue?: ChatRequestQueueKind;13191320}13211322export type IChatModelReference = IReference<IChatModel>;13231324export const IChatService = createDecorator<IChatService>('IChatService');13251326export interface IChatService {1327_serviceBrand: undefined;1328transferredSessionResource: URI | undefined;13291330readonly onDidSubmitRequest: Event<{ readonly chatSessionResource: URI }>;13311332readonly onDidCreateModel: Event<IChatModel>;13331334/**1335* An observable containing all live chat models.1336*/1337readonly chatModels: IObservable<Iterable<IChatModel>>;13381339isEnabled(location: ChatAgentLocation): boolean;1340hasSessions(): boolean;1341startSession(location: ChatAgentLocation, options?: IChatSessionStartOptions): IChatModelReference;13421343/**1344* Get an active session without holding a reference to it.1345*/1346getSession(sessionResource: URI): IChatModel | undefined;13471348/**1349* Acquire a reference to an active session.1350*/1351getActiveSessionReference(sessionResource: URI): IChatModelReference | undefined;13521353getOrRestoreSession(sessionResource: URI): Promise<IChatModelReference | undefined>;1354getSessionTitle(sessionResource: URI): string | undefined;1355loadSessionFromContent(data: IExportableChatData | ISerializableChatData | URI): IChatModelReference | undefined;1356loadSessionForResource(resource: URI, location: ChatAgentLocation, token: CancellationToken): Promise<IChatModelReference | undefined>;1357readonly editingSessions: IChatEditingSession[];1358getChatSessionFromInternalUri(sessionResource: URI): IChatSessionContext | undefined;13591360/**1361* Sends a chat request for the given session.1362* @returns A result indicating whether the request was sent, queued, or rejected.1363*/1364sendRequest(sessionResource: URI, message: string, options?: IChatSendRequestOptions): Promise<ChatSendResult>;13651366/**1367* Sets a custom title for a chat model.1368*/1369setTitle(sessionResource: URI, title: string): void;1370appendProgress(request: IChatRequestModel, progress: IChatProgress): void;1371resendRequest(request: IChatRequestModel, options?: IChatSendRequestOptions): Promise<void>;1372adoptRequest(sessionResource: URI, request: IChatRequestModel): Promise<void>;1373removeRequest(sessionResource: URI, requestId: string): Promise<void>;1374cancelCurrentRequestForSession(sessionResource: URI): void;1375/**1376* Sets yieldRequested on the active request for the given session.1377*/1378setYieldRequested(sessionResource: URI): void;1379/**1380* Removes a pending request from the session's queue.1381*/1382removePendingRequest(sessionResource: URI, requestId: string): void;1383/**1384* Sets the pending requests for a session, allowing for deletions/reordering.1385* Adding new requests should go through sendRequest with the queue option.1386*/1387setPendingRequests(sessionResource: URI, requests: readonly { requestId: string; kind: ChatRequestQueueKind }[]): void;1388/**1389* Ensures pending requests for the session are processing. If restoring from1390* storage or after an error, pending requests may be present without an1391* active chat message 'loop' happening. THis triggers the loop to happen1392* as needed. Idempotent, safe to call at any time.1393*/1394processPendingRequests(sessionResource: URI): void;1395addCompleteRequest(sessionResource: URI, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void;1396setChatSessionTitle(sessionResource: URI, title: string): void;1397getLocalSessionHistory(): Promise<IChatDetail[]>;1398clearAllHistoryEntries(): Promise<void>;1399removeHistoryEntry(sessionResource: URI): Promise<void>;1400getChatStorageFolder(): URI;1401logChatIndex(): void;1402getLiveSessionItems(): Promise<IChatDetail[]>;1403getHistorySessionItems(): Promise<IChatDetail[]>;1404getMetadataForSession(sessionResource: URI): Promise<IChatDetail | undefined>;14051406readonly onDidPerformUserAction: Event<IChatUserActionEvent>;1407notifyUserAction(event: IChatUserActionEvent): void;14081409readonly onDidReceiveQuestionCarouselAnswer: Event<{ requestId: string; resolveId: string; answers: Record<string, unknown> | undefined }>;1410notifyQuestionCarouselAnswer(requestId: string, resolveId: string, answers: Record<string, unknown> | undefined): void;14111412readonly onDidDisposeSession: Event<{ readonly sessionResource: URI[]; readonly reason: 'cleared' }>;14131414transferChatSession(transferredSessionResource: URI, toWorkspace: URI): Promise<void>;14151416activateDefaultAgent(location: ChatAgentLocation): Promise<void>;14171418readonly edits2Enabled: boolean;14191420readonly requestInProgressObs: IObservable<boolean>;14211422/**1423* For tests only!1424*/1425setSaveModelsEnabled(enabled: boolean): void;14261427/**1428* For tests only!1429*/1430waitForModelDisposals(): Promise<void>;1431}14321433export interface IChatSessionContext {1434readonly chatSessionType: string;1435readonly chatSessionResource: URI;1436readonly isUntitled: boolean;1437}14381439export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation';14401441export interface IChatSessionStartOptions {1442canUseTools?: boolean;1443disableBackgroundKeepAlive?: boolean;1444}144514461447