Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/chatVariables.tsx
13405 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 { BasePromptElementProps, PromptElement, PromptElementProps, PromptPiece, PromptReference, PromptSizing, TextChunk, UserMessage } from '@vscode/prompt-tsx';
7
import type { Diagnostic, LanguageModelToolInformation } from 'vscode';
8
import { ChatFetchResponseType, ChatLocation } from '../../../../platform/chat/common/commonTypes';
9
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
10
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
11
import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
12
import { FileType } from '../../../../platform/filesystem/common/fileTypes';
13
import { ILogService } from '../../../../platform/log/common/logService';
14
import { ICopilotToolCall } from '../../../../platform/networking/common/fetch';
15
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
16
import { IAlternativeNotebookContentService } from '../../../../platform/notebook/common/alternativeContent';
17
import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';
18
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
19
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
20
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
21
import { getLanguage, getLanguageForResource } from '../../../../util/common/languages';
22
import { createFencedCodeBlock } from '../../../../util/common/markdown';
23
import { getNotebookAndCellFromUri } from '../../../../util/common/notebooks';
24
import { isLocation } from '../../../../util/common/types';
25
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
26
import { Schemas } from '../../../../util/vs/base/common/network';
27
import { isEqual } from '../../../../util/vs/base/common/resources';
28
import { URI } from '../../../../util/vs/base/common/uri';
29
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
30
import { DiagnosticSeverity } from '../../../../util/vs/workbench/api/common/extHostTypes/diagnostic';
31
import { ChatReferenceBinaryData, ChatReferenceDiagnostic, LanguageModelToolResult2, Range, Uri } from '../../../../vscodeTypes';
32
import { GenericBasePromptElementProps } from '../../../context/node/resolvers/genericPanelIntentInvocation';
33
import { ChatVariablesCollection, isCustomizationsIndex, isInstructionFile, isPromptFile, isSessionReference, parseSlashCommand, sessionReferenceAttachmentAttrs } from '../../../prompt/common/chatVariablesCollection';
34
import { InternalToolReference } from '../../../prompt/common/intents';
35
import { ToolName } from '../../../tools/common/toolNames';
36
import { normalizeToolSchema } from '../../../tools/common/toolSchemaNormalizer';
37
import { IToolsService } from '../../../tools/common/toolsService';
38
import { EmbeddedInsideUserMessage, embeddedInsideUserMessageDefault } from '../base/promptElement';
39
import { IPromptEndpoint, PromptRenderer } from '../base/promptRenderer';
40
import { Tag } from '../base/tag';
41
import { DiagnosticSuggestedFix } from '../inline/diagnosticsContext';
42
import { Cookbook, IFixCookbookService } from '../inline/fixCookbookService';
43
import { SummarizedDocumentLineNumberStyle } from '../inline/summarizedDocument/implementation';
44
import { FilePathMode, FileVariable } from './fileVariable';
45
import { Image } from './image';
46
import { NotebookCellOutputVariable } from './notebookVariables';
47
import { PanelChatBasePrompt } from './panelChatBasePrompt';
48
import { PromptFile } from './promptFile';
49
import { sendInvokedToolTelemetry, toolCallErrorToResult, ToolResult, ToolResultMetadata } from './toolCalling';
50
import { IFileTreeData, workspaceVisualFileTree } from './workspace/visualFileTree';
51
52
export interface ChatVariablesProps extends BasePromptElementProps, EmbeddedInsideUserMessage {
53
readonly chatVariables: ChatVariablesCollection;
54
readonly includeFilepath?: boolean;
55
readonly omitReferences?: boolean;
56
readonly isAgent?: boolean;
57
readonly useFixCookbook?: boolean;
58
/**
59
* If true, file attachment contents are omitted and only the file names/paths are included.
60
*/
61
readonly omitFileContents?: boolean;
62
}
63
64
export class ChatVariables extends PromptElement<ChatVariablesProps, void> {
65
constructor(
66
props: ChatVariablesProps,
67
@IFileSystemService private readonly fileSystemService: IFileSystemService,
68
@IConfigurationService private readonly configurationService: IConfigurationService,
69
@IExperimentationService private readonly experimentationService: IExperimentationService,
70
) {
71
super(props);
72
}
73
74
override async render(state: void, sizing: PromptSizing): Promise<PromptPiece<any, any> | undefined> {
75
// Only check experiment setting for agent mode
76
const omitFileContents = this.props.omitFileContents ?? (this.props.isAgent && this.configurationService.getExperimentBasedConfig(ConfigKey.Advanced.AgentOmitFileAttachmentContents, this.experimentationService));
77
const elements = await renderChatVariables(this.props.chatVariables, this.fileSystemService, this.props.includeFilepath, true, this.props.omitReferences, this.props.isAgent, this.props.useFixCookbook, omitFileContents);
78
if (elements.length === 0) {
79
return undefined;
80
}
81
82
if (this.props.embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault) {
83
return (
84
<>
85
{Boolean(elements.length) && <Tag name='attachments' priority={this.props.priority}>
86
{...elements}
87
</Tag>}
88
</>
89
);
90
}
91
return (<>{...elements.map(element => asUserMessage(element, this.props.priority))}</>);
92
}
93
}
94
95
export interface QueryProps extends BasePromptElementProps {
96
readonly chatVariables: ChatVariablesCollection;
97
readonly query: string;
98
}
99
100
export class UserQuery extends PromptElement<QueryProps, void> {
101
constructor(
102
props: PromptElementProps<QueryProps>,
103
) {
104
super(props);
105
}
106
107
override render(state: void, sizing: PromptSizing): PromptPiece<any, any> | undefined {
108
const promptFiles: PromptElement[] = [];
109
for (const v of this.props.chatVariables) {
110
if (isPromptFile(v)) {
111
promptFiles.push(<PromptFile variable={v} omitReferences={false} />);
112
}
113
}
114
115
const userMessage = buildSlashCommandUserMessage(this.props.query, this.props.chatVariables);
116
117
return (
118
<>
119
{...promptFiles}
120
{userMessage}
121
</>
122
);
123
}
124
}
125
126
/**
127
* Builds the user message for a slash command query. If the query matches a slash command
128
* that corresponds to a prompt file, returns an instruction to follow that prompt file
129
* (with any trailing arguments). Otherwise, returns the original query.
130
*/
131
export function buildSlashCommandUserMessage(query: string, chatVariables: ChatVariablesCollection): string {
132
const match = parseSlashCommand(query, chatVariables);
133
if (match) {
134
return match.args
135
? `Follow instructions in #${match.promptFile.name} with these arguments: ${match.args}`
136
: `Follow instructions in #${match.promptFile.name}`;
137
}
138
return query;
139
}
140
141
export interface ChatVariablesAndQueryProps extends BasePromptElementProps, EmbeddedInsideUserMessage {
142
readonly query: string;
143
readonly chatVariables: ChatVariablesCollection;
144
/**
145
* By default, the chat variables are reversed. Set this to true to maintain the variable order.
146
*/
147
readonly maintainOrder?: boolean;
148
readonly includeFilepath?: boolean;
149
readonly omitReferences?: boolean;
150
/**
151
* If true, file attachment contents are omitted and only the file names/paths are included.
152
*/
153
readonly omitFileContents?: boolean;
154
}
155
156
export class ChatVariablesAndQuery extends PromptElement<ChatVariablesAndQueryProps, void> {
157
constructor(
158
props: ChatVariablesAndQueryProps,
159
@IFileSystemService private readonly fileSystemService: IFileSystemService,
160
) {
161
super(props);
162
}
163
164
override async render(state: void, sizing: PromptSizing): Promise<PromptPiece<any, any> | undefined> {
165
const chatVariables = this.props.maintainOrder ? this.props.chatVariables : this.props.chatVariables.reverse();
166
const elements = await renderChatVariables(chatVariables, this.fileSystemService, this.props.includeFilepath, true, this.props.omitReferences, undefined, undefined, this.props.omitFileContents);
167
168
if (this.props.embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault) {
169
if (!elements.length) {
170
return (
171
<Tag name='prompt'>
172
<UserQuery chatVariables={chatVariables} query={this.props.query} priority={this.props.priority} />
173
</Tag>
174
);
175
}
176
return (<>
177
{Boolean(elements.length) && <Tag name='attachments' flexGrow={1} priority={this.props.priority}>
178
{elements}
179
</Tag>}
180
<Tag name='prompt'>
181
<UserQuery chatVariables={chatVariables} query={this.props.query} priority={this.props.priority} />
182
</Tag>
183
</>);
184
}
185
186
return (<>
187
{...elements.map(element => asUserMessage(element, this.props.priority && this.props.priority - 1))}
188
{asUserMessage(<UserQuery chatVariables={chatVariables} query={this.props.query} />, this.props.priority)}
189
</>);
190
}
191
}
192
193
function asUserMessage(element: PromptElement, priority: number | undefined): UserMessage {
194
return (<UserMessage priority={priority}>{element}</UserMessage>);
195
}
196
197
198
export async function renderChatVariables(chatVariables: ChatVariablesCollection, fileSystemService: IFileSystemService, includeFilepathInCodeBlocks = true, alwaysIncludeSummary = true, omitReferences?: boolean, isAgent?: boolean, useFixCookbook?: boolean, omitFileContents?: boolean): Promise<PromptElement[]> {
199
const elements = [];
200
const filePathMode = (isAgent && includeFilepathInCodeBlocks)
201
? FilePathMode.AsAttribute
202
: includeFilepathInCodeBlocks
203
? FilePathMode.AsComment
204
: FilePathMode.None;
205
for (const variable of chatVariables) {
206
const { uniqueName: variableName, value: variableValue, reference } = variable;
207
if (isInstructionFile(variable) || isCustomizationsIndex(variable) || isPromptFile(variable)) { // instructions and index are handled in the `CustomInstructions` element, prompt file as part of the UserQuery
208
continue;
209
}
210
211
if (isSessionReference(variable)) {
212
elements.push(<Tag name='attachment' attrs={sessionReferenceAttachmentAttrs(variable)} />);
213
continue;
214
}
215
216
if (URI.isUri(variableValue) || isLocation(variableValue)) {
217
const uri = 'uri' in variableValue ? variableValue.uri : variableValue;
218
219
// Check if the variable is a directory
220
let isDirectory = false;
221
try {
222
const stat = await fileSystemService.stat(uri);
223
isDirectory = stat.type === FileType.Directory;
224
} catch { }
225
226
if (isDirectory) {
227
elements.push(<FolderVariable variableName={variableName} folderUri={uri} omitReferences={omitReferences} description={reference.modelDescription} omitContents={omitFileContents} />);
228
} else {
229
const file = <FileVariable
230
alwaysIncludeSummary={alwaysIncludeSummary}
231
filePathMode={filePathMode}
232
variableName={variableName}
233
variableValue={variableValue}
234
omitReferences={omitReferences}
235
description={reference.modelDescription}
236
lineNumberStyle={isAgent ? SummarizedDocumentLineNumberStyle.OmittedRanges : undefined}
237
omitContents={omitFileContents}
238
/>;
239
240
if (!isAgent || (!URI.isUri(variableValue) || variableValue.scheme !== Schemas.vscodeNotebookCellOutput)) {
241
// When attaching outupts, there's no need to add the entire notebook file again, as model can request the notebook file.
242
// In non agent mode, we need to add the file for context.
243
elements.push(file);
244
}
245
if (URI.isUri(variableValue) && variableValue.scheme === Schemas.vscodeNotebookCellOutput) {
246
elements.push(<NotebookCellOutputVariable outputUri={variableValue} />);
247
}
248
}
249
} else if (typeof variableValue === 'string') {
250
elements.push(
251
<Tag name='attachment' attrs={variableName ? { id: variableName } : undefined} >
252
<TextChunk>
253
{!omitReferences && <references value={[new PromptReference({ variableName })]} />}
254
{reference.modelDescription ? reference.modelDescription + ':\n' : ''}
255
{variableValue}
256
</TextChunk>
257
</Tag>
258
);
259
} else if (variableValue instanceof ChatReferenceBinaryData) {
260
elements.push(<Image variableName={variableName} variableValue={await variableValue.data()} reference={variableValue.reference} omitReferences={omitReferences}></Image>);
261
} else if (typeof ChatReferenceDiagnostic !== 'undefined' && variableValue instanceof ChatReferenceDiagnostic) { // check undefined to avoid breaking old Insiders versions
262
elements.push(<DiagnosticVariable diagnostics={variableValue.diagnostics} useCookbook={useFixCookbook ?? false} />);
263
}
264
}
265
return elements;
266
}
267
268
interface IDiagnosticVariableProps extends BasePromptElementProps {
269
diagnostics: [uri: Uri, diagnostics: Diagnostic[]][];
270
useCookbook?: boolean;
271
// useRelatedInfo?: boolean;
272
}
273
274
const diagnosticSeverityMap: { [K in DiagnosticSeverity]: string } = {
275
[DiagnosticSeverity.Error]: 'error',
276
[DiagnosticSeverity.Warning]: 'warning',
277
[DiagnosticSeverity.Information]: 'info',
278
[DiagnosticSeverity.Hint]: 'hint'
279
};
280
281
class DiagnosticVariable extends PromptElement<IDiagnosticVariableProps> {
282
constructor(
283
props: PromptElementProps<IDiagnosticVariableProps>,
284
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
285
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
286
@IFixCookbookService private readonly fixCookbookService: IFixCookbookService,
287
@IAlternativeNotebookContentService private readonly alternativeNotebookContent: IAlternativeNotebookContentService,
288
@IPromptEndpoint private readonly endpoint: IPromptEndpoint,
289
) {
290
super(props);
291
}
292
293
render() {
294
return <>
295
{this.props.diagnostics.flatMap(([uri, diagnostics]) =>
296
diagnostics.map(d => {
297
let range = d.range;
298
([uri, range] = this.translateNotebookUri(uri, range));
299
300
let cookbook: Cookbook | undefined;
301
if (this.props.useCookbook) {
302
const doc = this.workspaceService.textDocuments.find(doc => isEqual(doc.uri, uri));
303
const lang = doc ? getLanguage(doc) : getLanguageForResource(uri);
304
cookbook = this.fixCookbookService.getCookbook(lang.languageId, d);
305
}
306
307
return <>
308
<Tag name='error' attrs={{ path: this.promptPathRepresentationService.getFilePath(uri), line: range.start.line + 1, code: getDiagnosticCode(d), severity: diagnosticSeverityMap[d.severity] }}>
309
{d.message}
310
</Tag>
311
{cookbook && <DiagnosticSuggestedFix cookbook={cookbook} />}
312
</>;
313
}
314
)
315
)}
316
</>;
317
}
318
private translateNotebookUri(uri: Uri, range: Range): [Uri, Range] {
319
if (uri.scheme !== Schemas.vscodeNotebookCell) {
320
return [uri, range];
321
}
322
const [notebook, cell] = getNotebookAndCellFromUri(uri, this.workspaceService.notebookDocuments);
323
if (!notebook || !cell) {
324
return [uri, range];
325
}
326
if (range.start.line > cell.document.lineCount || range.end.line > cell.document.lineCount) {
327
return [uri, range];
328
}
329
330
const altDocument = this.alternativeNotebookContent.create(this.alternativeNotebookContent.getFormat(this.endpoint)).getAlternativeDocument(notebook);
331
const start = altDocument.fromCellPosition(cell, range.start);
332
const end = altDocument.fromCellPosition(cell, range.end);
333
const newRange = new Range(start, end);
334
return [notebook.uri, newRange];
335
}
336
}
337
338
function getDiagnosticCode(diagnostic: Diagnostic): string {
339
const code = (typeof diagnostic.code === 'object' && !!diagnostic.code) ? diagnostic.code.value : diagnostic.code;
340
return String(code);
341
}
342
343
interface IFolderVariableProps extends BasePromptElementProps {
344
variableName: string;
345
folderUri: Uri;
346
omitReferences?: boolean;
347
description?: string;
348
/**
349
* If true, folder contents (file tree) are omitted and only the folder path is included.
350
*/
351
omitContents?: boolean;
352
}
353
354
class FolderVariable extends PromptElement<IFolderVariableProps, IFileTreeData | undefined> {
355
constructor(
356
props: PromptElementProps<IFolderVariableProps>,
357
@IInstantiationService private readonly instantiationService: IInstantiationService,
358
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
359
) {
360
super(props);
361
}
362
363
override async prepare(sizing: PromptSizing): Promise<IFileTreeData | undefined> {
364
if (this.props.omitContents) {
365
// Skip fetching the file tree when contents are omitted
366
return undefined;
367
}
368
try {
369
return this.instantiationService.invokeFunction(accessor =>
370
workspaceVisualFileTree(accessor, this.props.folderUri, { maxLength: 2000, excludeDotFiles: false }, CancellationToken.None)
371
);
372
} catch {
373
// Directory doesn't exist or is not accessible
374
return undefined;
375
}
376
}
377
378
render(state: IFileTreeData | undefined) {
379
const folderPath = this.promptPathRepresentationService.getFilePath(this.props.folderUri);
380
if (this.props.omitContents) {
381
return (
382
<Tag name='attachment' attrs={this.props.variableName ? { id: this.props.variableName, folderPath } : undefined} />
383
);
384
}
385
return (
386
<Tag name='attachment' attrs={this.props.variableName ? { id: this.props.variableName, folderPath } : undefined}>
387
<TextChunk>
388
{!this.props.omitReferences && <references value={[new PromptReference({ variableName: this.props.variableName })]} />}
389
{this.props.description ? this.props.description + ':\n' : ''}
390
The user attached the folder `{folderPath}`{state ? ' which has the following structure: ' + createFencedCodeBlock('', state.tree) : ''}
391
</TextChunk>
392
</Tag>
393
);
394
}
395
}
396
397
export interface ChatToolCallProps extends GenericBasePromptElementProps, EmbeddedInsideUserMessage {
398
}
399
400
interface IToolCallResult {
401
readonly name: string | undefined;
402
readonly value: LanguageModelToolResult2;
403
}
404
405
/**
406
* Render toolReferences set on the request.
407
*/
408
export class ChatToolReferences extends PromptElement<ChatToolCallProps, void> {
409
constructor(
410
props: ChatToolCallProps,
411
@IInstantiationService private readonly instantiationService: IInstantiationService,
412
@IToolsService private readonly toolsService: IToolsService,
413
@ILogService private readonly logService: ILogService,
414
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
415
@IPromptEndpoint private readonly promptEndpoint: IPromptEndpoint,
416
@ITelemetryService private readonly telemetryService: ITelemetryService
417
) {
418
super(props);
419
}
420
421
override async render(state: void, sizing: PromptSizing, _progress: unknown, token?: CancellationToken): Promise<PromptPiece<any, any> | undefined> {
422
const { tools, toolCallResults } = this.props.promptContext;
423
if (!tools || !tools.toolReferences.length) {
424
return;
425
}
426
427
const results: IToolCallResult[] = [];
428
for (const toolReference of tools.toolReferences) {
429
const tool = this.toolsService.getTool(toolReference.name);
430
if (!tool) {
431
throw new Error(`Unknown tool: "${toolReference.name}"`);
432
}
433
434
if (toolCallResults?.[toolReference.id]) {
435
results.push({ name: toolReference.name, value: toolCallResults[toolReference.id] });
436
continue;
437
}
438
439
const toolArgsEndpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');
440
const internalToolArgs = toolReference.input ?? {};
441
const toolArgs = await this.fetchToolArgs(tool, toolArgsEndpoint);
442
443
const name = toolReference.range ? this.props.promptContext.query.slice(toolReference.range[0], toolReference.range[1]) : undefined;
444
try {
445
const result = await this.toolsService.invokeToolWithEndpoint(tool.name, { input: { ...toolArgs, ...internalToolArgs }, toolInvocationToken: tools.toolInvocationToken }, this.promptEndpoint, token || CancellationToken.None);
446
sendInvokedToolTelemetry(this.promptEndpoint.acquireTokenizer(), this.telemetryService, tool.name, result);
447
results.push({ name, value: result });
448
} catch (err) {
449
const errResult = toolCallErrorToResult(err);
450
results.push({ name, value: errResult.result });
451
}
452
}
453
454
if (this.props.embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault) {
455
return this._renderChatToolResults(tools.toolReferences, results, this.props.priority);
456
}
457
458
return (
459
<UserMessage priority={this.props.priority}>
460
{this._renderChatToolResults(tools.toolReferences, results)}
461
</UserMessage>
462
);
463
}
464
465
private _renderChatToolResults(tools: readonly InternalToolReference[], results: readonly IToolCallResult[], priority?: number) {
466
return (
467
<>
468
These attachments may have useful context for the user's query. The user may refer to these attachments directly using a term that starts with #.<br />
469
{...results.map((toolResult, i) => this.renderChatToolResult(tools[i].id, toolResult, priority))}
470
</>
471
);
472
}
473
474
private renderChatToolResult(id: string, toolResult: IToolCallResult, priority?: number): PromptElement {
475
return <Tag name='attachment' attrs={toolResult.name ? { tool: toolResult.name } : undefined} priority={priority}>
476
<meta value={new ToolResultMetadata(id, toolResult.value)}></meta>
477
<ToolResult content={toolResult.value.content} toolCallId={id} sessionId={this.props.promptContext.request?.sessionId} />
478
</Tag>;
479
}
480
481
private async fetchToolArgs(tool: LanguageModelToolInformation, endpoint: IChatEndpoint): Promise<any> {
482
const ownTool = this.toolsService.getCopilotTool(tool.name as ToolName);
483
if (typeof ownTool?.provideInput === 'function') {
484
const input = await ownTool.provideInput(this.props.promptContext);
485
if (input) {
486
return input;
487
}
488
}
489
490
if (!tool.inputSchema || Object.keys(tool.inputSchema).length === 0) {
491
return {};
492
}
493
494
const argFetchProps: GenericBasePromptElementProps = {
495
...this.props,
496
promptContext: {
497
...this.props.promptContext,
498
tools: undefined
499
}
500
};
501
const toolTokens = await endpoint.acquireTokenizer().countToolTokens([tool]);
502
const { messages } = await PromptRenderer.create(this.instantiationService, { ...endpoint, modelMaxPromptTokens: endpoint.modelMaxPromptTokens - toolTokens }, PanelChatBasePrompt, argFetchProps).render();
503
let fnCall: ICopilotToolCall | undefined;
504
const fetchResult = await endpoint.makeChatRequest(
505
'fetchToolArgs',
506
messages,
507
async (text, _, delta) => {
508
if (delta.copilotToolCalls) {
509
fnCall = delta.copilotToolCalls[0];
510
}
511
return undefined;
512
},
513
CancellationToken.None,
514
ChatLocation.Panel,
515
undefined,
516
{
517
tools: normalizeToolSchema(
518
endpoint.family,
519
[
520
{
521
type: 'function',
522
function: {
523
name: tool.name,
524
description: tool.description,
525
parameters: tool.inputSchema
526
}
527
}
528
],
529
(tool, rule) => this.logService.warn(`Tool ${tool} failed validation: ${rule}`)
530
),
531
tool_choice: {
532
type: 'function',
533
function: {
534
name: tool.name,
535
}
536
},
537
},
538
false
539
);
540
if (!fnCall) {
541
throw new Error(`Failed to compute args for tool: "${tool.name}"`);
542
}
543
544
if (fetchResult.type !== ChatFetchResponseType.Success) {
545
throw new Error(`Fetching tool args failed: ${fetchResult.type} ${fetchResult.reason}`);
546
}
547
548
try {
549
const args = JSON.parse(fnCall.arguments);
550
return args;
551
} catch (e) {
552
throw new Error('Invalid tool arguments: ' + e.message);
553
}
554
}
555
}
556
557