Skip to content

Commit f63842c

Browse files
authored
prompt files: completions for tool names (microsoft#252998)
add completions for tool names
1 parent ff360be commit f63842c

File tree

1 file changed

+82
-12
lines changed

1 file changed

+82
-12
lines changed

src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
7-
import { IPromptsService } from '../service/promptsService.js';
8-
import { ITextModel } from '../../../../../../editor/common/model.js';
6+
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
7+
import { CharCode } from '../../../../../../base/common/charCode.js';
98
import { Disposable } from '../../../../../../base/common/lifecycle.js';
10-
import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js';
119
import { Position } from '../../../../../../editor/common/core/position.js';
12-
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
13-
import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js';
14-
import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList } from '../../../../../../editor/common/languages.js';
1510
import { Range } from '../../../../../../editor/common/core/range.js';
11+
import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList } from '../../../../../../editor/common/languages.js';
12+
import { ITextModel } from '../../../../../../editor/common/model.js';
13+
import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js';
1614
import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js';
15+
import { ILanguageModelToolsService } from '../../languageModelToolsService.js';
16+
import { InstructionsHeader } from '../parsers/promptHeader/instructionsHeader.js';
17+
import { PromptToolsMetadata } from '../parsers/promptHeader/metadata/tools.js';
18+
import { ModeHeader } from '../parsers/promptHeader/modeHeader.js';
19+
import { PromptHeader } from '../parsers/promptHeader/promptHeader.js';
20+
import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js';
21+
import { IPromptsService } from '../service/promptsService.js';
1722

1823
export class PromptHeaderAutocompletion extends Disposable implements CompletionItemProvider {
1924
/**
@@ -30,7 +35,7 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
3035
@IPromptsService private readonly promptsService: IPromptsService,
3136
@ILanguageFeaturesService private readonly languageService: ILanguageFeaturesService,
3237
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
33-
38+
@ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService,
3439
) {
3540
super();
3641

@@ -61,10 +66,12 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
6166
return undefined;
6267
}
6368

64-
if (!parser.header) {
69+
const header = parser.header;
70+
if (!header) {
6571
return undefined;
6672
}
67-
await parser.header.settled;
73+
74+
await header.settled;
6875

6976
const fullHeaderRange = parser.header.range;
7077
const headerRange = new Range(fullHeaderRange.startLineNumber + 1, 0, fullHeaderRange.endLineNumber - 1, model.getLineMaxColumn(fullHeaderRange.endLineNumber - 1),);
@@ -81,7 +88,7 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
8188
if (!colonPosition || position.isBeforeOrEqual(colonPosition)) {
8289
return this.providePropertyCompletions(model, position, headerRange, colonPosition, promptType);
8390
} else if (colonPosition && colonPosition.isBefore(position)) {
84-
return this.provideValueCompletions(model, position, headerRange, colonPosition, promptType);
91+
return this.provideValueCompletions(model, position, header, colonPosition, promptType);
8592
}
8693
return undefined;
8794
}
@@ -127,7 +134,7 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
127134
private async provideValueCompletions(
128135
model: ITextModel,
129136
position: Position,
130-
headerRange: Range,
137+
header: PromptHeader | ModeHeader | InstructionsHeader,
131138
colonPosition: Position,
132139
promptType: string,
133140
): Promise<CompletionList | undefined> {
@@ -139,6 +146,19 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
139146
if (!this.getSupportedProperties(promptType).has(property)) {
140147
return undefined;
141148
}
149+
150+
if (header instanceof PromptHeader || header instanceof ModeHeader) {
151+
const tools = header.metadataUtility.tools;
152+
if (tools) {
153+
// if the position is inside the tools metadata, we provide tool name completions
154+
const result = this.provideToolCompletions(model, position, tools);
155+
if (result) {
156+
return result;
157+
}
158+
}
159+
}
160+
161+
142162
const bracketIndex = lineContent.indexOf('[');
143163
if (bracketIndex !== -1 && bracketIndex <= position.column - 1) {
144164
// if the property is already inside a bracket, we don't provide value completions
@@ -211,4 +231,54 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
211231
}
212232
return result;
213233
}
234+
235+
private provideToolCompletions(model: ITextModel, position: Position, node: PromptToolsMetadata): CompletionList | undefined {
236+
const tools = node.value;
237+
if (!tools || !node.range.containsPosition(position)) {
238+
return undefined;
239+
}
240+
const getSuggestions = (toolRange: Range) => {
241+
const suggestions: CompletionItem[] = [];
242+
const addSuggestion = (toolName: string, toolRange: Range) => {
243+
let insertText: string;
244+
if (!toolRange.isEmpty()) {
245+
const firstChar = model.getValueInRange(toolRange).charCodeAt(0);
246+
insertText = firstChar === CharCode.SingleQuote ? `'${toolName}'` : firstChar === CharCode.DoubleQuote ? `"${toolName}"` : toolName;
247+
} else {
248+
insertText = `'${toolName}'`;
249+
}
250+
suggestions.push({
251+
label: toolName,
252+
kind: CompletionItemKind.Value,
253+
filterText: insertText,
254+
insertText: insertText,
255+
range: toolRange,
256+
});
257+
};
258+
for (const tool of this.languageModelToolsService.getTools()) {
259+
if (tool.canBeReferencedInPrompt) {
260+
addSuggestion(tool.toolReferenceName ?? tool.displayName, toolRange);
261+
}
262+
}
263+
for (const toolSet of this.languageModelToolsService.toolSets.get()) {
264+
addSuggestion(toolSet.referenceName, toolRange);
265+
}
266+
return { suggestions };
267+
};
268+
269+
for (const tool of tools) {
270+
const toolRange = node.getToolRange(tool);
271+
if (toolRange?.containsPosition(position)) {
272+
// if the position is inside a tool range, we provide tool name completions
273+
return getSuggestions(toolRange);
274+
}
275+
}
276+
const prefix = model.getValueInRange(new Range(position.lineNumber, 1, position.lineNumber, position.column));
277+
if (prefix.match(/[,[]\s*$/)) {
278+
// if the position is after a comma or bracket
279+
return getSuggestions(new Range(position.lineNumber, position.column, position.lineNumber, position.column));
280+
}
281+
return undefined;
282+
}
283+
214284
}

0 commit comments

Comments
 (0)