Files
vscode/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts
T
Rob LourensandGitHub a6870298b3 Use proper DTO type for tools (#248944)
Some ContextKeyExpressions that weren't json-stringifiable caused all tools to break.
2025-05-14 11:03:08 -07:00

99 lines
4.2 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../base/common/cancellation.js';
import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
import { revive } from '../../../base/common/marshalling.js';
import { CountTokensCallback, ILanguageModelToolsService, IToolInvocation, IToolProgressStep, IToolResult, ToolProgress, toolResultHasBuffers } from '../../contrib/chat/common/languageModelToolsService.js';
import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';
import { Dto, SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js';
import { ExtHostContext, ExtHostLanguageModelToolsShape, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from '../common/extHost.protocol.js';
@extHostNamedCustomer(MainContext.MainThreadLanguageModelTools)
export class MainThreadLanguageModelTools extends Disposable implements MainThreadLanguageModelToolsShape {
private readonly _proxy: ExtHostLanguageModelToolsShape;
private readonly _tools = this._register(new DisposableMap<string>());
private readonly _runningToolCalls = new Map</* call ID */string, {
countTokens: CountTokensCallback;
progress: ToolProgress;
}>();
constructor(
extHostContext: IExtHostContext,
@ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageModelTools);
this._register(this._languageModelToolsService.onDidChangeTools(e => this._proxy.$onDidChangeTools(this.getToolDtos())));
}
private getToolDtos(): IToolDataDto[] {
return Array.from(this._languageModelToolsService.getTools())
.map(tool => ({
id: tool.id,
displayName: tool.displayName,
toolReferenceName: tool.toolReferenceName,
tags: tool.tags,
userDescription: tool.userDescription,
modelDescription: tool.modelDescription,
inputSchema: tool.inputSchema,
} satisfies IToolDataDto));
}
async $getTools(): Promise<IToolDataDto[]> {
return this.getToolDtos();
}
async $invokeTool(dto: IToolInvocation, token?: CancellationToken): Promise<Dto<IToolResult> | SerializableObjectWithBuffers<Dto<IToolResult>>> {
const result = await this._languageModelToolsService.invokeTool(
dto,
(input, token) => this._proxy.$countTokensForInvocation(dto.callId, input, token),
token ?? CancellationToken.None,
);
// Don't return extra metadata to EH
const out: Dto<IToolResult> = { content: result.content };
return toolResultHasBuffers(result) ? new SerializableObjectWithBuffers(out) : out;
}
$acceptToolProgress(callId: string, progress: IToolProgressStep): void {
this._runningToolCalls.get(callId)?.progress.report(progress);
}
$countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise<number> {
const fn = this._runningToolCalls.get(callId);
if (!fn) {
throw new Error(`Tool invocation call ${callId} not found`);
}
return fn.countTokens(input, token);
}
$registerTool(id: string): void {
const disposable = this._languageModelToolsService.registerToolImplementation(
id,
{
invoke: async (dto, countTokens, progress, token) => {
try {
this._runningToolCalls.set(dto.callId, { countTokens, progress });
const resultSerialized = await this._proxy.$invokeTool(dto, token);
const resultDto: Dto<IToolResult> = resultSerialized instanceof SerializableObjectWithBuffers ? resultSerialized.value : resultSerialized;
return revive<IToolResult>(resultDto);
} finally {
this._runningToolCalls.delete(dto.callId);
}
},
prepareToolInvocation: (parameters, token) => this._proxy.$prepareToolInvocation(id, parameters, token),
});
this._tools.set(id, disposable);
}
$unregisterTool(name: string): void {
this._tools.deleteAndDispose(name);
}
}