From 38768712a381ebf9bb3857e9f5bc3ab171acf1dd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 16 Apr 2025 14:57:53 -0700 Subject: [PATCH 1/6] chat: allow tools to report progress This adds a `toolProgress` proposed API that allows extensions to report progress keyed on the `toolInvocationToken`. Internally, when given to a tool, this now includes the call ID of the tool. We can use that both from extensions and internally to report progress for tools, and I hooked this up for MCP servers. Currently only the text is updated. Involved some changes in the progress service internally: - Previously `viewId` was a naked string, I wrapped it in an object to make it more identifiable. - The progress service is in `workbench/services` and directly calls into other services to effect progress. In leui of going for a full 'contribution' model, I made a small `ILanguageModelToolProgressService` that it writes state into and that can be read back out. The state (an observable) is kept as long as progress is ongoing or this is is an observer. --- .../common/extensionsApiProposals.ts | 3 + src/vs/platform/progress/common/progress.ts | 8 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostLanguageModelTools.ts | 2 +- .../workbench/api/common/extHostProgress.ts | 9 +- .../api/common/extHostTypeConverters.ts | 12 ++- .../workbench/browser/parts/views/treeView.ts | 4 +- .../workbench/browser/parts/views/viewPane.ts | 4 +- .../chatToolInvocationPart.ts | 33 ++++++-- .../chat/common/languageModelToolsService.ts | 1 + .../browser/languageModelToolsService.test.ts | 3 +- .../contrib/debug/browser/debugProgress.ts | 2 +- .../contrib/debug/browser/debugViewlet.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../contrib/files/browser/fileImportExport.ts | 6 +- .../workbench/contrib/mcp/common/mcpServer.ts | 28 ++++++- .../mcp/common/mcpServerRequestHandler.ts | 2 +- .../contrib/mcp/common/mcpService.ts | 9 +- .../workbench/contrib/mcp/common/mcpTypes.ts | 6 ++ .../contrib/scm/browser/scmHistoryViewPane.ts | 6 +- .../testing/browser/testExplorerActions.ts | 2 +- .../testResultsView/testResultsTree.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 2 +- .../webviewView/browser/webviewViewPane.ts | 2 +- .../languageModelToolProgressService.ts | 83 +++++++++++++++++++ .../progress/browser/progressService.ts | 26 ++++-- .../userDataProfileImportExportService.ts | 2 +- .../vscode.proposed.toolProgress.d.ts | 34 ++++++++ 28 files changed, 248 insertions(+), 49 deletions(-) create mode 100644 src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts create mode 100644 src/vscode-dts/vscode.proposed.toolProgress.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index e21a27208f3..536e3dd8a45 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -394,6 +394,9 @@ const _allApiProposals = { tokenInformation: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', }, + toolProgress: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.toolProgress.d.ts', + }, treeViewActiveItem: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', }, diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 312f01a17d9..3a0a6ca7f31 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -50,8 +50,12 @@ export const enum ProgressLocation { Dialog = 20 } +export type ProgressLocationDetailed = { viewId: string } | { toolInvocationCallId: string | undefined }; + +export type ProgressLocationDescriptor = ProgressLocation | ProgressLocationDetailed; + export interface IProgressOptions { - readonly location: ProgressLocation | string; + readonly location: ProgressLocationDescriptor; readonly title?: string; readonly source?: string | INotificationSource; readonly total?: number; @@ -81,7 +85,7 @@ export interface IProgressWindowOptions extends IProgressOptions { } export interface IProgressCompositeOptions extends IProgressOptions { - readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; + readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | { viewId: string }; readonly delay?: number; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 0db23694cd0..494f426ba16 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -815,7 +815,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, - withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { + withProgress(options: vscode.ProgressOptions | vscode.ProgressOptions2, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { return extHostProgress.withProgress(extension, options, task); }, createOutputChannel(name: string, options: string | { log: true } | undefined): any { diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index fcbd6c80691..594c870540e 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -73,7 +73,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape callId, parameters: options.input, tokenBudget: options.tokenizationOptions?.tokenBudget, - context: options.toolInvocationToken as IToolInvocationContext | undefined, + context: options.toolInvocationToken ? { ...(options.toolInvocationToken as IToolInvocationContext), callId } : undefined, chatRequestId: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.chatRequestId : undefined, chatInteractionId: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.chatInteractionId : undefined, }, token); diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index 0914c2e7916..15f94246b25 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ProgressOptions } from 'vscode'; +import { ProgressOptions, ProgressOptions2 } from 'vscode'; import { MainThreadProgressShape, ExtHostProgressShape } from './extHost.protocol.js'; import { ProgressLocation } from './extHostTypeConverters.js'; import { Progress, IProgressStep } from '../../../platform/progress/common/progress.js'; @@ -11,6 +11,7 @@ import { CancellationTokenSource, CancellationToken } from '../../../base/common import { throttle } from '../../../base/common/decorators.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { onUnexpectedExternalError } from '../../../base/common/errors.js'; +import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js'; export class ExtHostProgress implements ExtHostProgressShape { @@ -22,11 +23,15 @@ export class ExtHostProgress implements ExtHostProgressShape { this._proxy = proxy; } - async withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Promise { + async withProgress(extension: IExtensionDescription, options: ProgressOptions | ProgressOptions2, task: (progress: Progress, token: CancellationToken) => Thenable): Promise { const handle = this._handles++; const { title, location, cancellable } = options; const source = { label: extension.displayName || extension.name, id: extension.identifier.value }; + if (typeof location === 'object' && location.hasOwnProperty('toolInvocationToken')) { + checkProposedApiEnabled(extension, 'toolProgress'); + } + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, !extension.isUnderDevelopment ? extension.identifier.value : undefined).catch(onUnexpectedExternalError); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 75c7f54d600..cb208b29f5e 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,14 +36,14 @@ import { EndOfLineSequence, TrackedRangeStickiness } from '../../../editor/commo import { ITextEditorOptions } from '../../../platform/editor/common/editor.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from '../../../platform/markers/common/markers.js'; -import { ProgressLocation as MainProgressLocation } from '../../../platform/progress/common/progress.js'; +import { ProgressLocation as MainProgressLocation, ProgressLocationDescriptor as MainProgressLocationDescriptor } from '../../../platform/progress/common/progress.js'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatModel.js'; import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; -import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; +import { IToolData, IToolInvocationContext, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import * as chatProvider from '../../contrib/chat/common/languageModels.js'; import { IChatResponsePromptTsxPart, IChatResponseTextPart } from '../../contrib/chat/common/languageModels.js'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from '../../contrib/debug/common/debug.js'; @@ -1458,9 +1458,13 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation | { viewId: string }): MainProgressLocation | string { + export function from(loc: vscode.ProgressLocation | { viewId: string } | { toolInvocationToken: object | undefined }): MainProgressLocationDescriptor { if (typeof loc === 'object') { - return loc.viewId; + if ('toolInvocationToken' in loc) { + return { toolInvocationCallId: (loc.toolInvocationToken as IToolInvocationContext).callId }; + } else { + return { viewId: loc.viewId }; + } } switch (loc) { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index bc660c6f7a1..e5562575d4c 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -692,7 +692,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService); const treeMenus = this.treeDisposables.add(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this.treeDisposables.add(this.instantiationService.createInstance(ResourceLabels, this)); - const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: { viewId: this.id } }, () => task)); const aligner = this.treeDisposables.add(new Aligner(this.themeService)); const checkboxStateHandler = this.treeDisposables.add(new CheckboxStateHandler()); const renderer = this.treeDisposables.add(this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes)); @@ -1791,7 +1791,7 @@ export class CustomTreeView extends AbstractTreeView { id: this.id, }); this.createTree(); - this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + this.progressService.withProgress({ location: { viewId: this.id } }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) .then(() => timeout(2000)) .then(() => { this.updateMessage(); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index e8dedb875d8..58200d5fc40 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -651,8 +651,8 @@ export abstract class ViewPane extends Pane implements IView { return this.progressIndicator; } - protected getProgressLocation(): string { - return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id; + protected getProgressLocation(): { viewId: string } { + return { viewId: this.viewDescriptorService.getViewContainerByViewId(this.id)!.id }; } protected getLocationBasedColors(): IViewPaneLocationColors { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts index 86a5d9e5991..a1489fc90b1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts @@ -9,6 +9,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable, thenIfNotDisposed, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { autorunWithStore } from '../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; @@ -22,6 +23,8 @@ import { IContextKeyService } from '../../../../../platform/contextkey/common/co import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; +import { IProgressService } from '../../../../../platform/progress/common/progress.js'; +import { ILanguageModelToolProgressService } from '../../../../services/progress/browser/languageModelToolProgressService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatMarkdownContent, IChatProgressMessage, IChatTerminalToolInvocationData, IChatToolInvocation, IChatToolInvocationSerialized } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; @@ -64,6 +67,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa codeBlockModelCollection: CodeBlockModelCollection, codeBlockStartIndex: number, @IInstantiationService instantiationService: IInstantiationService, + @IProgressService progressService: IProgressService, ) { super(); @@ -140,6 +144,7 @@ class ChatToolInvocationSubPart extends Disposable { @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, @ICommandService private readonly commandService: ICommandService, @IMarkerService private readonly markerService: IMarkerService, + @ILanguageModelToolProgressService private readonly languageModelToolProgressService: ILanguageModelToolProgressService, ) { super(); @@ -454,27 +459,37 @@ class ChatToolInvocationSubPart extends Disposable { } private createProgressPart(): HTMLElement { - let content: IMarkdownString; if (this.toolInvocation.isComplete && this.toolInvocation.isConfirmed !== false && this.toolInvocation.pastTenseMessage) { - content = typeof this.toolInvocation.pastTenseMessage === 'string' ? - new MarkdownString().appendText(this.toolInvocation.pastTenseMessage) : - this.toolInvocation.pastTenseMessage; + const part = this.renderProgressContent(this.toolInvocation.pastTenseMessage); + this._register(part); + return part.domNode; } else { - content = typeof this.toolInvocation.invocationMessage === 'string' ? - new MarkdownString().appendText(this.toolInvocation.invocationMessage + '…') : - MarkdownString.lift(this.toolInvocation.invocationMessage).appendText('…'); + const container = document.createElement('div'); + const progressObservable = this._register(this.languageModelToolProgressService.listenForProgress(this.toolInvocation.toolCallId)); + this._register(autorunWithStore((reader, store) => { + const progress = progressObservable.object.read(reader); + const part = store.add(this.renderProgressContent(progress?.message || this.toolInvocation.invocationMessage)); + dom.reset(container, part.domNode); + })); + return container; + } + } + + private renderProgressContent(content: IMarkdownString | string) { + if (typeof content === 'string') { + content = new MarkdownString().appendText(content); } const progressMessage: IChatProgressMessage = { kind: 'progressMessage', content }; + const iconOverride = !this.toolInvocation.isConfirmed ? Codicon.error : this.toolInvocation.isComplete ? Codicon.check : undefined; - const progressPart = this._register(this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, iconOverride)); - return progressPart.domNode; + return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, iconOverride); } private createTerminalMarkdownProgressPart(toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, terminalData: IChatTerminalToolInvocationData): HTMLElement { diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index c123f290cb8..7b37b56bf2f 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -83,6 +83,7 @@ export interface IToolInvocation { export interface IToolInvocationContext { sessionId: string; + callId: string | undefined; } export function isToolInvocationContext(obj: any): obj is IToolInvocationContext { diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index 855c89aefed..eb47ea1ac00 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -174,7 +174,8 @@ suite('LanguageModelToolsService', () => { a: 1 }, context: { - sessionId + sessionId, + callId: '1', }, }; chatService.addSession({ diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts index 0cca619bf96..5ad4c9c995a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugProgress.ts +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.ts @@ -37,7 +37,7 @@ export class DebugProgressContribution implements IWorkbenchContribution { }); if (viewsService.isViewContainerVisible(VIEWLET_ID)) { - progressService.withProgress({ location: VIEWLET_ID }, () => promise); + progressService.withProgress({ location: { viewId: VIEWLET_ID } }, () => promise); } const source = debugService.getAdapterManager().getDebuggerLabel(session.configuration.type); progressService.withProgress({ diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 10b8d244b2e..af6ef42997c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -131,7 +131,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } if (state === State.Initializing) { - this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => { + this.progressService.withProgress({ location: { viewId: VIEWLET_ID }, }, _progress => { return new Promise(resolve => this.progressResolve = resolve); }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index ce3cab343b4..8533863a48c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2737,7 +2737,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation | string): Promise { const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....'); return this.withProgress({ - location: progressLocation ?? ProgressLocation.Extensions, + location: typeof progressLocation === 'string' ? { viewId: progressLocation } : (progressLocation ?? ProgressLocation.Extensions), title }, async () => { try { diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 44ecb6d65e5..8337c2a9750 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -98,7 +98,7 @@ export class BrowserFileUpload { ); // Also indicate progress in the files view - this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => uploadPromise); + this.progressService.withProgress({ location: { viewId: VIEW_ID }, delay: 500 }, () => uploadPromise); return uploadPromise; } @@ -417,7 +417,7 @@ export class ExternalFileImport { ); // Also indicate progress in the files view - this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => importPromise); + this.progressService.withProgress({ location: { viewId: VIEW_ID }, delay: 500 }, () => importPromise); return importPromise; } @@ -619,7 +619,7 @@ export class FileDownload { ); // Also indicate progress in the files view - this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => downloadPromise); + this.progressService.withProgress({ location: { viewId: VIEW_ID }, delay: 500 }, () => downloadPromise); return downloadPromise; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index 6545737fb4a..e6a54cbac63 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -27,6 +27,8 @@ import { INotificationService, Severity } from '../../../../platform/notificatio import { localize } from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; type ServerBootData = { supportsLogging: boolean; @@ -481,7 +483,31 @@ export class McpTool implements IMcpTool { call(params: Record, token?: CancellationToken): Promise { // serverToolName is always set now, but older cache entries (from 1.99-Insiders) may not have it. const name = this._definition.serverToolName ?? this._definition.name; - return this._server.callOn(h => h.callTool({ name, arguments: params }), token); + return this._server.callOn(h => h.callTool({ name, arguments: params }, token), token); + } + + callWithProgress(params: Record, progress: IProgress, token?: CancellationToken): Promise { + // serverToolName is always set now, but older cache entries (from 1.99-Insiders) may not have it. + const name = this._definition.serverToolName ?? this._definition.name; + const progressToken = generateUuid(); + + return this._server.callOn(h => { + let lastProgressN = 0; + const listener = h.onDidReceiveProgressNotification((e) => { + if (e.params.progressToken === progressToken) { + progress.report({ + message: e.params.message, + increment: e.params.progress - lastProgressN, + total: e.params.total, + }); + lastProgressN = e.params.progress; + } + }); + + return h.callTool({ name, arguments: params, _meta: { progressToken } }, token).finally(() => { + listener.dispose(); + }); + }, token); } compare(other: IMcpTool): number { diff --git a/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts b/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts index 8c0fa08cae0..011f50f026d 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts @@ -466,7 +466,7 @@ export class McpServerRequestHandler extends Disposable { /** * Call a specific tool */ - callTool(params: MCP.CallToolRequest['params'], token?: CancellationToken): Promise { + callTool(params: MCP.CallToolRequest['params'] & MCP.Request['params'], token?: CancellationToken): Promise { return this.sendRequest({ method: 'tools/call', params }, token); } diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index 98a9bddf4e2..fe8660b7625 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -14,6 +14,7 @@ import { localize } from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IProgressService } from '../../../../platform/progress/common/progress.js'; import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; @@ -214,6 +215,7 @@ class McpToolImplementation implements IToolImpl { private readonly _tool: IMcpTool, private readonly _server: IMcpServer, @IProductService private readonly _productService: IProductService, + @IProgressService private readonly _progressService: IProgressService, ) { } async prepareToolInvocation(parameters: any): Promise { @@ -254,7 +256,12 @@ class McpToolImplementation implements IToolImpl { const outputParts: string[] = []; - const callResult = await this._tool.call(invocation.parameters as Record, token); + + const params = invocation.parameters as Record; + const callResult = invocation.context + ? await this._progressService.withProgress({ location: { toolInvocationCallId: invocation.callId } }, p => this._tool.callWithProgress(params, p, token)) + : await this._tool.call(params, token); + for (const item of callResult.content) { if (item.type === 'text') { result.content.push({ diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 7d06c30764e..9450894dffb 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -19,6 +19,7 @@ import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { IWorkspaceFolderData } from '../../../../platform/workspace/common/workspace.js'; import { McpServerRequestHandler } from './mcpServerRequestHandler.js'; import { MCP } from './modelContextProtocol.js'; +import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; export const extensionMcpCollectionPrefix = 'ext.'; @@ -248,6 +249,11 @@ export interface IMcpTool { * @throws {@link McpConnectionFailedError} if the connection to the server fails */ call(params: Record, token?: CancellationToken): Promise; + + /** + * Identical to {@link call}, but reports progress. + */ + callWithProgress(params: Record, progress: IProgress, token?: CancellationToken): Promise; } export const enum McpServerTransportType { diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index fdde018b017..9d984d1abfc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -651,7 +651,7 @@ class SCMHistoryViewPaneActionRunner extends ActionRunner { } protected override runAction(action: IAction, context?: unknown): Promise { - return this._progressService.withProgress({ location: HISTORY_VIEW_PANE_ID }, + return this._progressService.withProgress({ location: { viewId: HISTORY_VIEW_PANE_ID } }, async () => await super.runAction(action, context)); } } @@ -1318,7 +1318,7 @@ export class SCMHistoryViewPane extends ViewPane { await waitForState(firstRepositoryInitialized); // Initial rendering - await this._progressService.withProgress({ location: this.id }, async () => { + await this._progressService.withProgress({ location: { viewId: this.id } }, async () => { await this._treeOperationSequencer.queue(async () => { await this._tree.setInput(this._treeViewModel); this._tree.scrollTop = 0; @@ -1689,7 +1689,7 @@ export class SCMHistoryViewPane extends ViewPane { return this._updateChildrenThrottler.queue( () => this._treeOperationSequencer.queue( async () => { - await this._progressService.withProgress({ location: this.id }, + await this._progressService.withProgress({ location: { viewId: this.id } }, async () => { await this._tree.updateChildren(undefined, undefined, undefined, { // diffIdentityProvider: this._treeIdentityProvider diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 160800155fc..f23dae9b9a5 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1684,7 +1684,7 @@ export class RefreshTestsAction extends Action2 { const progressService = accessor.get(IProgressService); const controllerIds = distinct(elements.filter(isDefined).map(e => e.test.controllerId)); - return progressService.withProgress({ location: Testing.ViewletId }, async () => { + return progressService.withProgress({ location: { viewId: Testing.ViewletId } }, async () => { if (controllerIds.length) { await Promise.all(controllerIds.map(id => testService.refreshTests(id))); } else { diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 1895434ddba..7bfe65c7eff 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -585,7 +585,7 @@ export class OutputPeekTree extends Disposable { return coverageService.closeCoverage(); } progressService.withProgress( - { location: options.locationForProgress }, + { location: { viewId: options.locationForProgress } }, () => coverageService.openCoverage(task, true) ); } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index d6582ce54f8..f48f461e011 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -645,7 +645,7 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let response: Timeline | undefined; try { - response = await this.progressService.withProgress({ location: this.id }, () => request.result); + response = await this.progressService.withProgress({ location: { viewId: this.id } }, () => request.result); } finally { this.pendingRequests.get(request.source)?.dispose(); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 6dae8155249..0de87305fc0 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -267,7 +267,7 @@ export class WebviewViewPane extends ViewPane { } private async withProgress(task: () => Promise): Promise { - return this.progressService.withProgress({ location: this.id, delay: 500 }, task); + return this.progressService.withProgress({ location: { viewId: this.id }, delay: 500 }, task); } override onDidScrollRoot() { diff --git a/src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts b/src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts new file mode 100644 index 00000000000..1e744fd1dc0 --- /dev/null +++ b/src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from '../../../../base/common/event.js'; +import { IReference } from '../../../../base/common/lifecycle.js'; +import { IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; + +export const ILanguageModelToolProgressService = createDecorator('languageModelToolProgressService'); + +export interface ILanguageModelToolProgressService { + _serviceBrand: undefined; + + listenForProgress(callId: string): IReference>; + + handleProgress

, R = unknown>(callId: string, callback: (progress: IProgress) => P): P; +} + +export interface ILanguageModelToolProgressDelegate { + onDidCancel: Event; + update(step: IProgressStep): void; + dispose?(): void; +} + +export class LanguageModelToolProgressService implements ILanguageModelToolProgressService { + declare readonly _serviceBrand: undefined; + + private readonly progress = new Map; + rc: number; + }>(); + + listenForProgress(callId: string): IReference> { + let rec = this.progress.get(callId); + if (!rec) { + rec = { value: observableValue(this, { message: undefined, total: 0, done: 0 }), rc: 0 }; + this.progress.set(callId, rec); + } + rec.rc++; + + return { + object: rec.value, + dispose: () => { + if (!--rec.rc) { + this.progress.delete(callId); + } + } + }; + } + + handleProgress

, R = unknown>(callId: string, callback: (progress: IProgress) => P): P { + let rec = this.progress.get(callId); + if (!rec) { + rec = { value: observableValue(this, { message: undefined, total: 0, done: 0 }), rc: 0 }; + this.progress.set(callId, rec); + } + + rec.rc++; + + let last = 0; + const promise = callback({ + report: update => { + if (update.increment) { + last += update.increment; + } + rec.value.set({ message: update.message, total: update.total || 100, done: last }, undefined); + }, + }); + promise.finally(() => { + if (!--rec.rc) { + this.progress.delete(callId); + } + }); + + return promise; + } +} + +registerSingleton(ILanguageModelToolProgressService, LanguageModelToolProgressService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index cb8831c7e77..96d04e24474 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -24,6 +24,7 @@ import { IPaneCompositePartService } from '../../panecomposite/browser/panecompo import { stripIcons } from '../../../../base/common/iconLabels.js'; import { IUserActivityService } from '../../userActivity/common/userActivityService.js'; import { createWorkbenchDialogOptions } from '../../../../platform/dialogs/browser/dialog.js'; +import { ILanguageModelToolProgressService } from './languageModelToolProgressService.js'; export class ProgressService extends Disposable implements IProgressService { @@ -39,6 +40,7 @@ export class ProgressService extends Disposable implements IProgressService { @ILayoutService private readonly layoutService: ILayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IUserActivityService private readonly userActivityService: IUserActivityService, + @ILanguageModelToolProgressService private readonly languageModelToolProgressService: ILanguageModelToolProgressService, ) { super(); } @@ -55,24 +57,32 @@ export class ProgressService extends Disposable implements IProgressService { } }; - const handleStringLocation = (location: string) => { - const viewContainer = this.viewDescriptorService.getViewContainerById(location); + const handleViewLocation = (location: { viewId: string }) => { + const viewContainer = this.viewDescriptorService.getViewContainerById(location.viewId); if (viewContainer) { const viewContainerLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); if (viewContainerLocation !== null) { - return this.withPaneCompositeProgress(location, viewContainerLocation, task, { ...options, location }); + return this.withPaneCompositeProgress(location.viewId, viewContainerLocation, task, { ...options, location }); } } - if (this.viewDescriptorService.getViewDescriptorById(location) !== null) { - return this.withViewProgress(location, task, { ...options, location }); + if (this.viewDescriptorService.getViewDescriptorById(location.viewId) !== null) { + return this.withViewProgress(location.viewId, task, { ...options, location }); } throw new Error(`Bad progress location: ${location}`); }; - if (typeof location === 'string') { - return handleStringLocation(location); + if (typeof location === 'object') { + if ('viewId' in location) { + return handleViewLocation(location); + } else if ('toolInvocationCallId' in location) { + if (!location.toolInvocationCallId) { + return task({ report: () => { } }); + } + + return this.languageModelToolProgressService.handleProgress(location.toolInvocationCallId, task); + } } switch (location) { @@ -102,7 +112,7 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Explorer: return this.withPaneCompositeProgress('workbench.view.explorer', ViewContainerLocation.Sidebar, task, { ...options, location }); case ProgressLocation.Scm: - return handleStringLocation('workbench.scm'); + return handleViewLocation({ viewId: 'workbench.scm' }); case ProgressLocation.Extensions: return this.withPaneCompositeProgress('workbench.view.extensions', ViewContainerLocation.Sidebar, task, { ...options, location }); case ProgressLocation.Dialog: diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index df2b9fa8763..b309fe618e8 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -262,7 +262,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU try { await this.progressService.withProgress({ - location, + location: typeof location === 'string' ? { viewId: location } : location, title: localize('profiles.exporting', "{0}: Exporting...", PROFILES_CATEGORY.value), }, async progress => { const id = await this.pickProfileContentHandler(profile.name); diff --git a/src/vscode-dts/vscode.proposed.toolProgress.d.ts b/src/vscode-dts/vscode.proposed.toolProgress.d.ts new file mode 100644 index 00000000000..6969c1f928d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.toolProgress.d.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface ProgressOptions2 extends Omit { + location: ProgressLocation | { + /** + * The identifier of a view for which progress should be shown. + */ + viewId: string; + } | { + /** + * An invocation token for progress shown while a {@link LanguageModelTool} is running. + */ + toolInvocationToken: ChatParticipantToolToken | undefined; + }; + } + + export namespace window { + export function withProgress(options: ProgressOptions2, task: (progress: Progress<{ + /** + * A progress message that represents a chunk of work + */ + message?: string; + /** + * An increment for discrete progress. Increments will be summed up until 100% is reached + */ + increment?: number; + }>, token: CancellationToken) => Thenable): Thenable; + } +} From edc8644253dc87ee322ace9dd9e94d3111d2afe4 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 17 Apr 2025 10:51:04 -0700 Subject: [PATCH 2/6] be less creative ;) --- src/vs/platform/progress/common/progress.ts | 8 +- .../browser/mainThreadLanguageModelTools.ts | 7 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostLanguageModelTools.ts | 15 +++- .../workbench/api/common/extHostProgress.ts | 9 +- .../api/common/extHostTypeConverters.ts | 12 +-- .../workbench/browser/parts/views/treeView.ts | 4 +- .../workbench/browser/parts/views/viewPane.ts | 4 +- .../chatToolInvocationPart.ts | 6 +- .../chat/browser/languageModelToolsService.ts | 37 +++++++-- .../chatProgressTypes/chatToolInvocation.ts | 12 +++ .../contrib/chat/common/chatService.ts | 2 + .../chat/common/languageModelToolsService.ts | 9 +- .../contrib/chat/common/tools/editFileTool.ts | 3 +- .../electron-sandbox/tools/fetchPageTool.ts | 3 +- .../browser/languageModelToolsService.test.ts | 5 +- .../common/mockLanguageModelToolsService.ts | 5 ++ .../contrib/debug/browser/debugProgress.ts | 2 +- .../contrib/debug/browser/debugViewlet.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../extensions/common/searchExtensionsTool.ts | 3 +- .../contrib/files/browser/fileImportExport.ts | 6 +- .../contrib/mcp/common/mcpConfiguration.ts | 20 ++--- .../contrib/mcp/common/mcpService.ts | 12 +-- .../contrib/scm/browser/scmHistoryViewPane.ts | 6 +- .../testing/browser/testExplorerActions.ts | 2 +- .../testResultsView/testResultsTree.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 2 +- .../webviewView/browser/webviewViewPane.ts | 2 +- .../languageModelToolProgressService.ts | 83 ------------------- .../progress/browser/progressService.ts | 26 ++---- .../userDataProfileImportExportService.ts | 2 +- .../vscode.proposed.toolProgress.d.ts | 37 ++++----- 34 files changed, 144 insertions(+), 209 deletions(-) delete mode 100644 src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 3a0a6ca7f31..312f01a17d9 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -50,12 +50,8 @@ export const enum ProgressLocation { Dialog = 20 } -export type ProgressLocationDetailed = { viewId: string } | { toolInvocationCallId: string | undefined }; - -export type ProgressLocationDescriptor = ProgressLocation | ProgressLocationDetailed; - export interface IProgressOptions { - readonly location: ProgressLocationDescriptor; + readonly location: ProgressLocation | string; readonly title?: string; readonly source?: string | INotificationSource; readonly total?: number; @@ -85,7 +81,7 @@ export interface IProgressWindowOptions extends IProgressOptions { } export interface IProgressCompositeOptions extends IProgressOptions { - readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | { viewId: string }; + readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; readonly delay?: number; } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts index 67502d9db2c..88ab6e0e598 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts @@ -6,6 +6,7 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; import { revive } from '../../../base/common/marshalling.js'; +import { IProgressStep } from '../../../platform/progress/common/progress.js'; import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolInvocation, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; @@ -45,6 +46,10 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre }; } + $acceptToolProgress(requestId: string | undefined, callId: string, progress: IProgressStep): void { + this._languageModelToolsService.acceptProgress(requestId, callId, progress); + } + $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise { const fn = this._countTokenCallbacks.get(callId); if (!fn) { @@ -58,7 +63,7 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre const disposable = this._languageModelToolsService.registerToolImplementation( id, { - invoke: async (dto, countTokens, token) => { + invoke: async (dto, countTokens, _progress, token) => { try { this._countTokenCallbacks.set(dto.callId, countTokens); const resultDto = await this._proxy.$invokeTool(dto, token); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 494f426ba16..0db23694cd0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -815,7 +815,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, - withProgress(options: vscode.ProgressOptions | vscode.ProgressOptions2, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { + withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { return extHostProgress.withProgress(extension, options, task); }, createOutputChannel(name: string, options: string | { log: true } | undefined): any { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 23b2026b543..b74dc18c946 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1380,6 +1380,7 @@ export type IToolDataDto = Omit; export interface MainThreadLanguageModelToolsShape extends IDisposable { $getTools(): Promise[]>; + $acceptToolProgress(requestId: string | undefined, callId: string, progress: IProgressStep): void; $invokeTool(dto: IToolInvocation, token?: CancellationToken): Promise>; $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise; $registerTool(id: string): void; diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 594c870540e..82de8a39f02 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -73,7 +73,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape callId, parameters: options.input, tokenBudget: options.tokenizationOptions?.tokenBudget, - context: options.toolInvocationToken ? { ...(options.toolInvocationToken as IToolInvocationContext), callId } : undefined, + context: options.toolInvocationToken as IToolInvocationContext | undefined, chatRequestId: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.chatRequestId : undefined, chatInteractionId: isProposedApiEnabled(extension, 'chatParticipantPrivate') ? options.chatInteractionId : undefined, }, token); @@ -138,7 +138,18 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token)), token); + const progress: vscode.Progress<{ message?: string; increment?: number }> = { + report: value => { + checkProposedApiEnabled(item.extension, 'toolProgress'); + this._proxy.$acceptToolProgress(dto.chatRequestId, dto.callId, { + message: value.message, + increment: value.increment, + total: 100, + }); + } + }; + + const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token, progress)), token); if (!extensionResult) { throw new CancellationError(); } diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index 15f94246b25..0914c2e7916 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ProgressOptions, ProgressOptions2 } from 'vscode'; +import { ProgressOptions } from 'vscode'; import { MainThreadProgressShape, ExtHostProgressShape } from './extHost.protocol.js'; import { ProgressLocation } from './extHostTypeConverters.js'; import { Progress, IProgressStep } from '../../../platform/progress/common/progress.js'; @@ -11,7 +11,6 @@ import { CancellationTokenSource, CancellationToken } from '../../../base/common import { throttle } from '../../../base/common/decorators.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { onUnexpectedExternalError } from '../../../base/common/errors.js'; -import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js'; export class ExtHostProgress implements ExtHostProgressShape { @@ -23,15 +22,11 @@ export class ExtHostProgress implements ExtHostProgressShape { this._proxy = proxy; } - async withProgress(extension: IExtensionDescription, options: ProgressOptions | ProgressOptions2, task: (progress: Progress, token: CancellationToken) => Thenable): Promise { + async withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Promise { const handle = this._handles++; const { title, location, cancellable } = options; const source = { label: extension.displayName || extension.name, id: extension.identifier.value }; - if (typeof location === 'object' && location.hasOwnProperty('toolInvocationToken')) { - checkProposedApiEnabled(extension, 'toolProgress'); - } - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, !extension.isUnderDevelopment ? extension.identifier.value : undefined).catch(onUnexpectedExternalError); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index cb208b29f5e..75c7f54d600 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,14 +36,14 @@ import { EndOfLineSequence, TrackedRangeStickiness } from '../../../editor/commo import { ITextEditorOptions } from '../../../platform/editor/common/editor.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from '../../../platform/markers/common/markers.js'; -import { ProgressLocation as MainProgressLocation, ProgressLocationDescriptor as MainProgressLocationDescriptor } from '../../../platform/progress/common/progress.js'; +import { ProgressLocation as MainProgressLocation } from '../../../platform/progress/common/progress.js'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatModel.js'; import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; -import { IToolData, IToolInvocationContext, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; +import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import * as chatProvider from '../../contrib/chat/common/languageModels.js'; import { IChatResponsePromptTsxPart, IChatResponseTextPart } from '../../contrib/chat/common/languageModels.js'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from '../../contrib/debug/common/debug.js'; @@ -1458,13 +1458,9 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation | { viewId: string } | { toolInvocationToken: object | undefined }): MainProgressLocationDescriptor { + export function from(loc: vscode.ProgressLocation | { viewId: string }): MainProgressLocation | string { if (typeof loc === 'object') { - if ('toolInvocationToken' in loc) { - return { toolInvocationCallId: (loc.toolInvocationToken as IToolInvocationContext).callId }; - } else { - return { viewId: loc.viewId }; - } + return loc.viewId; } switch (loc) { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e5562575d4c..bc660c6f7a1 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -692,7 +692,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService); const treeMenus = this.treeDisposables.add(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this.treeDisposables.add(this.instantiationService.createInstance(ResourceLabels, this)); - const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: { viewId: this.id } }, () => task)); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); const aligner = this.treeDisposables.add(new Aligner(this.themeService)); const checkboxStateHandler = this.treeDisposables.add(new CheckboxStateHandler()); const renderer = this.treeDisposables.add(this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes)); @@ -1791,7 +1791,7 @@ export class CustomTreeView extends AbstractTreeView { id: this.id, }); this.createTree(); - this.progressService.withProgress({ location: { viewId: this.id } }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) .then(() => timeout(2000)) .then(() => { this.updateMessage(); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 58200d5fc40..e8dedb875d8 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -651,8 +651,8 @@ export abstract class ViewPane extends Pane implements IView { return this.progressIndicator; } - protected getProgressLocation(): { viewId: string } { - return { viewId: this.viewDescriptorService.getViewContainerByViewId(this.id)!.id }; + protected getProgressLocation(): string { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id; } protected getLocationBasedColors(): IViewPaneLocationColors { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts index a1489fc90b1..20ca1f46cdb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts @@ -24,7 +24,6 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; import { IProgressService } from '../../../../../platform/progress/common/progress.js'; -import { ILanguageModelToolProgressService } from '../../../../services/progress/browser/languageModelToolProgressService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatMarkdownContent, IChatProgressMessage, IChatTerminalToolInvocationData, IChatToolInvocation, IChatToolInvocationSerialized } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; @@ -144,7 +143,6 @@ class ChatToolInvocationSubPart extends Disposable { @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, @ICommandService private readonly commandService: ICommandService, @IMarkerService private readonly markerService: IMarkerService, - @ILanguageModelToolProgressService private readonly languageModelToolProgressService: ILanguageModelToolProgressService, ) { super(); @@ -465,9 +463,9 @@ class ChatToolInvocationSubPart extends Disposable { return part.domNode; } else { const container = document.createElement('div'); - const progressObservable = this._register(this.languageModelToolProgressService.listenForProgress(this.toolInvocation.toolCallId)); + const progressObservable = this.toolInvocation.kind === 'toolInvocation' ? this.toolInvocation.progress : undefined; this._register(autorunWithStore((reader, store) => { - const progress = progressObservable.object.read(reader); + const progress = progressObservable?.read(reader); const part = store.add(this.renderProgressContent(progress?.message || this.toolInvocation.invocationMessage)); dom.reset(container, part.domNode); })); diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index fa680a553d1..4a6b02c24b8 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -12,7 +12,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { Lazy } from '../../../../base/common/lazy.js'; -import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { LRUCache } from '../../../../base/common/map.js'; import { localize } from '../../../../nls.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; @@ -22,6 +22,7 @@ import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import * as JSONContributionRegistry from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { IProgressStep } from '../../../../platform/progress/common/progress.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; @@ -40,6 +41,11 @@ interface IToolEntry { impl?: IToolImpl; } +interface TrackedCall { + invocation?: ChatToolInvocation; + store: IDisposable; +} + export class LanguageModelToolsService extends Disposable implements ILanguageModelToolsService { _serviceBrand: undefined; @@ -53,7 +59,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private _toolContextKeys = new Set(); private readonly _ctxToolsCount: IContextKey; - private _callsByRequestId = new Map(); + private _callsByRequestId = new Map(); private _workspaceToolConfirmStore: Lazy; private _profileToolConfirmStore: Lazy; @@ -91,6 +97,17 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo this._ctxToolsCount = ChatContextKeys.Tools.toolsCount.bindTo(_contextKeyService); } + acceptProgress(sessionId: string | undefined, callId: string, progress: IProgressStep): void { + if (!sessionId) { + return; // not supported, yet + } + + this._callsByRequestId.get(sessionId) + ?.find(call => call.invocation?.toolCallId === callId) + ?.invocation + ?.acceptProgress(progress); + } + registerToolData(toolData: IToolData): IDisposable { if (this._tools.has(toolData.id)) { throw new Error(`Tool "${toolData.id}" is already registered.`); @@ -235,7 +252,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo if (!this._callsByRequestId.has(requestId)) { this._callsByRequestId.set(requestId, []); } - this._callsByRequestId.get(requestId)!.push(store); + const trackedCall: TrackedCall = { store }; + this._callsByRequestId.get(requestId)!.push(trackedCall); const source = new CancellationTokenSource(); store.add(toDisposable(() => { @@ -252,6 +270,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo const prepared = await this.prepareToolInvocation(tool, dto, token); toolInvocation = new ChatToolInvocation(prepared, tool.data, dto.callId); + trackedCall.invocation = toolInvocation; if (this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace)) { toolInvocation.confirmed.complete(true); } @@ -285,7 +304,11 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw new CancellationError(); } - toolResult = await tool.impl.invoke(dto, countTokens, token); + toolResult = await tool.impl.invoke(dto, countTokens, { + report: step => { + toolInvocation?.acceptProgress(step); + } + }, token); this.ensureToolDetails(dto, toolResult, tool.data); this._telemetryService.publicLog2( @@ -403,7 +426,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private cleanupCallDisposables(requestId: string, store: DisposableStore): void { const disposables = this._callsByRequestId.get(requestId); if (disposables) { - const index = disposables.indexOf(store); + const index = disposables.findIndex(d => d.store === store); if (index > -1) { disposables.splice(index, 1); } @@ -417,7 +440,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo cancelToolCallsForRequest(requestId: string): void { const calls = this._callsByRequestId.get(requestId); if (calls) { - calls.forEach(call => call.dispose()); + calls.forEach(call => call.store.dispose()); this._callsByRequestId.delete(requestId); } } @@ -425,7 +448,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo public override dispose(): void { super.dispose(); - this._callsByRequestId.forEach(calls => dispose(calls)); + this._callsByRequestId.forEach(calls => calls.forEach(call => call.store.dispose())); this._ctxToolsCount.reset(); } } diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 9c3570e9f68..e27f5219ea4 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -5,7 +5,9 @@ import { DeferredPromise } from '../../../../../base/common/async.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; +import { observableValue } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; +import { IProgressStep } from '../../../../../platform/progress/common/progress.js'; import { IChatTerminalToolInvocationData, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized } from '../chatService.js'; import { IPreparedToolInvocation, IToolConfirmationMessages, IToolData, IToolResult } from '../languageModelToolsService.js'; @@ -45,6 +47,8 @@ export class ChatToolInvocation implements IChatToolInvocation { public readonly toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData; + public readonly progress = observableValue<{ message?: string; progress: number }>(this, { progress: 0 }); + constructor(preparedInvocation: IPreparedToolInvocation | undefined, toolData: IToolData, public readonly toolCallId: string) { const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${toolData.displayName}"`); const invocationMessage = preparedInvocation?.invocationMessage ?? defaultMessage; @@ -84,6 +88,14 @@ export class ChatToolInvocation implements IChatToolInvocation { return this._confirmationMessages; } + public acceptProgress(step: IProgressStep) { + const prev = this.progress.get(); + this.progress.set({ + progress: step.increment ? (prev.progress + step.increment) : prev.progress, + message: step.message, + }, undefined); + } + public toJSON(): IChatToolInvocationSerialized { return { kind: 'toolInvocationSerialized', diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 3891650fa10..84f6f3309dc 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -7,6 +7,7 @@ import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; +import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; @@ -234,6 +235,7 @@ export interface IChatToolInvocation { invocationMessage: string | IMarkdownString; pastTenseMessage: string | IMarkdownString | undefined; resultDetails: IToolResult['toolResultDetails']; + progress: IObservable<{ message?: string; progress: number }>; readonly toolId: string; readonly toolCallId: string; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 7b37b56bf2f..4f675dee458 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -8,14 +8,15 @@ import { Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; +import { Location } from '../../../../editor/common/languages.js'; import { ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { Location } from '../../../../editor/common/languages.js'; +import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; import { IChatTerminalToolInvocationData, IChatToolInputInvocationData } from './chatService.js'; -import { Schemas } from '../../../../base/common/network.js'; import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js'; export interface IToolData { @@ -83,7 +84,6 @@ export interface IToolInvocation { export interface IToolInvocationContext { sessionId: string; - callId: string | undefined; } export function isToolInvocationContext(obj: any): obj is IToolInvocationContext { @@ -134,7 +134,7 @@ export interface IPreparedToolInvocation { } export interface IToolImpl { - invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise; + invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: IProgress, token: CancellationToken): Promise; prepareToolInvocation?(parameters: any, token: CancellationToken): Promise; } @@ -151,6 +151,7 @@ export interface ILanguageModelToolsService { getTool(id: string): IToolData | undefined; getToolByName(name: string): IToolData | undefined; invokeTool(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise; + acceptProgress(sessionId: string | undefined, callId: string, progress: IProgressStep): void; setToolAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'memory', autoConfirm?: boolean): void; resetToolAutoConfirmation(): void; cancelToolCallsForRequest(requestId: string): void; diff --git a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts index 4dbe039d0e5..95a77534bd4 100644 --- a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts @@ -9,6 +9,7 @@ import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun } from '../../../../../base/common/observable.js'; import { URI, UriComponents } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; +import { IProgress, IProgressStep } from '../../../../../platform/progress/common/progress.js'; import { SaveReason } from '../../../../common/editor.js'; import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; import { CellUri } from '../../../notebook/common/notebookCommon.js'; @@ -42,7 +43,7 @@ export class EditTool implements IToolImpl { @INotebookService private readonly notebookService: INotebookService, ) { } - async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise { + async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, _progress: IProgress, token: CancellationToken): Promise { if (!invocation.context) { throw new Error('toolInvocationToken is required for this tool'); } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts b/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts index b81143b9467..7d361f97bb8 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts @@ -11,6 +11,7 @@ import { ITrustedDomainService } from '../../../url/browser/trustedDomainService import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultTextPart } from '../../common/languageModelToolsService.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { InternalFetchWebPageToolId } from '../../common/tools/tools.js'; +import { IProgress, IProgressStep } from '../../../../../platform/progress/common/progress.js'; export const FetchWebPageToolData: IToolData = { id: InternalFetchWebPageToolId, @@ -41,7 +42,7 @@ export class FetchWebPageTool implements IToolImpl { @ITrustedDomainService private readonly _trustedDomainService: ITrustedDomainService, ) { } - async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _token: CancellationToken): Promise { + async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: IProgress, _token: CancellationToken): Promise { const parsedUriResults = this._parseUris((invocation.parameters as { urls?: string[] }).urls); const validUris = Array.from(parsedUriResults.values()).filter((uri): uri is URI => !!uri); if (!validUris.length) { diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index eb47ea1ac00..5454f7f7ca3 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -149,7 +149,7 @@ suite('LanguageModelToolsService', () => { const toolBarrier = new Barrier(); const toolImpl: IToolImpl = { - invoke: async (invocation, countTokens, cancelToken) => { + invoke: async (invocation, countTokens, progress, cancelToken) => { assert.strictEqual(invocation.callId, '1'); assert.strictEqual(invocation.toolId, 'testTool'); assert.deepStrictEqual(invocation.parameters, { a: 1 }); @@ -174,8 +174,7 @@ suite('LanguageModelToolsService', () => { a: 1 }, context: { - sessionId, - callId: '1', + sessionId }, }; chatService.addSession({ diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index 87a60b7fc0e..2cf7956aa5a 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -6,6 +6,7 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { IProgressStep } from '../../../../../platform/progress/common/progress.js'; import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../common/languageModelToolsService.js'; export class MockLanguageModelToolsService implements ILanguageModelToolsService { @@ -46,6 +47,10 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService return undefined; } + acceptProgress(sessionId: string | undefined, callId: string, progress: IProgressStep): void { + + } + async invokeTool(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise { return { content: [{ kind: 'text', value: 'result' }] diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts index 5ad4c9c995a..0cca619bf96 100644 --- a/src/vs/workbench/contrib/debug/browser/debugProgress.ts +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.ts @@ -37,7 +37,7 @@ export class DebugProgressContribution implements IWorkbenchContribution { }); if (viewsService.isViewContainerVisible(VIEWLET_ID)) { - progressService.withProgress({ location: { viewId: VIEWLET_ID } }, () => promise); + progressService.withProgress({ location: VIEWLET_ID }, () => promise); } const source = debugService.getAdapterManager().getDebuggerLabel(session.configuration.type); progressService.withProgress({ diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index af6ef42997c..10b8d244b2e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -131,7 +131,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } if (state === State.Initializing) { - this.progressService.withProgress({ location: { viewId: VIEWLET_ID }, }, _progress => { + this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => { return new Promise(resolve => this.progressResolve = resolve); }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 8533863a48c..ce3cab343b4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2737,7 +2737,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation | string): Promise { const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....'); return this.withProgress({ - location: typeof progressLocation === 'string' ? { viewId: progressLocation } : (progressLocation ?? ProgressLocation.Extensions), + location: progressLocation ?? ProgressLocation.Extensions, title }, async () => { try { diff --git a/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts b/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts index 3d587cef983..9bf3486ad36 100644 --- a/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts +++ b/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts @@ -9,6 +9,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; import { SortBy } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js'; +import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; import { CountTokensCallback, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js'; import { ExtensionState, IExtensionsWorkbenchService } from '../common/extensions.js'; @@ -64,7 +65,7 @@ export class SearchExtensionsTool implements IToolImpl { @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, ) { } - async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, token: CancellationToken): Promise { + async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: IProgress, token: CancellationToken): Promise { const params = invocation.parameters as InputParams; if (!params.keywords?.length && !params.category) { return { diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 8337c2a9750..44ecb6d65e5 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -98,7 +98,7 @@ export class BrowserFileUpload { ); // Also indicate progress in the files view - this.progressService.withProgress({ location: { viewId: VIEW_ID }, delay: 500 }, () => uploadPromise); + this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => uploadPromise); return uploadPromise; } @@ -417,7 +417,7 @@ export class ExternalFileImport { ); // Also indicate progress in the files view - this.progressService.withProgress({ location: { viewId: VIEW_ID }, delay: 500 }, () => importPromise); + this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => importPromise); return importPromise; } @@ -619,7 +619,7 @@ export class FileDownload { ); // Also indicate progress in the files view - this.progressService.withProgress({ location: { viewId: VIEW_ID }, delay: 500 }, () => downloadPromise); + this.progressService.withProgress({ location: VIEW_ID, delay: 500 }, () => downloadPromise); return downloadPromise; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts b/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts index 51c78d8dd7a..2d3e09b6a4d 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts @@ -16,12 +16,6 @@ const mcpActivationEventPrefix = 'onMcpCollection:'; export const mcpActivationEvent = (collectionId: string) => mcpActivationEventPrefix + collectionId; -const mcpSchemaExampleServer = { - command: 'node', - args: ['my-mcp-server.js'], - env: {}, -}; - export const enum DiscoverySource { ClaudeDesktop = 'claude-desktop', Windsurf = 'windsurf', @@ -55,15 +49,17 @@ export const mcpSchemaExampleServers = { } }; -const httpSchemaExample = { - url: 'http://localhost:3001/mcp', - headers: {}, +const httpSchemaExamples = { + 'my-mcp-server': { + url: 'http://localhost:3001/mcp', + headers: {}, + } }; export const mcpStdioServerSchema: IJSONSchema = { type: 'object', additionalProperties: false, - examples: [mcpSchemaExampleServer], + examples: [mcpSchemaExampleServers['mcp-server-time']], properties: { type: { type: 'string', @@ -110,14 +106,14 @@ export const mcpServerSchema: IJSONSchema = { servers: { examples: [ mcpSchemaExampleServers, - httpSchemaExample, + httpSchemaExamples, ], additionalProperties: { oneOf: [mcpStdioServerSchema, { type: 'object', additionalProperties: false, required: ['url'], - examples: [httpSchemaExample], + examples: [httpSchemaExamples['my-mcp-server']], properties: { type: { type: 'string', diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index fe8660b7625..bf4c7407011 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -14,7 +14,7 @@ import { localize } from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IProgressService } from '../../../../platform/progress/common/progress.js'; +import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; @@ -215,7 +215,6 @@ class McpToolImplementation implements IToolImpl { private readonly _tool: IMcpTool, private readonly _server: IMcpServer, @IProductService private readonly _productService: IProductService, - @IProgressService private readonly _progressService: IProgressService, ) { } async prepareToolInvocation(parameters: any): Promise { @@ -248,7 +247,7 @@ class McpToolImplementation implements IToolImpl { }; } - async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, token: CancellationToken) { + async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, progress: IProgress, token: CancellationToken) { const result: IToolResult = { content: [] @@ -256,12 +255,7 @@ class McpToolImplementation implements IToolImpl { const outputParts: string[] = []; - - const params = invocation.parameters as Record; - const callResult = invocation.context - ? await this._progressService.withProgress({ location: { toolInvocationCallId: invocation.callId } }, p => this._tool.callWithProgress(params, p, token)) - : await this._tool.call(params, token); - + const callResult = await this._tool.callWithProgress(invocation.parameters as Record, progress, token); for (const item of callResult.content) { if (item.type === 'text') { result.content.push({ diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 9d984d1abfc..fdde018b017 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -651,7 +651,7 @@ class SCMHistoryViewPaneActionRunner extends ActionRunner { } protected override runAction(action: IAction, context?: unknown): Promise { - return this._progressService.withProgress({ location: { viewId: HISTORY_VIEW_PANE_ID } }, + return this._progressService.withProgress({ location: HISTORY_VIEW_PANE_ID }, async () => await super.runAction(action, context)); } } @@ -1318,7 +1318,7 @@ export class SCMHistoryViewPane extends ViewPane { await waitForState(firstRepositoryInitialized); // Initial rendering - await this._progressService.withProgress({ location: { viewId: this.id } }, async () => { + await this._progressService.withProgress({ location: this.id }, async () => { await this._treeOperationSequencer.queue(async () => { await this._tree.setInput(this._treeViewModel); this._tree.scrollTop = 0; @@ -1689,7 +1689,7 @@ export class SCMHistoryViewPane extends ViewPane { return this._updateChildrenThrottler.queue( () => this._treeOperationSequencer.queue( async () => { - await this._progressService.withProgress({ location: { viewId: this.id } }, + await this._progressService.withProgress({ location: this.id }, async () => { await this._tree.updateChildren(undefined, undefined, undefined, { // diffIdentityProvider: this._treeIdentityProvider diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index f23dae9b9a5..160800155fc 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1684,7 +1684,7 @@ export class RefreshTestsAction extends Action2 { const progressService = accessor.get(IProgressService); const controllerIds = distinct(elements.filter(isDefined).map(e => e.test.controllerId)); - return progressService.withProgress({ location: { viewId: Testing.ViewletId } }, async () => { + return progressService.withProgress({ location: Testing.ViewletId }, async () => { if (controllerIds.length) { await Promise.all(controllerIds.map(id => testService.refreshTests(id))); } else { diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 7bfe65c7eff..1895434ddba 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -585,7 +585,7 @@ export class OutputPeekTree extends Disposable { return coverageService.closeCoverage(); } progressService.withProgress( - { location: { viewId: options.locationForProgress } }, + { location: options.locationForProgress }, () => coverageService.openCoverage(task, true) ); } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index f48f461e011..d6582ce54f8 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -645,7 +645,7 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let response: Timeline | undefined; try { - response = await this.progressService.withProgress({ location: { viewId: this.id } }, () => request.result); + response = await this.progressService.withProgress({ location: this.id }, () => request.result); } finally { this.pendingRequests.get(request.source)?.dispose(); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 0de87305fc0..6dae8155249 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -267,7 +267,7 @@ export class WebviewViewPane extends ViewPane { } private async withProgress(task: () => Promise): Promise { - return this.progressService.withProgress({ location: { viewId: this.id }, delay: 500 }, task); + return this.progressService.withProgress({ location: this.id, delay: 500 }, task); } override onDidScrollRoot() { diff --git a/src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts b/src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts deleted file mode 100644 index 1e744fd1dc0..00000000000 --- a/src/vs/workbench/services/progress/browser/languageModelToolProgressService.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from '../../../../base/common/event.js'; -import { IReference } from '../../../../base/common/lifecycle.js'; -import { IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; -import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; -import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; - -export const ILanguageModelToolProgressService = createDecorator('languageModelToolProgressService'); - -export interface ILanguageModelToolProgressService { - _serviceBrand: undefined; - - listenForProgress(callId: string): IReference>; - - handleProgress

, R = unknown>(callId: string, callback: (progress: IProgress) => P): P; -} - -export interface ILanguageModelToolProgressDelegate { - onDidCancel: Event; - update(step: IProgressStep): void; - dispose?(): void; -} - -export class LanguageModelToolProgressService implements ILanguageModelToolProgressService { - declare readonly _serviceBrand: undefined; - - private readonly progress = new Map; - rc: number; - }>(); - - listenForProgress(callId: string): IReference> { - let rec = this.progress.get(callId); - if (!rec) { - rec = { value: observableValue(this, { message: undefined, total: 0, done: 0 }), rc: 0 }; - this.progress.set(callId, rec); - } - rec.rc++; - - return { - object: rec.value, - dispose: () => { - if (!--rec.rc) { - this.progress.delete(callId); - } - } - }; - } - - handleProgress

, R = unknown>(callId: string, callback: (progress: IProgress) => P): P { - let rec = this.progress.get(callId); - if (!rec) { - rec = { value: observableValue(this, { message: undefined, total: 0, done: 0 }), rc: 0 }; - this.progress.set(callId, rec); - } - - rec.rc++; - - let last = 0; - const promise = callback({ - report: update => { - if (update.increment) { - last += update.increment; - } - rec.value.set({ message: update.message, total: update.total || 100, done: last }, undefined); - }, - }); - promise.finally(() => { - if (!--rec.rc) { - this.progress.delete(callId); - } - }); - - return promise; - } -} - -registerSingleton(ILanguageModelToolProgressService, LanguageModelToolProgressService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 96d04e24474..cb8831c7e77 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -24,7 +24,6 @@ import { IPaneCompositePartService } from '../../panecomposite/browser/panecompo import { stripIcons } from '../../../../base/common/iconLabels.js'; import { IUserActivityService } from '../../userActivity/common/userActivityService.js'; import { createWorkbenchDialogOptions } from '../../../../platform/dialogs/browser/dialog.js'; -import { ILanguageModelToolProgressService } from './languageModelToolProgressService.js'; export class ProgressService extends Disposable implements IProgressService { @@ -40,7 +39,6 @@ export class ProgressService extends Disposable implements IProgressService { @ILayoutService private readonly layoutService: ILayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IUserActivityService private readonly userActivityService: IUserActivityService, - @ILanguageModelToolProgressService private readonly languageModelToolProgressService: ILanguageModelToolProgressService, ) { super(); } @@ -57,32 +55,24 @@ export class ProgressService extends Disposable implements IProgressService { } }; - const handleViewLocation = (location: { viewId: string }) => { - const viewContainer = this.viewDescriptorService.getViewContainerById(location.viewId); + const handleStringLocation = (location: string) => { + const viewContainer = this.viewDescriptorService.getViewContainerById(location); if (viewContainer) { const viewContainerLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); if (viewContainerLocation !== null) { - return this.withPaneCompositeProgress(location.viewId, viewContainerLocation, task, { ...options, location }); + return this.withPaneCompositeProgress(location, viewContainerLocation, task, { ...options, location }); } } - if (this.viewDescriptorService.getViewDescriptorById(location.viewId) !== null) { - return this.withViewProgress(location.viewId, task, { ...options, location }); + if (this.viewDescriptorService.getViewDescriptorById(location) !== null) { + return this.withViewProgress(location, task, { ...options, location }); } throw new Error(`Bad progress location: ${location}`); }; - if (typeof location === 'object') { - if ('viewId' in location) { - return handleViewLocation(location); - } else if ('toolInvocationCallId' in location) { - if (!location.toolInvocationCallId) { - return task({ report: () => { } }); - } - - return this.languageModelToolProgressService.handleProgress(location.toolInvocationCallId, task); - } + if (typeof location === 'string') { + return handleStringLocation(location); } switch (location) { @@ -112,7 +102,7 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Explorer: return this.withPaneCompositeProgress('workbench.view.explorer', ViewContainerLocation.Sidebar, task, { ...options, location }); case ProgressLocation.Scm: - return handleViewLocation({ viewId: 'workbench.scm' }); + return handleStringLocation('workbench.scm'); case ProgressLocation.Extensions: return this.withPaneCompositeProgress('workbench.view.extensions', ViewContainerLocation.Sidebar, task, { ...options, location }); case ProgressLocation.Dialog: diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index b309fe618e8..df2b9fa8763 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -262,7 +262,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU try { await this.progressService.withProgress({ - location: typeof location === 'string' ? { viewId: location } : location, + location, title: localize('profiles.exporting', "{0}: Exporting...", PROFILES_CATEGORY.value), }, async progress => { const id = await this.pickProfileContentHandler(profile.name); diff --git a/src/vscode-dts/vscode.proposed.toolProgress.d.ts b/src/vscode-dts/vscode.proposed.toolProgress.d.ts index 6969c1f928d..8deea607747 100644 --- a/src/vscode-dts/vscode.proposed.toolProgress.d.ts +++ b/src/vscode-dts/vscode.proposed.toolProgress.d.ts @@ -5,30 +5,21 @@ declare module 'vscode' { - export interface ProgressOptions2 extends Omit { - location: ProgressLocation | { - /** - * The identifier of a view for which progress should be shown. - */ - viewId: string; - } | { - /** - * An invocation token for progress shown while a {@link LanguageModelTool} is running. - */ - toolInvocationToken: ChatParticipantToolToken | undefined; - }; + /** + * todo@connor4312: `vscode.window.withProgres` can take this interface as well. + */ + export interface ProgressStep { + /** + * A progress message that represents a chunk of work + */ + message?: string; + /** + * An increment for discrete progress. Increments will be summed up until 100 (100%) is reached + */ + increment?: number; } - export namespace window { - export function withProgress(options: ProgressOptions2, task: (progress: Progress<{ - /** - * A progress message that represents a chunk of work - */ - message?: string; - /** - * An increment for discrete progress. Increments will be summed up until 100% is reached - */ - increment?: number; - }>, token: CancellationToken) => Thenable): Thenable; + export interface LanguageModelTool { + invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken, progress: Progress): ProviderResult; } } From 9ea17f8a1c8d6c3d3b14daa74bf787c6c55cbace Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 22 Apr 2025 09:19:58 -0700 Subject: [PATCH 3/6] pr comments --- .../browser/mainThreadLanguageModelTools.ts | 21 +++++++++------- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageModelTools.ts | 24 ++++++++++--------- .../chatToolInvocationPart.ts | 2 -- .../contrib/chat/browser/chatSetup.ts | 4 ++-- .../chat/browser/languageModelToolsService.ts | 18 +++----------- .../chat/common/languageModelToolsService.ts | 1 - .../vscode.proposed.toolProgress.d.ts | 6 ++--- 8 files changed, 34 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts index 88ab6e0e598..0a7db37edb5 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts @@ -6,7 +6,7 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; import { revive } from '../../../base/common/marshalling.js'; -import { IProgressStep } from '../../../platform/progress/common/progress.js'; +import { IProgress, IProgressStep } from '../../../platform/progress/common/progress.js'; import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolInvocation, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; @@ -17,7 +17,10 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre private readonly _proxy: ExtHostLanguageModelToolsShape; private readonly _tools = this._register(new DisposableMap()); - private readonly _countTokenCallbacks = new Map(); + private readonly _runningToolCalls = new Map; + }>(); constructor( extHostContext: IExtHostContext, @@ -46,30 +49,30 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre }; } - $acceptToolProgress(requestId: string | undefined, callId: string, progress: IProgressStep): void { - this._languageModelToolsService.acceptProgress(requestId, callId, progress); + $acceptToolProgress(callId: string, progress: IProgressStep): void { + this._runningToolCalls.get(callId)?.progress.report(progress); } $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise { - const fn = this._countTokenCallbacks.get(callId); + const fn = this._runningToolCalls.get(callId); if (!fn) { throw new Error(`Tool invocation call ${callId} not found`); } - return fn(input, token); + return fn.countTokens(input, token); } $registerTool(id: string): void { const disposable = this._languageModelToolsService.registerToolImplementation( id, { - invoke: async (dto, countTokens, _progress, token) => { + invoke: async (dto, countTokens, progress, token) => { try { - this._countTokenCallbacks.set(dto.callId, countTokens); + this._runningToolCalls.set(dto.callId, { countTokens, progress }); const resultDto = await this._proxy.$invokeTool(dto, token); return revive(resultDto) as IToolResult; } finally { - this._countTokenCallbacks.delete(dto.callId); + this._runningToolCalls.delete(dto.callId); } }, prepareToolInvocation: (parameters, token) => this._proxy.$prepareToolInvocation(id, parameters, token), diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3e50b7d456e..9eeabd64abd 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1380,7 +1380,7 @@ export type IToolDataDto = Omit; export interface MainThreadLanguageModelToolsShape extends IDisposable { $getTools(): Promise[]>; - $acceptToolProgress(requestId: string | undefined, callId: string, progress: IProgressStep): void; + $acceptToolProgress(callId: string, progress: IProgressStep): void; $invokeTool(dto: IToolInvocation, token?: CancellationToken): Promise>; $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise; $registerTool(id: string): void; diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 82de8a39f02..faaa208029e 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -138,18 +138,20 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - const progress: vscode.Progress<{ message?: string; increment?: number }> = { - report: value => { - checkProposedApiEnabled(item.extension, 'toolProgress'); - this._proxy.$acceptToolProgress(dto.chatRequestId, dto.callId, { - message: value.message, - increment: value.increment, - total: 100, - }); - } - }; + let progress: vscode.Progress<{ message?: string; increment?: number }> | undefined; + if (isProposedApiEnabled(item.extension, 'toolProgress')) { + progress = { + report: value => { + this._proxy.$acceptToolProgress(dto.callId, { + message: value.message, + increment: value.increment, + total: 100, + }); + } + }; + } - const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token, progress)), token); + const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token, progress!)), token); if (!extensionResult) { throw new CancellationError(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts index 20ca1f46cdb..24a8d787794 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts @@ -23,7 +23,6 @@ import { IContextKeyService } from '../../../../../platform/contextkey/common/co import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; -import { IProgressService } from '../../../../../platform/progress/common/progress.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatMarkdownContent, IChatProgressMessage, IChatTerminalToolInvocationData, IChatToolInvocation, IChatToolInvocationSerialized } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; @@ -66,7 +65,6 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa codeBlockModelCollection: CodeBlockModelCollection, codeBlockStartIndex: number, @IInstantiationService instantiationService: IInstantiationService, - @IProgressService progressService: IProgressService, ) { super(); diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 7b15fa89f3f..2fb469b94f2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -37,7 +37,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import product from '../../../../platform/product/common/product.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IProgress, IProgressService, IProgressStep, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js'; @@ -518,7 +518,7 @@ class SetupTool extends Disposable implements IToolImpl { super(); } - invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise { + invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: IProgress, token: CancellationToken): Promise { const result: IToolResult = { content: [ { diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 4a6b02c24b8..b21654e4d9e 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -22,7 +22,6 @@ import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import * as JSONContributionRegistry from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { IProgressStep } from '../../../../platform/progress/common/progress.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; @@ -41,7 +40,7 @@ interface IToolEntry { impl?: IToolImpl; } -interface TrackedCall { +interface ITrackedCall { invocation?: ChatToolInvocation; store: IDisposable; } @@ -59,7 +58,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private _toolContextKeys = new Set(); private readonly _ctxToolsCount: IContextKey; - private _callsByRequestId = new Map(); + private _callsByRequestId = new Map(); private _workspaceToolConfirmStore: Lazy; private _profileToolConfirmStore: Lazy; @@ -97,17 +96,6 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo this._ctxToolsCount = ChatContextKeys.Tools.toolsCount.bindTo(_contextKeyService); } - acceptProgress(sessionId: string | undefined, callId: string, progress: IProgressStep): void { - if (!sessionId) { - return; // not supported, yet - } - - this._callsByRequestId.get(sessionId) - ?.find(call => call.invocation?.toolCallId === callId) - ?.invocation - ?.acceptProgress(progress); - } - registerToolData(toolData: IToolData): IDisposable { if (this._tools.has(toolData.id)) { throw new Error(`Tool "${toolData.id}" is already registered.`); @@ -252,7 +240,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo if (!this._callsByRequestId.has(requestId)) { this._callsByRequestId.set(requestId, []); } - const trackedCall: TrackedCall = { store }; + const trackedCall: ITrackedCall = { store }; this._callsByRequestId.get(requestId)!.push(trackedCall); const source = new CancellationTokenSource(); diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index f78950c320f..75332b21169 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -152,7 +152,6 @@ export interface ILanguageModelToolsService { getTool(id: string): IToolData | undefined; getToolByName(name: string): IToolData | undefined; invokeTool(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise; - acceptProgress(sessionId: string | undefined, callId: string, progress: IProgressStep): void; setToolAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'memory', autoConfirm?: boolean): void; resetToolAutoConfirmation(): void; cancelToolCallsForRequest(requestId: string): void; diff --git a/src/vscode-dts/vscode.proposed.toolProgress.d.ts b/src/vscode-dts/vscode.proposed.toolProgress.d.ts index 8deea607747..51562cd6ee3 100644 --- a/src/vscode-dts/vscode.proposed.toolProgress.d.ts +++ b/src/vscode-dts/vscode.proposed.toolProgress.d.ts @@ -6,9 +6,9 @@ declare module 'vscode' { /** - * todo@connor4312: `vscode.window.withProgres` can take this interface as well. + * A progress update during an {@link LanguageModelTool.invoke} call. */ - export interface ProgressStep { + export interface ToolProgressStep { /** * A progress message that represents a chunk of work */ @@ -20,6 +20,6 @@ declare module 'vscode' { } export interface LanguageModelTool { - invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken, progress: Progress): ProviderResult; + invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken, progress: Progress): ProviderResult; } } From c6c85b3e9d3c5b200ee446a209be5473fbcef00c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 22 Apr 2025 10:33:18 -0700 Subject: [PATCH 4/6] pass in an options object instead --- src/vs/workbench/api/common/extHostLanguageModelTools.ts | 8 ++++---- src/vscode-dts/vscode.proposed.toolProgress.d.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index faaa208029e..2cbe65e5701 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -112,9 +112,10 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape throw new Error(`Unknown tool ${dto.toolId}`); } - const options: vscode.LanguageModelToolInvocationOptions = { + const options: vscode.LanguageModelToolInvocation = { input: dto.parameters, toolInvocationToken: dto.context as vscode.ChatParticipantToolToken | undefined, + progress: undefined!, }; if (isProposedApiEnabled(item.extension, 'chatParticipantPrivate')) { options.chatRequestId = dto.chatRequestId; @@ -138,9 +139,8 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - let progress: vscode.Progress<{ message?: string; increment?: number }> | undefined; if (isProposedApiEnabled(item.extension, 'toolProgress')) { - progress = { + options.progress = { report: value => { this._proxy.$acceptToolProgress(dto.callId, { message: value.message, @@ -151,7 +151,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token, progress!)), token); + const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token)), token); if (!extensionResult) { throw new CancellationError(); } diff --git a/src/vscode-dts/vscode.proposed.toolProgress.d.ts b/src/vscode-dts/vscode.proposed.toolProgress.d.ts index 51562cd6ee3..6eed0305a81 100644 --- a/src/vscode-dts/vscode.proposed.toolProgress.d.ts +++ b/src/vscode-dts/vscode.proposed.toolProgress.d.ts @@ -19,7 +19,14 @@ declare module 'vscode' { increment?: number; } + export interface LanguageModelToolInvocation extends LanguageModelToolInvocationOptions { + /** + * Progress interface to report updates as the tool is running. + */ + progress: Progress; + } + export interface LanguageModelTool { - invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken, progress: Progress): ProviderResult; + invoke(invocation: LanguageModelToolInvocation, token: CancellationToken): ProviderResult; } } From dd5b6c1235e3c93a4ee59b81f090f20c5733326c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 23 Apr 2025 10:58:44 -0700 Subject: [PATCH 5/6] Revert "pass in an options object instead" This reverts commit c6c85b3e9d3c5b200ee446a209be5473fbcef00c. --- src/vs/workbench/api/common/extHostLanguageModelTools.ts | 8 ++++---- src/vscode-dts/vscode.proposed.toolProgress.d.ts | 9 +-------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 2cbe65e5701..faaa208029e 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -112,10 +112,9 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape throw new Error(`Unknown tool ${dto.toolId}`); } - const options: vscode.LanguageModelToolInvocation = { + const options: vscode.LanguageModelToolInvocationOptions = { input: dto.parameters, toolInvocationToken: dto.context as vscode.ChatParticipantToolToken | undefined, - progress: undefined!, }; if (isProposedApiEnabled(item.extension, 'chatParticipantPrivate')) { options.chatRequestId = dto.chatRequestId; @@ -139,8 +138,9 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } + let progress: vscode.Progress<{ message?: string; increment?: number }> | undefined; if (isProposedApiEnabled(item.extension, 'toolProgress')) { - options.progress = { + progress = { report: value => { this._proxy.$acceptToolProgress(dto.callId, { message: value.message, @@ -151,7 +151,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token)), token); + const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token, progress!)), token); if (!extensionResult) { throw new CancellationError(); } diff --git a/src/vscode-dts/vscode.proposed.toolProgress.d.ts b/src/vscode-dts/vscode.proposed.toolProgress.d.ts index 6eed0305a81..51562cd6ee3 100644 --- a/src/vscode-dts/vscode.proposed.toolProgress.d.ts +++ b/src/vscode-dts/vscode.proposed.toolProgress.d.ts @@ -19,14 +19,7 @@ declare module 'vscode' { increment?: number; } - export interface LanguageModelToolInvocation extends LanguageModelToolInvocationOptions { - /** - * Progress interface to report updates as the tool is running. - */ - progress: Progress; - } - export interface LanguageModelTool { - invoke(invocation: LanguageModelToolInvocation, token: CancellationToken): ProviderResult; + invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken, progress: Progress): ProviderResult; } } From 41cc1e28362b0511ac7c39ff42fe1bafc27c4e71 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 23 Apr 2025 11:09:15 -0700 Subject: [PATCH 6/6] update with api feedback --- .../api/browser/mainThreadLanguageModelTools.ts | 7 +++---- src/vs/workbench/api/common/extHost.protocol.ts | 4 ++-- .../api/common/extHostLanguageModelTools.ts | 7 ++++--- .../workbench/contrib/chat/browser/chatSetup.ts | 12 ++++++------ .../chatProgressTypes/chatToolInvocation.ts | 7 +++---- .../workbench/contrib/chat/common/chatService.ts | 2 +- .../chat/common/languageModelToolsService.ts | 12 ++++++++++-- .../contrib/chat/common/tools/editFileTool.ts | 5 ++--- .../chat/electron-sandbox/tools/fetchPageTool.ts | 9 ++++----- .../extensions/common/searchExtensionsTool.ts | 7 +++---- src/vs/workbench/contrib/mcp/common/mcpServer.ts | 16 ++++++++-------- .../workbench/contrib/mcp/common/mcpService.ts | 5 ++--- src/vs/workbench/contrib/mcp/common/mcpTypes.ts | 6 +++--- src/vscode-dts/vscode.proposed.toolProgress.d.ts | 2 +- 14 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts index 0a7db37edb5..62fcc73e15e 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts @@ -6,8 +6,7 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; import { revive } from '../../../base/common/marshalling.js'; -import { IProgress, IProgressStep } from '../../../platform/progress/common/progress.js'; -import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolInvocation, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; +import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolInvocation, IToolProgressStep, IToolResult, ToolProgress } from '../../contrib/chat/common/languageModelToolsService.js'; import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; import { ExtHostContext, ExtHostLanguageModelToolsShape, MainContext, MainThreadLanguageModelToolsShape } from '../common/extHost.protocol.js'; @@ -19,7 +18,7 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre private readonly _tools = this._register(new DisposableMap()); private readonly _runningToolCalls = new Map; + progress: ToolProgress; }>(); constructor( @@ -49,7 +48,7 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre }; } - $acceptToolProgress(callId: string, progress: IProgressStep): void { + $acceptToolProgress(callId: string, progress: IToolProgressStep): void { this._runningToolCalls.get(callId)?.progress.report(progress); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9eeabd64abd..d72909f4cae 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -59,7 +59,7 @@ import { IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatPro import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata, ILanguageModelChatSelector, ILanguageModelsChangeEvent } from '../../contrib/chat/common/languageModels.js'; -import { IPreparedToolInvocation, IToolData, IToolInvocation, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; +import { IPreparedToolInvocation, IToolData, IToolInvocation, IToolProgressStep, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugTestRunReference, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from '../../contrib/debug/common/debug.js'; import { McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch } from '../../contrib/mcp/common/mcpTypes.js'; import * as notebookCommon from '../../contrib/notebook/common/notebookCommon.js'; @@ -1380,7 +1380,7 @@ export type IToolDataDto = Omit; export interface MainThreadLanguageModelToolsShape extends IDisposable { $getTools(): Promise[]>; - $acceptToolProgress(callId: string, progress: IProgressStep): void; + $acceptToolProgress(callId: string, progress: IToolProgressStep): void; $invokeTool(dto: IToolInvocation, token?: CancellationToken): Promise>; $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise; $registerTool(id: string): void; diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index faaa208029e..9d3272028ee 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -138,12 +138,12 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - let progress: vscode.Progress<{ message?: string; increment?: number }> | undefined; + let progress: vscode.Progress<{ message?: string | vscode.MarkdownString; increment?: number }> | undefined; if (isProposedApiEnabled(item.extension, 'toolProgress')) { progress = { report: value => { this._proxy.$acceptToolProgress(dto.callId, { - message: value.message, + message: typeConvert.MarkdownString.fromStrict(value.message), increment: value.increment, total: 100, }); @@ -151,7 +151,8 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }; } - const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token, progress!)), token); + // todo: 'any' cast because TS can't handle the overloads + const extensionResult = await raceCancellation(Promise.resolve((item.tool.invoke as any)(options, token, progress!)), token); if (!extensionResult) { throw new CancellationError(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 2fb469b94f2..3f7389ecdde 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -7,6 +7,7 @@ import { $ } from '../../../../base/browser/dom.js'; import { Dialog } from '../../../../base/browser/ui/dialog/dialog.js'; import { toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../base/common/actions.js'; import { timeout } from '../../../../base/common/async.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { isCancellationError } from '../../../../base/common/errors.js'; @@ -37,7 +38,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import product from '../../../../platform/product/common/product.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IProgress, IProgressService, IProgressStep, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js'; @@ -52,11 +53,13 @@ import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolProgress } from '../../chat/common/languageModelToolsService.js'; import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService } from '../common/chatEntitlementService.js'; -import { IChatRequestModel, ChatRequestModel, ChatModel, IChatRequestVariableData, IChatRequestToolEntry } from '../common/chatModel.js'; +import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestToolEntry, IChatRequestVariableData } from '../common/chatModel.js'; +import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; import { IChatProgress, IChatService } from '../common/chatService.js'; import { ChatAgentLocation, ChatConfiguration, ChatMode, validateChatMode } from '../common/constants.js'; import { ILanguageModelsService } from '../common/languageModels.js'; @@ -64,9 +67,6 @@ import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from './acti import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; import './media/chatSetup.css'; -import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; -import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -518,7 +518,7 @@ class SetupTool extends Disposable implements IToolImpl { super(); } - invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: IProgress, token: CancellationToken): Promise { + invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise { const result: IToolResult = { content: [ { diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 6df0d41ecaf..b9b27e255f4 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -7,9 +7,8 @@ import { DeferredPromise } from '../../../../../base/common/async.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { observableValue } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; -import { IProgressStep } from '../../../../../platform/progress/common/progress.js'; import { IChatTerminalToolInvocationData, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized } from '../chatService.js'; -import { IPreparedToolInvocation, IToolConfirmationMessages, IToolData, IToolResult } from '../languageModelToolsService.js'; +import { IPreparedToolInvocation, IToolConfirmationMessages, IToolData, IToolProgressStep, IToolResult } from '../languageModelToolsService.js'; export class ChatToolInvocation implements IChatToolInvocation { public readonly kind: 'toolInvocation' = 'toolInvocation'; @@ -47,7 +46,7 @@ export class ChatToolInvocation implements IChatToolInvocation { public readonly toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData; - public readonly progress = observableValue<{ message?: string; progress: number }>(this, { progress: 0 }); + public readonly progress = observableValue<{ message?: string | IMarkdownString; progress: number }>(this, { progress: 0 }); constructor(preparedInvocation: IPreparedToolInvocation | undefined, toolData: IToolData, public readonly toolCallId: string) { const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${toolData.displayName}"`); @@ -88,7 +87,7 @@ export class ChatToolInvocation implements IChatToolInvocation { return this._confirmationMessages; } - public acceptProgress(step: IProgressStep) { + public acceptProgress(step: IToolProgressStep) { const prev = this.progress.get(); this.progress.set({ progress: step.increment ? (prev.progress + step.increment) : prev.progress, diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 3bd7b542c92..9e9cead60f9 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -235,7 +235,7 @@ export interface IChatToolInvocation { invocationMessage: string | IMarkdownString; pastTenseMessage: string | IMarkdownString | undefined; resultDetails: IToolResult['toolResultDetails']; - progress: IObservable<{ message?: string; progress: number }>; + progress: IObservable<{ message?: string | IMarkdownString; progress: number }>; readonly toolId: string; readonly toolCallId: string; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 75332b21169..b574302dbc2 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -15,7 +15,7 @@ import { Location } from '../../../../editor/common/languages.js'; import { ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; +import { IProgress } from '../../../../platform/progress/common/progress.js'; import { IChatTerminalToolInvocationData, IChatToolInputInvocationData } from './chatService.js'; import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js'; @@ -41,6 +41,14 @@ export interface IToolData { supportsToolPicker?: boolean; } +export interface IToolProgressStep { + readonly message: string | IMarkdownString | undefined; + readonly increment: number | undefined; + readonly total: number | undefined; +} + +export type ToolProgress = IProgress; + export type ToolDataSource = | { type: 'extension'; @@ -135,7 +143,7 @@ export interface IPreparedToolInvocation { } export interface IToolImpl { - invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: IProgress, token: CancellationToken): Promise; + invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise; prepareToolInvocation?(parameters: any, token: CancellationToken): Promise; } diff --git a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts index 8206d59c212..8c4dc2cd9a4 100644 --- a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts @@ -9,7 +9,6 @@ import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun } from '../../../../../base/common/observable.js'; import { URI, UriComponents } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; -import { IProgress, IProgressStep } from '../../../../../platform/progress/common/progress.js'; import { SaveReason } from '../../../../common/editor.js'; import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; import { CellUri } from '../../../notebook/common/notebookCommon.js'; @@ -17,7 +16,7 @@ import { INotebookService } from '../../../notebook/common/notebookService.js'; import { ICodeMapperService } from '../../common/chatCodeMapperService.js'; import { ChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; -import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../common/languageModelToolsService.js'; +import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolProgress } from '../../common/languageModelToolsService.js'; export const ExtensionEditToolId = 'vscode_editFile'; export const InternalEditToolId = 'vscode_editFile_internal'; @@ -43,7 +42,7 @@ export class EditTool implements IToolImpl { @INotebookService private readonly notebookService: INotebookService, ) { } - async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, _progress: IProgress, token: CancellationToken): Promise { + async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { if (!invocation.context) { throw new Error('toolInvocationToken is required for this tool'); } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts b/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts index 7d361f97bb8..632a9b9bbc3 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/tools/fetchPageTool.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../nls.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { URI } from '../../../../../base/common/uri.js'; +import { localize } from '../../../../../nls.js'; import { IWebContentExtractorService } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { ITrustedDomainService } from '../../../url/browser/trustedDomainService.js'; -import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultTextPart } from '../../common/languageModelToolsService.js'; -import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultTextPart, ToolProgress } from '../../common/languageModelToolsService.js'; import { InternalFetchWebPageToolId } from '../../common/tools/tools.js'; -import { IProgress, IProgressStep } from '../../../../../platform/progress/common/progress.js'; export const FetchWebPageToolData: IToolData = { id: InternalFetchWebPageToolId, @@ -42,7 +41,7 @@ export class FetchWebPageTool implements IToolImpl { @ITrustedDomainService private readonly _trustedDomainService: ITrustedDomainService, ) { } - async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: IProgress, _token: CancellationToken): Promise { + async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, _token: CancellationToken): Promise { const parsedUriResults = this._parseUris((invocation.parameters as { urls?: string[] }).urls); const validUris = Array.from(parsedUriResults.values()).filter((uri): uri is URI => !!uri); if (!validUris.length) { diff --git a/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts b/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts index 98d19d83a65..9668c594e98 100644 --- a/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts +++ b/src/vs/workbench/contrib/extensions/common/searchExtensionsTool.ts @@ -9,9 +9,8 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; import { SortBy } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js'; -import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; -import { CountTokensCallback, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js'; -import { ExtensionState, IExtensionsWorkbenchService, IExtension } from '../common/extensions.js'; +import { CountTokensCallback, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolProgress } from '../../chat/common/languageModelToolsService.js'; +import { ExtensionState, IExtension, IExtensionsWorkbenchService } from '../common/extensions.js'; export const SearchExtensionsToolId = 'vscode_searchExtensions_internal'; @@ -73,7 +72,7 @@ export class SearchExtensionsTool implements IToolImpl { @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, ) { } - async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: IProgress, token: CancellationToken): Promise { + async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { const params = invocation.parameters as InputParams; if (!params.keywords?.length && !params.category && !params.ids?.length) { return { diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index c6e9129988c..01f828d7211 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -4,31 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import { raceCancellationError, Sequencer } from '../../../../base/common/async.js'; -import * as json from '../../../../base/common/json.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import * as json from '../../../../base/common/json.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { LRUCache } from '../../../../base/common/map.js'; import { autorun, autorunWithStore, derived, disposableObservableValue, IObservable, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; +import { localize } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogger, ILoggerService } from '../../../../platform/log/common/log.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IOutputService } from '../../../services/output/common/output.js'; +import { ToolProgress } from '../../chat/common/languageModelToolsService.js'; import { mcpActivationEvent } from './mcpConfiguration.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; import { McpServerRequestHandler } from './mcpServerRequestHandler.js'; import { extensionMcpCollectionPrefix, IMcpServer, IMcpServerConnection, IMcpTool, McpCollectionReference, McpConnectionFailedError, McpConnectionState, McpDefinitionReference, McpServerDefinition, McpServerToolsState } from './mcpTypes.js'; import { MCP } from './modelContextProtocol.js'; -import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; -import { localize } from '../../../../nls.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; -import { generateUuid } from '../../../../base/common/uuid.js'; type ServerBootData = { supportsLogging: boolean; @@ -502,7 +502,7 @@ export class McpTool implements IMcpTool { return this._server.callOn(h => h.callTool({ name, arguments: params }, token), token); } - callWithProgress(params: Record, progress: IProgress, token?: CancellationToken): Promise { + callWithProgress(params: Record, progress: ToolProgress, token?: CancellationToken): Promise { // serverToolName is always set now, but older cache entries (from 1.99-Insiders) may not have it. const name = this._definition.serverToolName ?? this._definition.name; const progressToken = generateUuid(); diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index bf4c7407011..a3578344379 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -14,9 +14,8 @@ import { localize } from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; import { StorageScope } from '../../../../platform/storage/common/storage.js'; -import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../../chat/common/languageModelToolsService.js'; +import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolProgress } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; import { McpServer, McpServerMetadataCache } from './mcpServer.js'; import { IMcpServer, IMcpService, IMcpTool, McpCollectionDefinition, McpServerDefinition, McpServerToolsState } from './mcpTypes.js'; @@ -247,7 +246,7 @@ class McpToolImplementation implements IToolImpl { }; } - async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, progress: IProgress, token: CancellationToken) { + async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken) { const result: IToolResult = { content: [] diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 529f0e9fb66..d87721225b8 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { equals as arraysEqual } from '../../../../base/common/arrays.js'; import { assertNever } from '../../../../base/common/assert.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { equals as objectsEqual } from '../../../../base/common/objects.js'; -import { equals as arraysEqual } from '../../../../base/common/arrays.js'; import { IObservable } from '../../../../base/common/observable.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { Location } from '../../../../editor/common/languages.js'; @@ -17,9 +17,9 @@ import { ExtensionIdentifier } from '../../../../platform/extensions/common/exte import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { IWorkspaceFolderData } from '../../../../platform/workspace/common/workspace.js'; +import { ToolProgress } from '../../chat/common/languageModelToolsService.js'; import { McpServerRequestHandler } from './mcpServerRequestHandler.js'; import { MCP } from './modelContextProtocol.js'; -import { IProgress, IProgressStep } from '../../../../platform/progress/common/progress.js'; export const extensionMcpCollectionPrefix = 'ext.'; @@ -264,7 +264,7 @@ export interface IMcpTool { /** * Identical to {@link call}, but reports progress. */ - callWithProgress(params: Record, progress: IProgress, token?: CancellationToken): Promise; + callWithProgress(params: Record, progress: ToolProgress, token?: CancellationToken): Promise; } export const enum McpServerTransportType { diff --git a/src/vscode-dts/vscode.proposed.toolProgress.d.ts b/src/vscode-dts/vscode.proposed.toolProgress.d.ts index 51562cd6ee3..0d20f626cc1 100644 --- a/src/vscode-dts/vscode.proposed.toolProgress.d.ts +++ b/src/vscode-dts/vscode.proposed.toolProgress.d.ts @@ -12,7 +12,7 @@ declare module 'vscode' { /** * A progress message that represents a chunk of work */ - message?: string; + message?: string | MarkdownString; /** * An increment for discrete progress. Increments will be summed up until 100 (100%) is reached */