Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1447,8 +1447,8 @@ export interface IChatParticipantDetectionResult {
export interface IToolDataDto {
id: string;
toolReferenceName?: string;
legacyToolReferenceFullNames?: string[];
tags?: string[];
legacyToolReferenceFullNames?: readonly string[];
tags?: readonly string[];
displayName: string;
userDescription?: string;
modelDescription: string;
Expand Down
7 changes: 2 additions & 5 deletions src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { derived, IObservable, observableFromEvent, ObservableMap } from '../../../../base/common/observable.js';
import { derived, IObservable, ObservableMap } from '../../../../base/common/observable.js';
import { isObject } from '../../../../base/common/types.js';
import { URI } from '../../../../base/common/uri.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
Expand Down Expand Up @@ -99,8 +99,6 @@ export class ChatSelectedTools extends Disposable {

private readonly _sessionStates = new ObservableMap<string, ToolEnablementStates | undefined>();

private readonly _allTools: IObservable<Readonly<IToolData>[]>;

constructor(
private readonly _mode: IObservable<IChatMode>,
@ILanguageModelToolsService private readonly _toolsService: ILanguageModelToolsService,
Expand All @@ -117,7 +115,6 @@ export class ChatSelectedTools extends Disposable {
});

this._globalState = this._store.add(globalStateMemento(StorageScope.PROFILE, StorageTarget.MACHINE, _storageService));
this._allTools = observableFromEvent(_toolsService.onDidChangeTools, () => Array.from(_toolsService.getTools()));
}

/**
Expand All @@ -139,7 +136,7 @@ export class ChatSelectedTools extends Disposable {
if (!currentMap) {
currentMap = this._globalState.read(r);
}
for (const tool of this._allTools.read(r)) {
for (const tool of this._toolsService.toolsObservable.read(r)) {
if (tool.canBeReferencedInPrompt) {
map.set(tool, currentMap.tools.get(tool.id) !== false); // if unknown, it's enabled
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements
};
}

manageConfirmationPreferences(tools: Readonly<IToolData>[], options?: { defaultScope?: 'workspace' | 'profile' | 'session' }): void {
manageConfirmationPreferences(tools: readonly IToolData[], options?: { defaultScope?: 'workspace' | 'profile' | 'session' }): void {
interface IToolTreeItem extends IQuickTreeItem {
type: 'tool' | 'server' | 'tool-pre' | 'tool-post' | 'server-pre' | 'server-post' | 'manage';
toolId?: string;
Expand Down
154 changes: 79 additions & 75 deletions src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class PromptFileRewriter {
}

public rewriteTools(model: ITextModel, newTools: IToolAndToolSetEnablementMap, range: Range): void {
const newToolNames = this._languageModelToolsService.toQualifiedToolNames(newTools);
const newToolNames = this._languageModelToolsService.toFullReferenceNames(newTools);
const newValue = `[${newToolNames.map(s => `'${s}'`).join(', ')}]`;
this.rewriteAttribute(model, newValue, range);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export interface ILanguageModelToolsConfirmationService extends ILanguageModelTo
readonly _serviceBrand: undefined;

/** Opens an IQuickTree to let the user manage their preferences. */
manageConfirmationPreferences(tools: Readonly<IToolData>[], options?: { defaultScope?: 'workspace' | 'profile' | 'session' }): void;
manageConfirmationPreferences(tools: readonly IToolData[], options?: { defaultScope?: 'workspace' | 'profile' | 'session' }): void;

/**
* Registers a contribution that provides more specific confirmation logic
Expand Down
50 changes: 25 additions & 25 deletions src/vs/workbench/contrib/chat/common/languageModelToolsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,28 @@ import { LanguageModelPartAudience } from './languageModels.js';
import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js';

export interface IToolData {
id: string;
source: ToolDataSource;
toolReferenceName?: string;
legacyToolReferenceFullNames?: string[];
icon?: { dark: URI; light?: URI } | ThemeIcon;
when?: ContextKeyExpression;
tags?: string[];
displayName: string;
userDescription?: string;
modelDescription: string;
inputSchema?: IJSONSchema;
canBeReferencedInPrompt?: boolean;
readonly id: string;
readonly source: ToolDataSource;
readonly toolReferenceName?: string;
readonly legacyToolReferenceFullNames?: readonly string[];
readonly icon?: { dark: URI; light?: URI } | ThemeIcon;
readonly when?: ContextKeyExpression;
readonly tags?: readonly string[];
readonly displayName: string;
readonly userDescription?: string;
readonly modelDescription: string;
readonly inputSchema?: IJSONSchema;
readonly canBeReferencedInPrompt?: boolean;
/**
* True if the tool runs in the (possibly remote) workspace, false if it runs
* on the host, undefined if known.
*/
runsInWorkspace?: boolean;
alwaysDisplayInputOutput?: boolean;
readonly runsInWorkspace?: boolean;
readonly alwaysDisplayInputOutput?: boolean;
/** True if this tool might ask for pre-approval */
canRequestPreApproval?: boolean;
readonly canRequestPreApproval?: boolean;
/** True if this tool might ask for post-approval */
canRequestPostApproval?: boolean;
readonly canRequestPostApproval?: boolean;
}

export interface IToolProgressStep {
Expand Down Expand Up @@ -354,7 +354,8 @@ export interface ILanguageModelToolsService {
registerToolData(toolData: IToolData): IDisposable;
registerToolImplementation(id: string, tool: IToolImpl): IDisposable;
registerTool(toolData: IToolData, tool: IToolImpl): IDisposable;
getTools(): Iterable<Readonly<IToolData>>;
getTools(): Iterable<IToolData>;
readonly toolsObservable: IObservable<readonly IToolData[]>;
getTool(id: string): IToolData | undefined;
getToolByName(name: string, includeDisabled?: boolean): IToolData | undefined;
invokeTool(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult>;
Expand All @@ -367,15 +368,14 @@ export interface ILanguageModelToolsService {
getToolSetByName(name: string): ToolSet | undefined;
createToolSet(source: ToolDataSource, id: string, referenceName: string, options?: { icon?: ThemeIcon; description?: string; legacyFullNames?: string[] }): ToolSet & IDisposable;

// tool names in prompt files handling ('qualified names')
// tool names in prompt and agent files ('full reference names')
getFullReferenceNames(): Iterable<string>;
getFullReferenceName(tool: IToolData, toolSet?: ToolSet): string;
getToolByFullReferenceName(fullReferenceName: string): IToolData | ToolSet | undefined;
getDeprecatedFullReferenceNames(): Map<string, Set<string>>;

getQualifiedToolNames(): Iterable<string>;
getToolByQualifiedName(qualifiedName: string): IToolData | ToolSet | undefined;
getQualifiedToolName(tool: IToolData, toolSet?: ToolSet): string;
getDeprecatedQualifiedToolNames(): Map<string, Set<string>>;

toToolAndToolSetEnablementMap(qualifiedToolOrToolSetNames: readonly string[], target: string | undefined): IToolAndToolSetEnablementMap;
toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[];
toToolAndToolSetEnablementMap(fullReferenceNames: readonly string[], target: string | undefined): IToolAndToolSetEnablementMap;
toFullReferenceNames(map: IToolAndToolSetEnablementMap): string[];
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class PromptBodyAutocompletion implements CompletionItemProvider {
}

private async collectToolCompletions(model: ITextModel, position: Position, toolRange: Range, suggestions: CompletionItem[]): Promise<void> {
for (const toolName of this.languageModelToolsService.getQualifiedToolNames()) {
for (const toolName of this.languageModelToolsService.getFullReferenceNames()) {
suggestions.push({
label: toolName,
kind: CompletionItemKind.Value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class PromptCodeActionProvider implements CodeActionProvider {
}

const values = toolsAttr.value.items;
const deprecatedNames = new Lazy(() => this.languageModelToolsService.getDeprecatedQualifiedToolNames());
const deprecatedNames = new Lazy(() => this.languageModelToolsService.getDeprecatedFullReferenceNames());
const edits: TextEdit[] = [];
for (const item of values) {
if (item.type !== 'string') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider {
}
const getSuggestions = (toolRange: Range) => {
const suggestions: CompletionItem[] = [];
const toolNames = isGitHubTarget ? Object.keys(knownGithubCopilotTools) : this.languageModelToolsService.getQualifiedToolNames();
const toolNames = isGitHubTarget ? Object.keys(knownGithubCopilotTools) : this.languageModelToolsService.getFullReferenceNames();
for (const toolName of toolNames) {
let insertText: string;
if (!toolRange.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class PromptHoverProvider implements HoverProvider {
}

private getToolHoverByName(toolName: string, range: Range): Hover | undefined {
const tool = this.languageModelToolsService.getToolByQualifiedName(toolName);
const tool = this.languageModelToolsService.getToolByFullReferenceName(toolName);
if (tool !== undefined) {
if (tool instanceof ToolSet) {
return this.getToolsetHover(tool, range);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ export class PromptValidator {
const headerTarget = promptAST.header?.target;
const headerToolsMap = headerTools ? this.languageModelToolsService.toToolAndToolSetEnablementMap(headerTools, headerTarget) : undefined;

const available = new Set<string>(this.languageModelToolsService.getQualifiedToolNames());
const deprecatedNames = this.languageModelToolsService.getDeprecatedQualifiedToolNames();
const available = new Set<string>(this.languageModelToolsService.getFullReferenceNames());
const deprecatedNames = this.languageModelToolsService.getDeprecatedFullReferenceNames();
for (const variable of body.variableReferences) {
if (!available.has(variable.name)) {
if (deprecatedNames.has(variable.name)) {
Expand All @@ -113,7 +113,7 @@ export class PromptValidator {
report(toMarker(localize('promptValidator.unknownVariableReference', "Unknown tool or toolset '{0}'.", variable.name), variable.range, MarkerSeverity.Warning));
}
} else if (headerToolsMap) {
const tool = this.languageModelToolsService.getToolByQualifiedName(variable.name);
const tool = this.languageModelToolsService.getToolByFullReferenceName(variable.name);
if (tool && headerToolsMap.get(tool) === false) {
report(toMarker(localize('promptValidator.disabledTool', "Tool or toolset '{0}' also needs to be enabled in the header.", variable.name), variable.range, MarkerSeverity.Warning));
}
Expand Down Expand Up @@ -344,8 +344,8 @@ export class PromptValidator {

private validateVSCodeTools(valueItem: IArrayValue, target: string | undefined, report: (markers: IMarkerData) => void) {
if (valueItem.items.length > 0) {
const available = new Set<string>(this.languageModelToolsService.getQualifiedToolNames());
const deprecatedNames = this.languageModelToolsService.getDeprecatedQualifiedToolNames();
const available = new Set<string>(this.languageModelToolsService.getFullReferenceNames());
const deprecatedNames = this.languageModelToolsService.getDeprecatedFullReferenceNames();
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));
Expand Down
52 changes: 26 additions & 26 deletions src/vs/workbench/contrib/chat/common/tools/runSubagentTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
import { IJSONSchema, IJSONSchemaMap } from '../../../../../base/common/jsonSchema.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { localize } from '../../../../../nls.js';
Expand Down Expand Up @@ -66,41 +67,40 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
}

getToolData(): IToolData {
let modelDescription = BaseModelDescription;
const inputSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
type: 'object',
properties: {
prompt: {
type: 'string',
description: 'A detailed description of the task for the agent to perform'
},
description: {
type: 'string',
description: 'A short (3-5 word) description of the task'
}
},
required: ['prompt', 'description']
};

if (this.configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) {
inputSchema.properties.subagentType = {
type: 'string',
description: 'Optional ID of a specific agent to invoke. If not provided, uses the current agent.'
};
modelDescription += `\n- If the user asks for a certain agent by name, you MUST provide that EXACT subagentType (case-sensitive) to invoke that specific agent.`;
}
const runSubagentToolData: IToolData = {
id: RunSubagentToolId,
toolReferenceName: VSCodeToolReference.runSubagent,
legacyToolReferenceFullNames: ['runSubagent'],
icon: ThemeIcon.fromId(Codicon.organization.id),
displayName: localize('tool.runSubagent.displayName', 'Run Subagent'),
userDescription: localize('tool.runSubagent.userDescription', 'Run a task within an isolated subagent context to enable efficient organization of tasks and context window management.'),
modelDescription: BaseModelDescription,
modelDescription: modelDescription,
source: ToolDataSource.Internal,
inputSchema: {
type: 'object',
properties: {
prompt: {
type: 'string',
description: 'A detailed description of the task for the agent to perform'
},
description: {
type: 'string',
description: 'A short (3-5 word) description of the task'
}
},
required: ['prompt', 'description']
}
inputSchema: inputSchema
};

if (this.configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) {
runSubagentToolData.inputSchema!.properties!['subagentType'] = {
type: 'string',
description: 'Optional ID of a specific agent to invoke. If not provided, uses the current agent.'
};
runSubagentToolData.modelDescription += `\n- If the user asks for a certain agent by name, you MUST provide that EXACT subagentType (case-sensitive) to invoke that specific agent.`;
}



return runSubagentToolData;
}

Expand Down
Loading
Loading