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