diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 6328cfd2df9..774cc0cc158 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -21,6 +21,7 @@ import './mainThreadLocalization'; import './mainThreadBulkEdits'; import './mainThreadChatProvider'; import './mainThreadChatSlashCommands'; +import './mainThreadChatVariables'; import './mainThreadCodeInsets'; import './mainThreadCLICommands'; import './mainThreadClipboard'; diff --git a/src/vs/workbench/api/browser/mainThreadChatVariables.ts b/src/vs/workbench/api/browser/mainThreadChatVariables.ts new file mode 100644 index 00000000000..d2d8902ccbd --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadChatVariables.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { ExtHostChatVariablesShape, ExtHostContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatVariableData, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +@extHostNamedCustomer(MainContext.MainThreadChatVariables) +export class MainThreadChatSlashCommands implements MainThreadChatVariablesShape { + + private readonly _proxy: ExtHostChatVariablesShape; + private readonly _variables = new DisposableMap(); + + constructor( + extHostContext: IExtHostContext, + @IChatVariablesService private readonly _chatVariablesService: IChatVariablesService, + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatVariables); + } + + dispose(): void { + this._variables.clearAndDisposeAll(); + } + + $registerVariable(handle: number, data: IChatVariableData): void { + const registration = this._chatVariablesService.registerChatVariable(data, token => { + return this._proxy.$resolveVariable(handle, token); + }); + this._variables.set(handle, registration); + } + + $unregisterVariable(handle: number): void { + this._variables.deleteAndDispose(handle); + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 383e886c8c1..9328804fda1 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -106,6 +106,7 @@ import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSo import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import { ExtHostChatSlashCommands } from 'vs/workbench/api/common/extHostChatSlashCommand'; +import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -207,6 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); const extHostChatSlashCommands = rpcProtocol.set(ExtHostContext.ExtHostChatSlashCommands, new ExtHostChatSlashCommands(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); @@ -1345,6 +1347,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I requestChatAccess(id: string) { checkProposedApiEnabled(extension, 'chatRequestAccess'); return extHostChatProvider.requestChatResponseProvider(extension.identifier, id); + }, + registerVariable(name, description, resolver) { + checkProposedApiEnabled(extension, 'chatVariables'); + return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 76565ab6f14..49356b070ca 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -76,6 +76,7 @@ import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common import { TerminalCommandMatchResult, TerminalQuickFixCommand, TerminalQuickFixOpener } from 'vscode'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IChatResolvedVariable, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; export type TerminalQuickFix = TerminalQuickFixCommand | TerminalQuickFixOpener; @@ -1149,6 +1150,15 @@ export interface ExtHostChatSlashCommandsShape { $executeCommand(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; } +export interface MainThreadChatVariablesShape extends IDisposable { + $registerVariable(handle: number, data: IChatVariableData): void; + $unregisterVariable(handle: number): void; +} + +export interface ExtHostChatVariablesShape { + $resolveVariable(handle: number, token: CancellationToken): Promise; +} + export interface MainThreadInlineChatShape extends IDisposable { $registerInteractiveEditorProvider(handle: number, label: string, debugName: string, supportsFeedback: boolean): Promise; $handleProgressChunk(requestId: string, chunk: { message?: string; edits?: languages.TextEdit[] }): Promise; @@ -2556,6 +2566,7 @@ export const MainContext = { MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), MainThreadChatSlashCommands: createProxyIdentifier('MainThreadChatSlashCommands'), + MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), MainThreadCommands: createProxyIdentifier('MainThreadCommands'), MainThreadComments: createProxyIdentifier('MainThreadComments'), @@ -2675,6 +2686,7 @@ export const ExtHostContext = { ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), ExtHostChatSlashCommands: createProxyIdentifier('ExtHostChatSlashCommands'), + ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostSemanticSimilarity: createProxyIdentifier('ExtHostSemanticSimilarity'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), diff --git a/src/vs/workbench/api/common/extHostChatVariables.ts b/src/vs/workbench/api/common/extHostChatVariables.ts new file mode 100644 index 00000000000..ac11bd3515f --- /dev/null +++ b/src/vs/workbench/api/common/extHostChatVariables.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type * as vscode from 'vscode'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostChatVariablesShape, IMainContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatResolvedVariable, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; + +export class ExtHostChatVariables implements ExtHostChatVariablesShape { + + private static _idPool = 0; + + private readonly _resolver = new Map(); + private readonly _proxy: MainThreadChatVariablesShape; + + constructor(mainContext: IMainContext) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatVariables); + } + + async $resolveVariable(handle: number, token: CancellationToken): Promise { + const item = this._resolver.get(handle); + if (!item) { + return undefined; + } + try { + return (await item.resolver.resolve(item.data.name, token)) ?? undefined; + } catch (err) { + onUnexpectedExternalError(err); + return undefined; + } + } + + registerVariableResolver(extension: IExtensionDescription, name: string, description: string, resolver: vscode.ChatVariableResolver): IDisposable { + const handle = ExtHostChatVariables._idPool++; + this._resolver.set(handle, { extension: extension.identifier, data: { name, description }, resolver: resolver }); + this._proxy.$registerVariable(handle, { name, description }); + + return toDisposable(() => { + this._resolver.delete(handle); + this._proxy.$unregisterVariable(handle); + }); + } +} diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index 7da1ebafcdb..d8cde94c682 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -15,12 +15,12 @@ export interface IChatVariableData { export interface IChatResolvedVariable { // TODO should we allow for multiple levels - value: string; + content: string; } export interface IChatVariableResolver { // TODO should we spec "zoom level" - (token: CancellationToken): IChatResolvedVariable; + (token: CancellationToken): Promise; } export const IChatVariablesService = createDecorator('IChatVariablesService'); @@ -28,7 +28,7 @@ export const IChatVariablesService = createDecorator('ICh export interface IChatVariablesService { _serviceBrand: undefined; registerChatVariable(data: IChatVariableData, resolver: IChatVariableResolver): IDisposable; - resolveVariable(name: string, token: CancellationToken): Promise; + resolveVariable(name: string, token: CancellationToken): Promise; getVariables(): Iterable>; } @@ -53,7 +53,7 @@ export class ChatVariablesService implements IChatVariablesService { { name: 'workspace', description: 'Details of your workspace' }, { name: 'vscode', description: 'Commands and settings in vscode' }, ].forEach(item => { - this.registerChatVariable(item, () => ({ value: item.name })); + this.registerChatVariable(item, async () => ({ content: item.name })); }); } @@ -72,7 +72,7 @@ export class ChatVariablesService implements IChatVariablesService { }); } - resolveVariable(name: string, token: CancellationToken): Promise { + resolveVariable(name: string, token: CancellationToken): Promise { const key = name.toLowerCase(); const chatData = this._resolver.get(key); if (!chatData) { diff --git a/src/vscode-dts/vscode.proposed.chatVariables.d.ts b/src/vscode-dts/vscode.proposed.chatVariables.d.ts index bb3380301e3..38f6c1947dc 100644 --- a/src/vscode-dts/vscode.proposed.chatVariables.d.ts +++ b/src/vscode-dts/vscode.proposed.chatVariables.d.ts @@ -5,17 +5,17 @@ declare module 'vscode' { - interface ResolvedVariable { + export interface ChatVariableValue { content: string; } - interface ChatVariableResolver { - resolve(name: string): ResolvedVariable; + export interface ChatVariableResolver { + resolve(name: string, token: CancellationToken): ProviderResult; } // Could be provider/resolver pattern, but how dynamic are they? export namespace chat { // name: selection - export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): void; + export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; } }