From 720f2eb1fb2bbb28bb87d037a09295ea666ae86c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 24 Jan 2025 09:59:58 -0800 Subject: [PATCH] Implement past tense invocation message and tooltip for tool calls (#238635) * Implement past tense invocation message and tooltip for tool calls * Fix * Clean up --- .../api/common/extHostLanguageModelTools.ts | 14 +++++++----- .../chatToolInvocationPart.ts | 22 +++++++++++++++---- .../chat/browser/languageModelToolsService.ts | 2 +- .../contrib/chat/browser/media/chat.css | 21 +++++++++++------- .../chatProgressTypes/chatToolInvocation.ts | 4 ++++ .../contrib/chat/common/chatService.ts | 4 ++++ .../chat/common/languageModelToolsService.ts | 2 ++ ...scode.proposed.chatParticipantPrivate.d.ts | 5 +++++ 8 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index c5d84525695..d07093176f2 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -12,7 +12,7 @@ import { revive } from '../../../base/common/marshalling.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { IPreparedToolInvocation, isToolInvocationContext, IToolInvocation, IToolInvocationContext, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; -import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; +import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js'; import * as typeConvert from './extHostTypeConverters.js'; @@ -136,16 +136,18 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape return undefined; } + if (result.pastTenseMessage || result.tooltip) { + checkProposedApiEnabled(item.extension, 'chatParticipantPrivate'); + } + return { confirmationMessages: result.confirmationMessages ? { title: result.confirmationMessages.title, message: typeof result.confirmationMessages.message === 'string' ? result.confirmationMessages.message : typeConvert.MarkdownString.from(result.confirmationMessages.message), } : undefined, - invocationMessage: typeof result.invocationMessage === 'string' ? - result.invocationMessage : - (result.invocationMessage ? - typeConvert.MarkdownString.from(result.invocationMessage) : - undefined), + invocationMessage: typeConvert.MarkdownString.fromStrict(result.invocationMessage), + pastTenseMessage: typeConvert.MarkdownString.fromStrict(result.pastTenseMessage), + tooltip: result.tooltip ? typeConvert.MarkdownString.fromStrict(result.tooltip) : undefined, }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts index 2d065692bdc..ee51460fad8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInvocationPart.ts @@ -6,10 +6,11 @@ import * as dom from '../../../../../base/browser/dom.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Relay } from '../../../../../base/common/event.js'; -import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressMessage, IChatToolInvocation, IChatToolInvocationSerialized } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; @@ -75,6 +76,7 @@ class ChatToolInvocationSubPart extends Disposable { context: IChatContentPartRenderContext, renderer: MarkdownRenderer, @IInstantiationService instantiationService: IInstantiationService, + @IHoverService hoverService: IHoverService, ) { super(); @@ -93,9 +95,17 @@ class ChatToolInvocationSubPart extends Disposable { this._onDidChangeHeight.input = confirmWidget.onDidChangeHeight; toolInvocation.confirmed.p.then(() => this._onNeedsRerender.fire()); } else { - const content = typeof toolInvocation.invocationMessage === 'string' ? - new MarkdownString().appendText(toolInvocation.invocationMessage + '…') : - new MarkdownString(toolInvocation.invocationMessage.value + '…'); + let content: IMarkdownString; + if (toolInvocation.isComplete && toolInvocation.isConfirmed !== false && toolInvocation.pastTenseMessage) { + content = typeof toolInvocation.pastTenseMessage === 'string' ? + new MarkdownString().appendText(toolInvocation.pastTenseMessage) : + toolInvocation.pastTenseMessage; + } else { + content = typeof toolInvocation.invocationMessage === 'string' ? + new MarkdownString().appendText(toolInvocation.invocationMessage + '…') : + new MarkdownString(toolInvocation.invocationMessage.value + '…'); + } + const progressMessage: IChatProgressMessage = { kind: 'progressMessage', content @@ -105,6 +115,10 @@ class ChatToolInvocationSubPart extends Disposable { toolInvocation.isComplete ? Codicon.check : undefined; const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, progressMessage, renderer, context, undefined, true, iconOverride)); + if (toolInvocation.tooltip) { + this._register(hoverService.setupDelayedHover(progressPart.domNode, { content: toolInvocation.tooltip, additionalClasses: ['chat-tool-hover'] })); + } + this.domNode = progressPart.domNode; } diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index d53c0db6859..9e3d66aeca9 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -187,7 +187,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${tool.data.displayName}"`); const invocationMessage = prepared?.invocationMessage ?? defaultMessage; if (tool.data.id !== 'vscode_editFile') { - toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.confirmationMessages); + toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.pastTenseMessage, prepared?.tooltip, prepared?.confirmationMessages); model.acceptResponseProgress(request, toolInvocation); if (prepared?.confirmationMessages) { const userConfirmed = await toolInvocation.confirmed.p; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index d315ab07737..d35fcc065f5 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -392,14 +392,15 @@ have to be updated for changes to the rules above, or to support more deeply nes max-width: 100%; } -.interactive-item-container .monaco-tokenized-source, -.interactive-item-container code { - font-family: var(--monaco-monospace-font); - font-size: 12px; - color: var(--vscode-textPreformat-foreground); - background-color: var(--vscode-textPreformat-background); - padding: 1px 3px; - border-radius: 4px; +.chat-tool-hover, .interactive-item-container { + .monaco-tokenized-source, code { + font-family: var(--monaco-monospace-font); + font-size: 12px; + color: var(--vscode-textPreformat-foreground); + background-color: var(--vscode-textPreformat-background); + padding: 1px 3px; + border-radius: 4px; + } } .interactive-item-container.interactive-item-compact { @@ -1447,6 +1448,10 @@ have to be updated for changes to the rules above, or to support more deeply nes padding: 4px 8px; } +.interactive-item-container .chat-confirmation-widget .rendered-markdown [data-code] { + margin-bottom: 8px; +} + .interactive-item-container .chat-command-button .monaco-button .codicon { margin-left: 0; margin-top: 1px; diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 76e572c91df..2b216ac49bc 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -38,6 +38,8 @@ export class ChatToolInvocation implements IChatToolInvocation { constructor( public readonly invocationMessage: string | IMarkdownString, + public readonly pastTenseMessage: string | IMarkdownString | undefined, + public readonly tooltip: string | IMarkdownString | undefined, private _confirmationMessages: IToolConfirmationMessages | undefined) { if (!_confirmationMessages) { // No confirmation needed @@ -63,6 +65,8 @@ export class ChatToolInvocation implements IChatToolInvocation { return { kind: 'toolInvocationSerialized', invocationMessage: this.invocationMessage, + pastTenseMessage: this.pastTenseMessage, + tooltip: this.tooltip, isConfirmed: this._isConfirmed ?? false, isComplete: this._isComplete, }; diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index e6974becb63..4251fe54356 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -203,6 +203,8 @@ export interface IChatToolInvocation { /** A 3-way: undefined=don't know yet. */ isConfirmed: boolean | undefined; invocationMessage: string | IMarkdownString; + pastTenseMessage: string | IMarkdownString | undefined; + tooltip: string | IMarkdownString | undefined; isComplete: boolean; isCompleteDeferred: DeferredPromise; @@ -214,6 +216,8 @@ export interface IChatToolInvocation { */ export interface IChatToolInvocationSerialized { invocationMessage: string | IMarkdownString; + pastTenseMessage: string | IMarkdownString | undefined; + tooltip: string | IMarkdownString | undefined; isConfirmed: boolean; isComplete: boolean; kind: 'toolInvocationSerialized'; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index bc80d7c6286..2e48c18bf08 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -66,6 +66,8 @@ export interface IToolConfirmationMessages { export interface IPreparedToolInvocation { invocationMessage?: string | IMarkdownString; + pastTenseMessage?: string | IMarkdownString; + tooltip?: string | IMarkdownString; confirmationMessages?: IToolConfirmationMessages; } diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index 4f08ef3a4a4..0aff2d2091b 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -122,4 +122,9 @@ declare module 'vscode' { export interface LanguageModelToolInvocationOptions { chatRequestId?: string; } + + export interface PreparedToolInvocation { + pastTenseMessage?: string | MarkdownString; + tooltip?: string | MarkdownString; + } }