Path: blob/main/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts
5222 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../base/common/cancellation.js';6import { Disposable, DisposableMap, DisposableStore } from '../../../base/common/lifecycle.js';7import { revive } from '../../../base/common/marshalling.js';8import { ThemeIcon } from '../../../base/common/themables.js';9import { isUriComponents, URI, UriComponents } from '../../../base/common/uri.js';10import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js';11import { ILogService } from '../../../platform/log/common/log.js';12import { toToolSetKey } from '../../contrib/chat/common/tools/languageModelToolsContribution.js';13import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolInvocation, IToolProgressStep, IToolResult, ToolDataSource, ToolProgress, toolResultHasBuffers, ToolSet } from '../../contrib/chat/common/tools/languageModelToolsService.js';14import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';15import { Dto, SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js';16import { ExtHostContext, ExtHostLanguageModelToolsShape, IToolDataDto, IToolDefinitionDto, MainContext, MainThreadLanguageModelToolsShape } from '../common/extHost.protocol.js';1718@extHostNamedCustomer(MainContext.MainThreadLanguageModelTools)19export class MainThreadLanguageModelTools extends Disposable implements MainThreadLanguageModelToolsShape {2021private readonly _proxy: ExtHostLanguageModelToolsShape;22private readonly _tools = this._register(new DisposableMap<string>());23private readonly _runningToolCalls = new Map</* call ID */string, {24countTokens: CountTokensCallback;25progress: ToolProgress;26}>();2728constructor(29extHostContext: IExtHostContext,30@ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService,31@ILogService private readonly _logService: ILogService,32) {33super();34this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageModelTools);3536this._register(this._languageModelToolsService.onDidChangeTools(e => this._proxy.$onDidChangeTools(this.getToolDtos())));37}3839private getToolDtos(): IToolDataDto[] {40return Array.from(this._languageModelToolsService.getAllToolsIncludingDisabled())41.map(tool => ({42id: tool.id,43displayName: tool.displayName,44toolReferenceName: tool.toolReferenceName,45legacyToolReferenceFullNames: tool.legacyToolReferenceFullNames,46tags: tool.tags,47userDescription: tool.userDescription,48modelDescription: tool.modelDescription,49inputSchema: tool.inputSchema,50source: tool.source,51} satisfies IToolDataDto));52}5354async $getTools(): Promise<IToolDataDto[]> {55return this.getToolDtos();56}5758async $invokeTool(dto: Dto<IToolInvocation>, token?: CancellationToken): Promise<Dto<IToolResult> | SerializableObjectWithBuffers<Dto<IToolResult>>> {59const result = await this._languageModelToolsService.invokeTool(60revive<IToolInvocation>(dto),61(input, token) => this._proxy.$countTokensForInvocation(dto.callId, input, token),62token ?? CancellationToken.None,63);6465// Only return content and metadata to EH66const out: Dto<IToolResult> = {67content: result.content,68toolMetadata: result.toolMetadata69};70return toolResultHasBuffers(result) ? new SerializableObjectWithBuffers(out) : out;71}7273$acceptToolProgress(callId: string, progress: IToolProgressStep): void {74this._runningToolCalls.get(callId)?.progress.report(progress);75}7677$countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise<number> {78const fn = this._runningToolCalls.get(callId);79if (!fn) {80throw new Error(`Tool invocation call ${callId} not found`);81}8283return fn.countTokens(input, token);84}8586$registerTool(id: string, hasHandleToolStream: boolean): void {87const disposable = this._languageModelToolsService.registerToolImplementation(88id,89{90invoke: async (dto, countTokens, progress, token) => {91try {92this._runningToolCalls.set(dto.callId, { countTokens, progress });93const resultSerialized = await this._proxy.$invokeTool(dto, token);94const resultDto: Dto<IToolResult> = resultSerialized instanceof SerializableObjectWithBuffers ? resultSerialized.value : resultSerialized;95return revive<IToolResult>(resultDto);96} finally {97this._runningToolCalls.delete(dto.callId);98}99},100prepareToolInvocation: (context, token) => this._proxy.$prepareToolInvocation(id, context, token),101handleToolStream: hasHandleToolStream ? (context, token) => this._proxy.$handleToolStream(id, context, token) : undefined,102});103this._tools.set(id, disposable);104}105106$registerToolWithDefinition(extensionId: ExtensionIdentifier, definition: IToolDefinitionDto, hasHandleToolStream: boolean): void {107let icon: IToolData['icon'] | undefined;108if (definition.icon) {109if (ThemeIcon.isThemeIcon(definition.icon)) {110icon = definition.icon;111} else if (typeof definition.icon === 'object' && definition.icon !== null && isUriComponents(definition.icon)) {112icon = { dark: URI.revive(definition.icon as UriComponents) };113} else {114const iconObj = definition.icon as { light?: UriComponents; dark: UriComponents };115icon = { dark: URI.revive(iconObj.dark), light: iconObj.light ? URI.revive(iconObj.light) : undefined };116}117}118119// Convert source from DTO120const source = revive<ToolDataSource>(definition.source);121122// Create the tool data123const toolData: IToolData = {124id: definition.id,125displayName: definition.displayName,126toolReferenceName: definition.toolReferenceName,127legacyToolReferenceFullNames: definition.legacyToolReferenceFullNames,128tags: definition.tags,129userDescription: definition.userDescription,130modelDescription: definition.modelDescription,131inputSchema: definition.inputSchema,132source,133icon,134models: definition.models,135canBeReferencedInPrompt: !!definition.userDescription && !definition.toolSet,136};137138// Register both tool data and implementation139const id = definition.id;140const store = new DisposableStore();141store.add(this._languageModelToolsService.registerTool(142toolData,143{144invoke: async (dto, countTokens, progress, token) => {145try {146this._runningToolCalls.set(dto.callId, { countTokens, progress });147const resultSerialized = await this._proxy.$invokeTool(dto, token);148const resultDto: Dto<IToolResult> = resultSerialized instanceof SerializableObjectWithBuffers ? resultSerialized.value : resultSerialized;149return revive<IToolResult>(resultDto);150} finally {151this._runningToolCalls.delete(dto.callId);152}153},154handleToolStream: hasHandleToolStream ? (context, token) => this._proxy.$handleToolStream(id, context, token) : undefined,155prepareToolInvocation: (context, token) => this._proxy.$prepareToolInvocation(id, context, token),156}157));158159if (definition.toolSet) {160const ts = this._languageModelToolsService.getToolSet(toToolSetKey(extensionId, definition.toolSet)) || this._languageModelToolsService.getToolSet(definition.toolSet);161if (!ts || !(ts instanceof ToolSet)) {162this._logService.warn(`ToolSet ${definition.toolSet} not found for tool ${definition.id} from extension ${extensionId.value}`);163} else {164store.add(ts.addTool(toolData));165}166}167168this._tools.set(id, store);169}170171$unregisterTool(name: string): void {172this._tools.deleteAndDispose(name);173}174}175176177