mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
qualify tool names in prompt files (#268574)
* qualified tool names * qualify tool names in prompt files
This commit is contained in:
committed by
GitHub
parent
2a3807c292
commit
dfe41ec1bf
@@ -596,58 +596,73 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
}
|
||||
}
|
||||
|
||||
toToolEnablementMap(toolOrToolsetNames: Set<string>): Record<string, boolean> {
|
||||
const result: Record<string, boolean> = {};
|
||||
for (const tool of this._tools.values()) {
|
||||
if (tool.data.toolReferenceName && toolOrToolsetNames.has(tool.data.toolReferenceName)) {
|
||||
result[tool.data.id] = true;
|
||||
} else {
|
||||
result[tool.data.id] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const toolSet of this._toolSets) {
|
||||
if (toolOrToolsetNames.has(toolSet.referenceName)) {
|
||||
for (const tool of toolSet.getTools()) {
|
||||
result[tool.id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a map that contains all tools and toolsets with their enablement state.
|
||||
* @param toolOrToolSetNames A list of tool or toolset names that are enabled.
|
||||
* @returns A map of tool or toolset instances to their enablement state.
|
||||
*/
|
||||
toToolAndToolSetEnablementMap(enabledToolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap {
|
||||
const toolOrToolSetNames = new Set(enabledToolOrToolSetNames);
|
||||
toToolAndToolSetEnablementMap(enabledQualifiedToolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap {
|
||||
const toolOrToolSetNames = new Set(enabledQualifiedToolOrToolSetNames);
|
||||
const result = new Map<ToolSet | IToolData, boolean>();
|
||||
for (const tool of this.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
result.set(tool, toolOrToolSetNames.has(tool.toolReferenceName ?? tool.displayName));
|
||||
const enabled = toolOrToolSetNames.has(getToolReferenceName(tool)) || /* legacy */ toolOrToolSetNames.has(tool.toolReferenceName ?? tool.displayName);
|
||||
result.set(tool, enabled);
|
||||
}
|
||||
}
|
||||
for (const toolSet of this._toolSets) {
|
||||
const enabled = toolOrToolSetNames.has(toolSet.referenceName);
|
||||
result.set(toolSet, enabled);
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
const toolSetEnabled = toolOrToolSetNames.has(getToolSetReferenceName(toolSet)) || /* legacy */ toolOrToolSetNames.has(toolSet.referenceName);
|
||||
result.set(toolSet, toolSetEnabled);
|
||||
for (const tool of toolSet.getTools()) {
|
||||
result.set(tool, enabled || toolOrToolSetNames?.has(tool.toolReferenceName ?? tool.displayName));
|
||||
const enabled = toolSetEnabled || toolOrToolSetNames.has(getToolReferenceName(tool, toolSet)) || /* legacy */ toolOrToolSetNames.has(tool.toolReferenceName ?? tool.displayName);
|
||||
result.set(tool, enabled);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] {
|
||||
const toolsOrToolSetByName = new Map<string, ToolSet | IToolData>();
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
toolsOrToolSetByName.set(toolSet.referenceName, toolSet);
|
||||
toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[] {
|
||||
const toolsCoveredBySets = new Set<IToolData>();
|
||||
for (const item of map.keys()) {
|
||||
if (item instanceof ToolSet) {
|
||||
for (const tool of item.getTools()) {
|
||||
toolsCoveredBySets.add(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result: string[] = [];
|
||||
for (const tool of this.getTools()) {
|
||||
toolsOrToolSetByName.set(tool.toolReferenceName ?? tool.displayName, tool);
|
||||
if (map.get(tool) && !toolsCoveredBySets.has(tool)) {
|
||||
result.push(getToolReferenceName(tool));
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
if (map.get(toolSet)) {
|
||||
result.push(getToolSetReferenceName(toolSet));
|
||||
} else {
|
||||
for (const tool of toolSet.getTools()) {
|
||||
if (map.get(tool)) {
|
||||
result.push(getToolReferenceName(tool, toolSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] {
|
||||
const toolsOrToolSetByName = new Map<string, ToolSet | IToolData>();
|
||||
for (const tool of this.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
toolsOrToolSetByName.set(getToolReferenceName(tool), tool);
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
toolsOrToolSetByName.set(getToolSetReferenceName(toolSet), toolSet);
|
||||
for (const tool of toolSet.getTools()) {
|
||||
toolsOrToolSetByName.set(getToolReferenceName(tool, toolSet), tool);
|
||||
}
|
||||
}
|
||||
|
||||
const result: ChatRequestToolReferenceEntry[] = [];
|
||||
@@ -704,6 +719,93 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
|
||||
this._toolSets.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
*getQualifiedToolNames(): Iterable<string> {
|
||||
for (const tool of this.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
yield getToolReferenceName(tool);
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
yield getToolSetReferenceName(toolSet);
|
||||
for (const tool of toolSet.getTools()) {
|
||||
yield getToolReferenceName(tool, toolSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDeprecatedQualifiedToolNames(): Map<string, string> {
|
||||
const result = new Map<string, string>();
|
||||
const add = (name: string, qualifiedName: string) => {
|
||||
if (name !== qualifiedName) {
|
||||
result.set(name, qualifiedName);
|
||||
}
|
||||
};
|
||||
for (const tool of this.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
add(tool.toolReferenceName ?? tool.displayName, getToolReferenceName(tool));
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
add(toolSet.referenceName, getToolSetReferenceName(toolSet));
|
||||
for (const tool of toolSet.getTools()) {
|
||||
add(tool.toolReferenceName ?? tool.displayName, getToolReferenceName(tool, toolSet));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getToolByQualifiedName(qualifiedName: string): IToolData | ToolSet | undefined {
|
||||
for (const tool of this.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
if (qualifiedName === getToolReferenceName(tool) || qualifiedName === (tool.toolReferenceName ?? tool.displayName) /* legacy */) {
|
||||
if (matchesToolReferenceName(tool, qualifiedName)) {
|
||||
return tool;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.toolSets.get()) {
|
||||
if (qualifiedName === getToolSetReferenceName(toolSet) || qualifiedName === toolSet.referenceName /* legacy */) {
|
||||
return toolSet;
|
||||
}
|
||||
for (const tool of toolSet.getTools()) {
|
||||
if (qualifiedName === getToolReferenceName(tool) || qualifiedName === (tool.toolReferenceName ?? tool.displayName) /* legacy */) {
|
||||
return tool;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getQualifiedToolName(tool: IToolData | ToolSet, toolSet?: ToolSet): string {
|
||||
if (tool instanceof ToolSet) {
|
||||
return getToolSetReferenceName(tool);
|
||||
}
|
||||
return getToolReferenceName(tool, toolSet);
|
||||
}
|
||||
}
|
||||
|
||||
function getToolReferenceName(tool: IToolData, toolSet?: ToolSet) {
|
||||
const toolName = tool.toolReferenceName ?? tool.displayName;
|
||||
if (toolSet) {
|
||||
return `${toolSet.referenceName}/${toolName}`;
|
||||
} else if (tool.source.type === 'extension') {
|
||||
return `${tool.source.extensionId.value.toLowerCase()}/${toolName}`;
|
||||
}
|
||||
return toolName;
|
||||
}
|
||||
|
||||
function getToolSetReferenceName(toolSet: ToolSet) {
|
||||
if (toolSet.source.type === 'mcp') {
|
||||
return `${toolSet.referenceName}/*`;
|
||||
}
|
||||
return toolSet.referenceName;
|
||||
}
|
||||
|
||||
function matchesToolReferenceName(tool: IToolData, name: string, toolSet?: ToolSet) {
|
||||
const toolName = tool.toolReferenceName ?? tool.displayName;
|
||||
return name === toolName || (toolSet && name === `${toolSet.referenceName}/${toolName}`);
|
||||
}
|
||||
|
||||
type LanguageModelToolInvokedEvent = {
|
||||
|
||||
@@ -9,13 +9,14 @@ import { ICodeEditorService } from '../../../../../editor/browser/services/codeE
|
||||
import { EditOperation } from '../../../../../editor/common/core/editOperation.js';
|
||||
import { Range } from '../../../../../editor/common/core/range.js';
|
||||
import { ITextModel } from '../../../../../editor/common/model.js';
|
||||
import { IToolAndToolSetEnablementMap, IToolData, ToolSet } from '../../common/languageModelToolsService.js';
|
||||
import { ILanguageModelToolsService, IToolAndToolSetEnablementMap } from '../../common/languageModelToolsService.js';
|
||||
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
|
||||
|
||||
export class PromptFileRewriter {
|
||||
constructor(
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IPromptsService private readonly _promptsService: IPromptsService
|
||||
@IPromptsService private readonly _promptsService: IPromptsService,
|
||||
@ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -48,24 +49,7 @@ export class PromptFileRewriter {
|
||||
}
|
||||
|
||||
public getNewValueString(tools: IToolAndToolSetEnablementMap): string {
|
||||
const newToolNames: string[] = [];
|
||||
const toolsCoveredBySets = new Set<IToolData>();
|
||||
for (const [item, picked] of tools) {
|
||||
if (picked && item instanceof ToolSet) {
|
||||
for (const tool of item.getTools()) {
|
||||
toolsCoveredBySets.add(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [item, picked] of tools) {
|
||||
if (picked) {
|
||||
if (item instanceof ToolSet) {
|
||||
newToolNames.push(item.referenceName);
|
||||
} else if (!toolsCoveredBySets.has(item)) {
|
||||
newToolNames.push(item.toolReferenceName ?? item.displayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
const newToolNames = this._languageModelToolsService.toQualifiedToolNames(tools);
|
||||
return `[${newToolNames.map(s => `'${s}'`).join(', ')}]`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,14 +324,22 @@ export interface ILanguageModelToolsService {
|
||||
getToolAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never';
|
||||
resetToolAutoConfirmation(): void;
|
||||
cancelToolCallsForRequest(requestId: string): void;
|
||||
toToolEnablementMap(toolOrToolSetNames: Set<string>): Record<string, boolean>;
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap;
|
||||
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[];
|
||||
|
||||
readonly toolSets: IObservable<Iterable<ToolSet>>;
|
||||
getToolSet(id: string): ToolSet | undefined;
|
||||
getToolSetByName(name: string): ToolSet | undefined;
|
||||
createToolSet(source: ToolDataSource, id: string, referenceName: string, options?: { icon?: ThemeIcon; description?: string }): ToolSet & IDisposable;
|
||||
|
||||
// tool names in prompt files handling ('qualified names')
|
||||
|
||||
getQualifiedToolNames(): Iterable<string>;
|
||||
getToolByQualifiedName(qualifiedName: string): IToolData | ToolSet | undefined;
|
||||
getQualifiedToolName(tool: IToolData, toolSet?: ToolSet): string;
|
||||
getDeprecatedQualifiedToolNames(): Map<string, string>;
|
||||
|
||||
toToolAndToolSetEnablementMap(qualifiedToolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap;
|
||||
toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[];
|
||||
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[];
|
||||
}
|
||||
|
||||
export function createToolInputUri(toolOrId: IToolData | string): URI {
|
||||
|
||||
+1
-9
@@ -70,7 +70,7 @@ export class PromptBodyAutocompletion extends Disposable implements CompletionIt
|
||||
}
|
||||
|
||||
private async collectToolCompletions(model: ITextModel, position: Position, toolRange: Range, suggestions: CompletionItem[]): Promise<void> {
|
||||
const addSuggestion = (toolName: string, toolRange: Range) => {
|
||||
for (const toolName of this.languageModelToolsService.getQualifiedToolNames()) {
|
||||
suggestions.push({
|
||||
label: toolName,
|
||||
kind: CompletionItemKind.Value,
|
||||
@@ -78,14 +78,6 @@ export class PromptBodyAutocompletion extends Disposable implements CompletionIt
|
||||
insertText: toolName,
|
||||
range: toolRange,
|
||||
});
|
||||
};
|
||||
for (const tool of this.languageModelToolsService.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
addSuggestion(tool.toolReferenceName ?? tool.displayName, toolRange);
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.languageModelToolsService.toolSets.get()) {
|
||||
addSuggestion(toolSet.referenceName, toolRange);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 } from '../../../../../../base/common/lifecycle.js';
|
||||
import { Range } from '../../../../../../editor/common/core/range.js';
|
||||
import { CodeActionContext, CodeActionList, CodeActionProvider, ProviderResult, TextEdit, WorkspaceEdit } from '../../../../../../editor/common/languages.js';
|
||||
import { ITextModel } from '../../../../../../editor/common/model.js';
|
||||
import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js';
|
||||
import { localize } from '../../../../../../nls.js';
|
||||
import { ILanguageModelToolsService } from '../../languageModelToolsService.js';
|
||||
import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId } from '../promptTypes.js';
|
||||
import { IPromptsService } from '../service/promptsService.js';
|
||||
import { IValue } from '../service/newPromptsParser.js';
|
||||
import { Selection } from '../../../../../../editor/common/core/selection.js';
|
||||
|
||||
export class PromptCodeActionProvider extends Disposable implements CodeActionProvider {
|
||||
/**
|
||||
* Debug display name for this provider.
|
||||
*/
|
||||
public readonly _debugDisplayName: string = 'PromptHoverProvider';
|
||||
|
||||
constructor(
|
||||
@IPromptsService private readonly promptsService: IPromptsService,
|
||||
@ILanguageFeaturesService private readonly languageService: ILanguageFeaturesService,
|
||||
@ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this.languageService.codeActionProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, this));
|
||||
}
|
||||
|
||||
provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<CodeActionList> {
|
||||
const promptType = getPromptsTypeForLanguageId(model.getLanguageId());
|
||||
if (!promptType) {
|
||||
// if the model is not a prompt, we don't provide any hovers
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parser = this.promptsService.getParsedPromptFile(model);
|
||||
const toolsAttr = parser.header?.getAttribute('tools');
|
||||
if (!toolsAttr || toolsAttr.value.type !== 'array' || !toolsAttr.value.range.containsRange(range)) {
|
||||
return undefined;
|
||||
}
|
||||
for (const item of toolsAttr.value.items) {
|
||||
if (item.range.containsRange(range)) {
|
||||
return this.getToolCodeActions(item, model);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getToolCodeActions(value: IValue, model: ITextModel): CodeActionList | undefined {
|
||||
if (value.type !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
const oldName = value.value;
|
||||
const deprecatedNames = this.languageModelToolsService.getDeprecatedQualifiedToolNames();
|
||||
const newName = deprecatedNames.get(oldName);
|
||||
if (newName) {
|
||||
const quote = model.getValueInRange(new Range(value.range.startLineNumber, value.range.startColumn, value.range.endLineNumber, value.range.startColumn + 1));
|
||||
const text = (quote === `'` || quote === '"') ? (quote + newName + quote) : newName;
|
||||
return {
|
||||
actions: [{
|
||||
title: localize('replaceWith', "Replace with '{0}'", newName),
|
||||
edit: asWorkspaceEdit(model, { range: value.range, text: text })
|
||||
}],
|
||||
dispose() { }
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
function asWorkspaceEdit(model: ITextModel, textEdit: TextEdit): WorkspaceEdit {
|
||||
return {
|
||||
edits: [{
|
||||
versionId: model.getVersionId(),
|
||||
resource: model.uri,
|
||||
textEdit
|
||||
}]
|
||||
};
|
||||
}
|
||||
+1
-9
@@ -222,7 +222,7 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
|
||||
}
|
||||
const getSuggestions = (toolRange: Range) => {
|
||||
const suggestions: CompletionItem[] = [];
|
||||
const addSuggestion = (toolName: string, toolRange: Range) => {
|
||||
for (const toolName of this.languageModelToolsService.getQualifiedToolNames()) {
|
||||
let insertText: string;
|
||||
if (!toolRange.isEmpty()) {
|
||||
const firstChar = model.getValueInRange(toolRange).charCodeAt(0);
|
||||
@@ -237,14 +237,6 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion
|
||||
insertText: insertText,
|
||||
range: toolRange,
|
||||
});
|
||||
};
|
||||
for (const tool of this.languageModelToolsService.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
addSuggestion(tool.toolReferenceName ?? tool.displayName, toolRange);
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.languageModelToolsService.toolSets.get()) {
|
||||
addSuggestion(toolSet.referenceName, toolRange);
|
||||
}
|
||||
return { suggestions };
|
||||
};
|
||||
|
||||
@@ -129,13 +129,13 @@ export class PromptHoverProvider extends Disposable implements HoverProvider {
|
||||
}
|
||||
|
||||
private getToolHoverByName(toolName: string, range: Range): Hover | undefined {
|
||||
const tool = this.languageModelToolsService.getToolByName(toolName);
|
||||
if (tool) {
|
||||
return this.createHover(tool.modelDescription, range);
|
||||
}
|
||||
const toolSet = this.languageModelToolsService.getToolSetByName(toolName);
|
||||
if (toolSet) {
|
||||
return this.getToolsetHover(toolSet, range);
|
||||
const tool = this.languageModelToolsService.getToolByQualifiedName(toolName);
|
||||
if (tool !== undefined) {
|
||||
if (tool instanceof ToolSet) {
|
||||
return this.getToolsetHover(tool, range);
|
||||
} else {
|
||||
return this.createHover(tool.modelDescription, range);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PromptHoverProvider } from './languageProviders/promptHovers.js';
|
||||
import { PromptHeaderDefinitionProvider } from './languageProviders/PromptHeaderDefinitionProvider.js';
|
||||
import { PromptValidatorContribution } from './service/promptValidator.js';
|
||||
import { PromptDocumentSemanticTokensProvider } from './languageProviders/promptDocumentSemanticTokensProvider.js';
|
||||
import { PromptCodeActionProvider } from './languageProviders/promptCodeActions.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,7 @@ export function registerPromptFileContributions(): void {
|
||||
registerContribution(PromptHoverProvider);
|
||||
registerContribution(PromptHeaderDefinitionProvider);
|
||||
registerContribution(PromptDocumentSemanticTokensProvider);
|
||||
registerContribution(PromptCodeActionProvider);
|
||||
registerContribution(ConfigMigration);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,10 +72,16 @@ export class PromptValidator {
|
||||
|
||||
// Validate variable references (tool or toolset names)
|
||||
if (body.variableReferences.length) {
|
||||
const available = this.getAvailableToolAndToolSetNames();
|
||||
const available = new Set<string>(this.languageModelToolsService.getQualifiedToolNames());
|
||||
const deprecatedNames = this.languageModelToolsService.getDeprecatedQualifiedToolNames();
|
||||
for (const variable of body.variableReferences) {
|
||||
if (!available.has(variable.name)) {
|
||||
report(toMarker(localize('promptValidator.unknownVariableReference', "Unknown tool or toolset '{0}'.", variable.name), variable.range, MarkerSeverity.Warning));
|
||||
if (deprecatedNames.has(variable.name)) {
|
||||
const currentName = deprecatedNames.get(variable.name);
|
||||
report(toMarker(localize('promptValidator.deprecatedVariableReference', "Tool or toolset '{0}' is deprecated, use '{1}' instead.", variable.name, currentName), variable.range, MarkerSeverity.Warning));
|
||||
} else {
|
||||
report(toMarker(localize('promptValidator.unknownVariableReference', "Unknown tool or toolset '{0}'.", variable.name), variable.range, MarkerSeverity.Error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,33 +242,23 @@ export class PromptValidator {
|
||||
|
||||
private validateToolsArray(valueItem: IArrayValue, report: (markers: IMarkerData) => void) {
|
||||
if (valueItem.items.length > 0) {
|
||||
const available = this.getAvailableToolAndToolSetNames();
|
||||
const available = new Set<string>(this.languageModelToolsService.getQualifiedToolNames());
|
||||
const deprecatedNames = this.languageModelToolsService.getDeprecatedQualifiedToolNames();
|
||||
for (const item of valueItem.items) {
|
||||
if (item.type !== 'string') {
|
||||
report(toMarker(localize('promptValidator.eachToolMustBeString', "Each tool name in the 'tools' attribute must be a string."), item.range, MarkerSeverity.Error));
|
||||
} else if (item.value && !available.has(item.value)) {
|
||||
report(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'.", item.value), item.range, MarkerSeverity.Warning));
|
||||
if (deprecatedNames.has(item.value)) {
|
||||
const currentName = deprecatedNames.get(item.value);
|
||||
report(toMarker(localize('promptValidator.toolDeprecated', "Tool or toolset '{0}' is deprecated, use '{1}' instead.", item.value, currentName), item.range, MarkerSeverity.Warning));
|
||||
} else {
|
||||
report(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'.", item.value), item.range, MarkerSeverity.Error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getAvailableToolAndToolSetNames(): Set<string> {
|
||||
const available = new Set<string>();
|
||||
for (const tool of this.languageModelToolsService.getTools()) {
|
||||
if (tool.canBeReferencedInPrompt) {
|
||||
available.add(tool.toolReferenceName ?? tool.displayName);
|
||||
}
|
||||
}
|
||||
for (const toolSet of this.languageModelToolsService.toolSets.get()) {
|
||||
available.add(toolSet.referenceName);
|
||||
for (const tool of toolSet.getTools()) {
|
||||
available.add(tool.toolReferenceName ?? tool.displayName);
|
||||
}
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
private validateApplyTo(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined {
|
||||
const attribute = attributes.find(attr => attr.key === 'applyTo');
|
||||
if (!attribute) {
|
||||
|
||||
@@ -23,6 +23,8 @@ import { IChatService, IChatToolInputInvocationData } from '../../common/chatSer
|
||||
import { IToolData, IToolImpl, IToolInvocation, ToolDataSource } from '../../common/languageModelToolsService.js';
|
||||
import { MockChatService } from '../common/mockChatService.js';
|
||||
import { IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js';
|
||||
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
|
||||
import { ChatConfiguration } from '../../common/constants.js';
|
||||
|
||||
// --- Test helpers to reduce repetition and improve readability ---
|
||||
|
||||
@@ -100,6 +102,7 @@ suite('LanguageModelToolsService', () => {
|
||||
|
||||
setup(() => {
|
||||
configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true);
|
||||
const instaService = workbenchInstantiationService({
|
||||
contextKeyService: () => store.add(new ContextKeyService(configurationService)),
|
||||
configurationService: () => configurationService
|
||||
@@ -378,13 +381,14 @@ suite('LanguageModelToolsService', () => {
|
||||
}, 'Expected tool call to be cancelled');
|
||||
});
|
||||
|
||||
test('toToolEnablementMap', () => {
|
||||
test('toToolAndToolSetEnablementMap', () => {
|
||||
const toolData1: IToolData = {
|
||||
id: 'tool1',
|
||||
toolReferenceName: 'refTool1',
|
||||
modelDescription: 'Test Tool 1',
|
||||
displayName: 'Test Tool 1',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
const toolData2: IToolData = {
|
||||
@@ -393,6 +397,7 @@ suite('LanguageModelToolsService', () => {
|
||||
modelDescription: 'Test Tool 2',
|
||||
displayName: 'Test Tool 2',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
const toolData3: IToolData = {
|
||||
@@ -401,6 +406,7 @@ suite('LanguageModelToolsService', () => {
|
||||
modelDescription: 'Test Tool 3',
|
||||
displayName: 'Test Tool 3',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
store.add(service.registerToolData(toolData1));
|
||||
@@ -408,31 +414,66 @@ suite('LanguageModelToolsService', () => {
|
||||
store.add(service.registerToolData(toolData3));
|
||||
|
||||
// Test with enabled tools
|
||||
const enabledToolNames = new Set(['refTool1']);
|
||||
const result1 = service.toToolEnablementMap(enabledToolNames);
|
||||
const enabledToolNames = [toolData1].map(t => service.getQualifiedToolName(t));
|
||||
const result1 = service.toToolAndToolSetEnablementMap(enabledToolNames);
|
||||
|
||||
assert.strictEqual(result1.get(toolData1), true, 'tool1 should be enabled');
|
||||
assert.strictEqual(result1.get(toolData2), false, 'tool2 should be disabled');
|
||||
assert.strictEqual(result1.get(toolData3), false, 'tool3 should be disabled (no reference name)');
|
||||
|
||||
const qualifiedNames1 = service.toQualifiedToolNames(result1);
|
||||
assert.deepStrictEqual(qualifiedNames1.sort(), enabledToolNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
|
||||
assert.strictEqual(result1['tool1'], true, 'tool1 should be enabled');
|
||||
assert.strictEqual(result1['tool2'], false, 'tool2 should be disabled');
|
||||
assert.strictEqual(result1['tool3'], false, 'tool3 should be disabled (no reference name)');
|
||||
|
||||
// Test with multiple enabled tools
|
||||
const multipleEnabledToolNames = new Set(['refTool1', 'refTool2']);
|
||||
const result2 = service.toToolEnablementMap(multipleEnabledToolNames);
|
||||
const multipleEnabledToolNames = [toolData1, toolData2].map(t => service.getQualifiedToolName(t));
|
||||
const result2 = service.toToolAndToolSetEnablementMap(multipleEnabledToolNames);
|
||||
|
||||
assert.strictEqual(result1.get(toolData1), true, 'tool1 should be enabled');
|
||||
assert.strictEqual(result2.get(toolData2), true, 'tool2 should be enabled');
|
||||
assert.strictEqual(result2.get(toolData3), false, 'tool3 should be disabled');
|
||||
|
||||
const qualifiedNames2 = service.toQualifiedToolNames(result2);
|
||||
assert.deepStrictEqual(qualifiedNames2.sort(), multipleEnabledToolNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
|
||||
assert.strictEqual(result2['tool1'], true, 'tool1 should be enabled');
|
||||
assert.strictEqual(result2['tool2'], true, 'tool2 should be enabled');
|
||||
assert.strictEqual(result2['tool3'], false, 'tool3 should be disabled');
|
||||
|
||||
// Test with no enabled tools
|
||||
const noEnabledToolNames = new Set<string>();
|
||||
const result3 = service.toToolEnablementMap(noEnabledToolNames);
|
||||
const noEnabledToolNames: string[] = [];
|
||||
const result3 = service.toToolAndToolSetEnablementMap(noEnabledToolNames);
|
||||
|
||||
assert.strictEqual(result3.get(toolData1), false, 'tool1 should be disabled');
|
||||
assert.strictEqual(result3.get(toolData2), false, 'tool2 should be disabled');
|
||||
assert.strictEqual(result3.get(toolData3), false, 'tool3 should be disabled');
|
||||
|
||||
const qualifiedNames3 = service.toQualifiedToolNames(result3);
|
||||
assert.deepStrictEqual(qualifiedNames3.sort(), noEnabledToolNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
|
||||
assert.strictEqual(result3['tool1'], false, 'tool1 should be disabled');
|
||||
assert.strictEqual(result3['tool2'], false, 'tool2 should be disabled');
|
||||
assert.strictEqual(result3['tool3'], false, 'tool3 should be disabled');
|
||||
});
|
||||
|
||||
test('toToolEnablementMap with tool sets', () => {
|
||||
test('toToolAndToolSetEnablementMap with extension tool', () => {
|
||||
// Register individual tools
|
||||
const toolData1: IToolData = {
|
||||
id: 'tool1',
|
||||
toolReferenceName: 'refTool1',
|
||||
modelDescription: 'Test Tool 1',
|
||||
displayName: 'Test Tool 1',
|
||||
source: { type: 'extension', label: "My Extension", extensionId: new ExtensionIdentifier('My.extension') },
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
store.add(service.registerToolData(toolData1));
|
||||
|
||||
// Test enabling the tool set
|
||||
const enabledNames = [toolData1].map(t => service.getQualifiedToolName(t));
|
||||
const result = service.toToolAndToolSetEnablementMap(enabledNames);
|
||||
|
||||
assert.strictEqual(result.get(toolData1), true, 'individual tool should be enabled');
|
||||
|
||||
const qualifiedNames = service.toQualifiedToolNames(result);
|
||||
assert.deepStrictEqual(qualifiedNames.sort(), enabledNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
});
|
||||
|
||||
test('toToolAndToolSetEnablementMap with tool sets', () => {
|
||||
// Register individual tools
|
||||
const toolData1: IToolData = {
|
||||
id: 'tool1',
|
||||
@@ -440,6 +481,7 @@ suite('LanguageModelToolsService', () => {
|
||||
modelDescription: 'Test Tool 1',
|
||||
displayName: 'Test Tool 1',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
const toolData2: IToolData = {
|
||||
@@ -447,6 +489,7 @@ suite('LanguageModelToolsService', () => {
|
||||
modelDescription: 'Test Tool 2',
|
||||
displayName: 'Test Tool 2',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
store.add(service.registerToolData(toolData1));
|
||||
@@ -481,33 +524,52 @@ suite('LanguageModelToolsService', () => {
|
||||
store.add(toolSet.addTool(toolSetTool2));
|
||||
|
||||
// Test enabling the tool set
|
||||
const enabledNames = new Set(['refToolSet', 'refTool1']);
|
||||
const result = service.toToolEnablementMap(enabledNames);
|
||||
const enabledNames = [toolSet, toolData1].map(t => service.getQualifiedToolName(t));
|
||||
const result = service.toToolAndToolSetEnablementMap(enabledNames);
|
||||
|
||||
assert.strictEqual(result['tool1'], true, 'individual tool should be enabled');
|
||||
assert.strictEqual(result['tool2'], false);
|
||||
assert.strictEqual(result['toolSetTool1'], true, 'tool set tool 1 should be enabled');
|
||||
assert.strictEqual(result['toolSetTool2'], true, 'tool set tool 2 should be enabled');
|
||||
assert.strictEqual(result.get(toolData1), true, 'individual tool should be enabled');
|
||||
assert.strictEqual(result.get(toolData2), false);
|
||||
assert.strictEqual(result.get(toolSet), true, 'tool set should be enabled');
|
||||
assert.strictEqual(result.get(toolSetTool1), true, 'tool set tool 1 should be enabled');
|
||||
assert.strictEqual(result.get(toolSetTool2), true, 'tool set tool 2 should be enabled');
|
||||
|
||||
const qualifiedNames = service.toQualifiedToolNames(result);
|
||||
assert.deepStrictEqual(qualifiedNames.sort(), enabledNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
});
|
||||
|
||||
test('toToolEnablementMap with non-existent tool names', () => {
|
||||
test('toToolAndToolSetEnablementMap with non-existent tool names', () => {
|
||||
const toolData: IToolData = {
|
||||
id: 'tool1',
|
||||
toolReferenceName: 'refTool1',
|
||||
modelDescription: 'Test Tool 1',
|
||||
displayName: 'Test Tool 1',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
store.add(service.registerToolData(toolData));
|
||||
|
||||
// Test with non-existent tool names
|
||||
const enabledNames = new Set(['nonExistentTool', 'refTool1']);
|
||||
const result = service.toToolEnablementMap(enabledNames);
|
||||
const unregisteredToolData: IToolData = {
|
||||
id: 'toolX',
|
||||
toolReferenceName: 'refToolX',
|
||||
modelDescription: 'Test Tool X',
|
||||
displayName: 'Test Tool X',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: true,
|
||||
};
|
||||
|
||||
assert.strictEqual(result['tool1'], true, 'existing tool should be enabled');
|
||||
// Test with non-existent tool names
|
||||
const enabledNames = [toolData, unregisteredToolData].map(t => service.getQualifiedToolName(t));
|
||||
const result = service.toToolAndToolSetEnablementMap(enabledNames);
|
||||
|
||||
assert.strictEqual(result.get(toolData), true, 'existing tool should be enabled');
|
||||
// Non-existent tools should not appear in the result map
|
||||
assert.strictEqual(result['nonExistentTool'], undefined, 'non-existent tool should not be in result');
|
||||
assert.strictEqual(result.get(unregisteredToolData), undefined, 'non-existent tool should not be in result');
|
||||
|
||||
const qualifiedNames = service.toQualifiedToolNames(result);
|
||||
const expectedNames = [service.getQualifiedToolName(toolData)]; // Only the existing tool
|
||||
assert.deepStrictEqual(qualifiedNames.sort(), expectedNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
|
||||
});
|
||||
|
||||
test('accessibility signal for tool confirmation', async () => {
|
||||
@@ -1298,21 +1360,28 @@ suite('LanguageModelToolsService', () => {
|
||||
store.add(mcpToolSet.addTool(mcpTool));
|
||||
|
||||
// Enable the MCP toolset
|
||||
const result = service.toToolAndToolSetEnablementMap(['mcpSetRef']);
|
||||
{
|
||||
const enabledNames = [mcpToolSet].map(t => service.getQualifiedToolName(t));
|
||||
const result = service.toToolAndToolSetEnablementMap(enabledNames);
|
||||
|
||||
let toolSetEnabled = false;
|
||||
let toolEnabled = false;
|
||||
for (const [toolOrSet, enabled] of result) {
|
||||
if ('referenceName' in toolOrSet && toolOrSet.referenceName === 'mcpSetRef') {
|
||||
toolSetEnabled = enabled;
|
||||
}
|
||||
if ('id' in toolOrSet && toolOrSet.id === 'mcpTool') {
|
||||
toolEnabled = enabled;
|
||||
}
|
||||
assert.strictEqual(result.get(mcpToolSet), true, 'MCP toolset should be enabled'); // Ensure the toolset is in the map
|
||||
assert.strictEqual(result.get(mcpTool), true, 'MCP tool should be enabled when its toolset is enabled'); // Ensure the tool is in the map
|
||||
|
||||
const qualifiedNames = service.toQualifiedToolNames(result);
|
||||
assert.deepStrictEqual(qualifiedNames.sort(), enabledNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
}
|
||||
// Enable a tool from the MCP toolset
|
||||
{
|
||||
const enabledNames = [mcpTool].map(t => service.getQualifiedToolName(t, mcpToolSet));
|
||||
const result = service.toToolAndToolSetEnablementMap(enabledNames);
|
||||
|
||||
assert.strictEqual(result.get(mcpToolSet), false, 'MCP toolset should be disabled'); // Ensure the toolset is in the map
|
||||
assert.strictEqual(result.get(mcpTool), true, 'MCP tool should be enabled'); // Ensure the tool is in the map
|
||||
|
||||
const qualifiedNames = service.toQualifiedToolNames(result);
|
||||
assert.deepStrictEqual(qualifiedNames.sort(), enabledNames.sort(), 'toQualifiedToolNames should return the original enabled names');
|
||||
}
|
||||
|
||||
assert.strictEqual(toolSetEnabled, true, 'MCP toolset should be enabled');
|
||||
assert.strictEqual(toolEnabled, true, 'MCP tool should be enabled when its toolset is enabled');
|
||||
});
|
||||
|
||||
test('shouldAutoConfirm with workspace-specific tool configuration', async () => {
|
||||
|
||||
+64
-34
@@ -5,26 +5,30 @@
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js';
|
||||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
import { NewPromptsParser } from '../../../../common/promptSyntax/service/newPromptsParser.js';
|
||||
import { PromptValidator } from '../../../../common/promptSyntax/service/promptValidator.js';
|
||||
import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js';
|
||||
import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js';
|
||||
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
|
||||
import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../../../common/languageModelToolsService.js';
|
||||
import { ObservableSet } from '../../../../../../../base/common/observable.js';
|
||||
import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../../common/languageModels.js';
|
||||
import { ExtensionIdentifier } from '../../../../../../../platform/extensions/common/extensions.js';
|
||||
import { ChatMode, CustomChatMode, IChatModeService } from '../../../../common/chatModes.js';
|
||||
import { MockChatModeService } from '../../mockChatModeService.js';
|
||||
import { PromptsType } from '../../../../common/promptSyntax/promptTypes.js';
|
||||
import { IMarkerData, MarkerSeverity } from '../../../../../../../platform/markers/common/markers.js';
|
||||
import { getPromptFileExtension } from '../../../../common/promptSyntax/config/promptFileLocations.js';
|
||||
import { IFileService } from '../../../../../../../platform/files/common/files.js';
|
||||
import { ResourceSet } from '../../../../../../../base/common/map.js';
|
||||
import { ILabelService } from '../../../../../../../platform/label/common/label.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
|
||||
import { URI } from '../../../../../../base/common/uri.js';
|
||||
import { NewPromptsParser } from '../../../common/promptSyntax/service/newPromptsParser.js';
|
||||
import { PromptValidator } from '../../../common/promptSyntax/service/promptValidator.js';
|
||||
import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js';
|
||||
import { PromptsConfig } from '../../../common/promptSyntax/config/config.js';
|
||||
import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/languageModelToolsService.js';
|
||||
import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../common/languageModels.js';
|
||||
import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js';
|
||||
import { ChatMode, CustomChatMode, IChatModeService } from '../../../common/chatModes.js';
|
||||
import { MockChatModeService } from '../../common/mockChatModeService.js';
|
||||
import { PromptsType } from '../../../common/promptSyntax/promptTypes.js';
|
||||
import { IMarkerData, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js';
|
||||
import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js';
|
||||
import { IFileService } from '../../../../../../platform/files/common/files.js';
|
||||
import { ResourceSet } from '../../../../../../base/common/map.js';
|
||||
import { ChatConfiguration } from '../../../common/constants.js';
|
||||
import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js';
|
||||
import { IChatService } from '../../../common/chatService.js';
|
||||
import { MockChatService } from '../../common/mockChatService.js';
|
||||
import { LanguageModelToolsService } from '../../../browser/languageModelToolsService.js';
|
||||
import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js';
|
||||
import { ILabelService } from '../../../../../../platform/label/common/label.js';
|
||||
|
||||
suite('PromptValidator', () => {
|
||||
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
@@ -32,22 +36,29 @@ suite('PromptValidator', () => {
|
||||
let instaService: TestInstantiationService;
|
||||
|
||||
setup(async () => {
|
||||
instaService = disposables.add(new TestInstantiationService());
|
||||
|
||||
const testConfigService = new TestConfigurationService();
|
||||
testConfigService.setUserConfiguration(PromptsConfig.KEY, true);
|
||||
|
||||
instaService.stub(IConfigurationService, testConfigService);
|
||||
|
||||
testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true);
|
||||
instaService = workbenchInstantiationService({
|
||||
contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)),
|
||||
configurationService: () => testConfigService
|
||||
}, disposables);
|
||||
const chatService = new MockChatService();
|
||||
instaService.stub(IChatService, chatService);
|
||||
instaService.stub(ILabelService, { getUriLabel: (resource) => resource.path });
|
||||
|
||||
const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService));
|
||||
|
||||
const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData;
|
||||
const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData;
|
||||
const testTool3 = { id: 'testTool3', displayName: 'tool3', canBeReferencedInPrompt: true, toolReferenceName: 'tool3', modelDescription: 'Test Tool 3', source: { type: 'extension', label: "My Extension", extensionId: new ExtensionIdentifier('My.extension') }, inputSchema: {} } satisfies IToolData;
|
||||
|
||||
instaService.stub(ILanguageModelToolsService, {
|
||||
getTools() { return [testTool1, testTool2]; },
|
||||
toolSets: new ObservableSet<ToolSet>().observable
|
||||
});
|
||||
disposables.add(toolService.registerToolData(testTool1));
|
||||
disposables.add(toolService.registerToolData(testTool2));
|
||||
disposables.add(toolService.registerToolData(testTool3));
|
||||
|
||||
instaService.set(ILanguageModelToolsService, toolService);
|
||||
|
||||
const testModels: ILanguageModelChatMetadata[] = [
|
||||
{ id: 'mae-4', name: 'MAE 4', vendor: 'olama', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true } } satisfies ILanguageModelChatMetadata,
|
||||
@@ -107,17 +118,16 @@ suite('PromptValidator', () => {
|
||||
/* 01 */"---",
|
||||
/* 02 */`description: ""`, // empty description -> error
|
||||
/* 03 */"model: MAE 4.2", // unknown model -> warning
|
||||
/* 04 */"tools: ['tool1', 'tool2', 'tool3']", // tool3 unknown -> warning
|
||||
/* 04 */"tools: ['tool1', 'tool2', 'tool4', 'my.extension/tool3']", // tool4 unknown -> error
|
||||
/* 05 */"---",
|
||||
/* 06 */"Body",
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.mode);
|
||||
assert.strictEqual(markers.length, 3, 'Expected 3 validation issues');
|
||||
assert.deepStrictEqual(
|
||||
markers.map(m => ({ severity: m.severity, message: m.message })),
|
||||
[
|
||||
{ severity: MarkerSeverity.Error, message: "The 'description' attribute should not be empty." },
|
||||
{ severity: MarkerSeverity.Warning, message: "Unknown tool 'tool3'." },
|
||||
{ severity: MarkerSeverity.Error, message: "Unknown tool 'tool4'." },
|
||||
{ severity: MarkerSeverity.Warning, message: "Unknown model 'MAE 4.2'." },
|
||||
]
|
||||
);
|
||||
@@ -143,8 +153,28 @@ suite('PromptValidator', () => {
|
||||
"---",
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.mode);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.strictEqual(markers[0].message, "Each tool name in the 'tools' attribute must be a string.");
|
||||
assert.deepStrictEqual(
|
||||
markers.map(m => ({ severity: m.severity, message: m.message })),
|
||||
[
|
||||
{ severity: MarkerSeverity.Error, message: "Each tool name in the 'tools' attribute must be a string." },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('old tool reference', async () => {
|
||||
const content = [
|
||||
"---",
|
||||
"description: \"Test\"",
|
||||
"tools: ['tool1', 'tool3']",
|
||||
"---",
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.mode);
|
||||
assert.deepStrictEqual(
|
||||
markers.map(m => ({ severity: m.severity, message: m.message })),
|
||||
[
|
||||
{ severity: MarkerSeverity.Warning, message: "Tool or toolset 'tool3' is deprecated, use 'my.extension/tool3' instead." },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('unknown attribute in mode file', async () => {
|
||||
@@ -327,7 +357,7 @@ suite('PromptValidator', () => {
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.prompt);
|
||||
assert.strictEqual(markers.length, 1, 'Expected one warning for unknown tool variable');
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
|
||||
assert.strictEqual(markers[0].message, "Unknown tool or toolset 'toolX'.");
|
||||
});
|
||||
|
||||
@@ -82,10 +82,6 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
toToolEnablementMap(toolOrToolSetNames: Set<string>): Record<string, boolean> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@@ -93,4 +89,24 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService
|
||||
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getQualifiedToolNames(): Iterable<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getToolByQualifiedName(qualifiedName: string): IToolData | ToolSet | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getQualifiedToolName(tool: IToolData, set?: ToolSet): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getDeprecatedQualifiedToolNames(): Map<string, string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user