From 4c6902146ecc28d638d4d5d7a649179e4928bd32 Mon Sep 17 00:00:00 2001 From: digitarald Date: Sun, 15 Feb 2026 14:41:57 -0800 Subject: [PATCH 1/2] Add General Purpose agent support --- .../computeAutomaticInstructions.ts | 40 +++-- .../tools/builtinTools/runSubagentTool.ts | 54 +++--- .../computeAutomaticInstructions.test.ts | 60 ++++++- .../builtinTools/runSubagentTool.test.ts | 169 +++++++++++------- 4 files changed, 218 insertions(+), 105 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index 50c007fc1168d..b8035f8c42585 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -26,6 +26,7 @@ import { AgentFileType, ICustomAgent, IPromptPath, IPromptsService } from './ser import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; import { ChatConfiguration, ChatModeKind } from '../constants.js'; import { UserSelectedTools } from '../participants/chatAgents.js'; +import { GeneralPurposeAgentName } from '../tools/builtinTools/runSubagentTool.js'; export type InstructionsCollectionEvent = { applyingInstructionsCount: number; @@ -384,21 +385,28 @@ export class ComputeAutomaticInstructions { entries.push('', '', ''); // add trailing newline } } - if (runSubagentTool && this._configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) { - const canUseAgent = (() => { - if (!this._enabledSubagents || this._enabledSubagents.includes('*')) { - return (agent: ICustomAgent) => agent.visibility.agentInvocable; - } else { - const subagents = this._enabledSubagents; - return (agent: ICustomAgent) => subagents.includes(agent.name); - } - })(); - const agents = await this._promptsService.getCustomAgents(token); - if (agents.length > 0) { - entries.push(''); - entries.push('Here is a list of agents that can be used when running a subagent.'); - entries.push('Each agent has optionally a description with the agent\'s purpose and expertise. When asked to run a subagent, choose the most appropriate agent from this list.'); - entries.push(`Use the ${runSubagentTool.variable} tool with the agent name to run the subagent.`); + if (runSubagentTool) { + entries.push(''); + entries.push('Here is a list of agents that can be used when running a subagent.'); + entries.push('Each agent has optionally a description with the agent\'s purpose and expertise. When asked to run a subagent, choose the most appropriate agent from this list.'); + entries.push(`Use the ${runSubagentTool.variable} tool with the agent name to run the subagent.`); + + // Built-in General Purpose agent, always available + entries.push(''); + entries.push(`${GeneralPurposeAgentName}`); + entries.push(`Full-capability agent for complex multi-step tasks requiring the complete toolset and high-quality reasoning. Has access to all tools. Inherits the parent agent's model and system prompt. Use for tasks that don't fit a more specialized agent.`); + entries.push(''); + + if (this._configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) { + const canUseAgent = (() => { + if (!this._enabledSubagents || this._enabledSubagents.includes('*')) { + return (agent: ICustomAgent) => agent.visibility.agentInvocable; + } else { + const subagents = this._enabledSubagents; + return (agent: ICustomAgent) => subagents.includes(agent.name); + } + })(); + const agents = await this._promptsService.getCustomAgents(token); for (const agent of agents) { if (canUseAgent(agent)) { entries.push(''); @@ -415,8 +423,8 @@ export class ComputeAutomaticInstructions { } } } - entries.push('', '', ''); // add trailing newline } + entries.push('', '', ''); // add trailing newline } if (entries.length === 0) { return undefined; diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts index 4e9eacf9ae365..151f437d35491 100644 --- a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts @@ -12,12 +12,12 @@ import { Disposable, DisposableStore } from '../../../../../../base/common/lifec import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { localize } from '../../../../../../nls.js'; -import { IConfigurationChangeEvent, IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; import { ChatRequestVariableSet } from '../../attachments/chatVariableEntries.js'; import { IChatProgress, IChatService } from '../../chatService/chatService.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../constants.js'; +import { ChatAgentLocation, ChatModeKind } from '../../constants.js'; import { ILanguageModelsService } from '../../languageModels.js'; import { ChatModel, IChatRequestModeInstructions } from '../../model/chatModel.js'; import { IChatAgentRequest, IChatAgentService } from '../../participants/chatAgents.js'; @@ -41,25 +41,32 @@ import { import { ManageTodoListToolToolId } from './manageTodoListTool.js'; import { createToolSimpleTextResult } from './toolHelpers.js'; +/** + * The built-in general-purpose agent name. When the model uses this name, + * the subagent inherits the parent's system prompt, model, and tools. + */ +export const GeneralPurposeAgentName = 'General Purpose'; + const BaseModelDescription = `Launch a new agent to handle complex, multi-step tasks autonomously. This tool is good at researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries, use this agent to perform the search for you. - Agents do not run async or in the background, you will wait for the agent\'s result. - When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. - Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you. - The agent's outputs should generally be trusted -- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user\'s intent`; +- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user\'s intent +- If the user asks for a certain agent, you MUST provide that EXACT agent name (case-sensitive) to invoke that specific agent.`; export interface IRunSubagentToolInputParams { prompt: string; description: string; - agentName?: string; + agentName: string; } export class RunSubagentTool extends Disposable implements IToolImpl { static readonly Id = 'runSubagent'; - readonly onDidUpdateToolData: Event; + readonly onDidUpdateToolData: Event; /** Hack to port data between prepare/invoke */ private readonly _resolvedModels = new Map(); @@ -71,16 +78,16 @@ export class RunSubagentTool extends Disposable implements IToolImpl { @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @ILogService private readonly logService: ILogService, @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService _configurationService: IConfigurationService, @IPromptsService private readonly promptsService: IPromptsService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); - this.onDidUpdateToolData = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ChatConfiguration.SubagentToolCustomAgents)); + this.onDidUpdateToolData = Event.None; } getToolData(): IToolData { - let modelDescription = BaseModelDescription; + const modelDescription = BaseModelDescription; const inputSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: { @@ -91,18 +98,14 @@ export class RunSubagentTool extends Disposable implements IToolImpl { description: { type: 'string', description: 'A short (3-5 word) description of the task' + }, + agentName: { + type: 'string', + description: 'Name of the agent to invoke.' } }, - required: ['prompt', 'description'] + required: ['prompt', 'description', 'agentName'] }; - - if (this.configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) { - inputSchema.properties.agentName = { - type: 'string', - description: 'Optional name of a specific agent to invoke. If not provided, uses the current agent.' - }; - modelDescription += `\n- If the user asks for a certain agent, you MUST provide that EXACT agent name (case-sensitive) to invoke that specific agent.`; - } const runSubagentToolData: IToolData = { id: RunSubagentTool.Id, toolReferenceName: VSCodeToolReference.runSubagent, @@ -150,7 +153,11 @@ export class RunSubagentTool extends Disposable implements IToolImpl { let resolvedModelName: string | undefined; const subAgentName = args.agentName; - if (subAgentName) { + // Defensive: model may omit agentName despite schema requiring it + const isGeneralPurpose = !subAgentName || subAgentName === GeneralPurposeAgentName; + + if (!isGeneralPurpose) { + // Custom agent; look up by name and apply its model/tools/instructions subagent = await this.getSubAgentByName(subAgentName); if (subagent) { // Check the pre-resolved model cache from prepareToolInvocation @@ -188,10 +195,11 @@ export class RunSubagentTool extends Disposable implements IToolImpl { metadata: instructions.metadata, }; } else { - throw new Error(`Requested agent '${subAgentName}' not found. Try again with the correct agent name, or omit the agentName to use the current agent.`); + this._resolvedModels.delete(invocation.callId); + throw new Error(`Requested agent '${subAgentName}' not found. Use '${GeneralPurposeAgentName}' for a full-capability agent.`); } } else { - // No subagent name - clean up any cached entry and resolve model name from main model + // General Purpose agent; inherit parent's model and tools const cached = this._resolvedModels.get(invocation.callId); if (cached) { this._resolvedModels.delete(invocation.callId); @@ -374,7 +382,9 @@ export class RunSubagentTool extends Disposable implements IToolImpl { async prepareToolInvocation(context: IToolInvocationPreparationContext, _token: CancellationToken): Promise { const args = context.parameters as IRunSubagentToolInputParams; - const subagent = args.agentName ? await this.getSubAgentByName(args.agentName) : undefined; + // Defensive: model may omit agentName despite schema requiring it + const isGeneralPurpose = !args.agentName || args.agentName === GeneralPurposeAgentName; + const subagent = isGeneralPurpose ? undefined : await this.getSubAgentByName(args.agentName); // Resolve the model early and cache it for invoke() const resolved = this.resolveSubagentModel(subagent, context.modelId); @@ -385,7 +395,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl { toolSpecificData: { kind: 'subagent', description: args.description, - agentName: subagent?.name, + agentName: isGeneralPurpose ? GeneralPurposeAgentName : (subagent?.name ?? args.agentName), prompt: args.prompt, modelName: resolved.resolvedModelName, }, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts index 3cb6fe2ad58fe..58eee7c1c93a7 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts @@ -29,6 +29,7 @@ import { TestContextService, TestUserDataProfileService } from '../../../../../t import { ChatRequestVariableSet, isPromptFileVariableEntry, isPromptTextVariableEntry, toFileVariableEntry } from '../../../common/attachments/chatVariableEntries.js'; import { ComputeAutomaticInstructions, InstructionsCollectionEvent } from '../../../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../../../common/promptSyntax/config/config.js'; +import { GeneralPurposeAgentName } from '../../../common/tools/builtinTools/runSubagentTool.js'; import { AGENTS_SOURCE_FOLDER, CLAUDE_RULES_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../common/promptSyntax/config/promptFileLocations.js'; import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../../../common/promptSyntax/service/promptsService.js'; @@ -1217,16 +1218,61 @@ suite('ComputeAutomaticInstructions', () => { assert.equal(agentsList.length, 1, 'There should be one agents list'); const agents = xmlContents(agentsList[0], 'agent'); - assert.equal(agents.length, 3, 'There should be three agents'); + assert.equal(agents.length, 4, 'There should be four agents (General Purpose + 3 custom)'); - assert.equal(xmlContents(agents[0], 'description')[0], 'Test agent 1'); - assert.equal(xmlContents(agents[0], 'name')[0], `test-agent-1`); + // First agent should always be the built-in General Purpose agent + assert.equal(xmlContents(agents[0], 'name')[0], GeneralPurposeAgentName); - assert.equal(xmlContents(agents[1], 'description')[0], 'Test agent 3'); - assert.equal(xmlContents(agents[1], 'name')[0], `test-agent-3`); + assert.equal(xmlContents(agents[1], 'description')[0], 'Test agent 1'); + assert.equal(xmlContents(agents[1], 'name')[0], `test-agent-1`); - assert.equal(xmlContents(agents[2], 'description')[0], 'Test agent 5'); - assert.equal(xmlContents(agents[2], 'name')[0], `test-agent-5`); + assert.equal(xmlContents(agents[2], 'description')[0], 'Test agent 3'); + assert.equal(xmlContents(agents[2], 'name')[0], `test-agent-3`); + + assert.equal(xmlContents(agents[3], 'description')[0], 'Test agent 5'); + assert.equal(xmlContents(agents[3], 'name')[0], `test-agent-5`); + }); + + test('should include only General Purpose agent when custom agents config is disabled', async () => { + const rootFolderName = 'agents-list-disabled-test'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); + + // Config for custom agents is NOT enabled (default is false) + + await mockFiles(fileService, [ + { + path: `${rootFolder}/.github/agents/test-agent-1.agent.md`, + contents: [ + '---', + 'description: \'Test agent 1\'', + '---', + 'Test agent content', + ] + }, + ]); + + const contextComputer = instaService.createInstance( + ComputeAutomaticInstructions, + ChatModeKind.Agent, + { 'vscode_runSubagent': true }, + ['*'] + ); + const variables = new ChatRequestVariableSet(); + + await contextComputer.collect(variables, CancellationToken.None); + + const textVariables = variables.asArray().filter(v => isPromptTextVariableEntry(v)); + assert.equal(textVariables.length, 1, 'There should be one text variable for agents list'); + + const agentsList = xmlContents(textVariables[0].value, 'agents'); + assert.equal(agentsList.length, 1, 'There should be one agents list'); + + const agents = xmlContents(agentsList[0], 'agent'); + assert.equal(agents.length, 1, 'There should be only the General Purpose agent'); + assert.equal(xmlContents(agents[0], 'name')[0], GeneralPurposeAgentName); }); test('should include skills list when readFile tool available', async () => { diff --git a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts index df306a3ef3ad0..5f94a776ac9a0 100644 --- a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts @@ -9,7 +9,7 @@ import { URI } from '../../../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { NullLogService } from '../../../../../../../platform/log/common/log.js'; import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { RunSubagentTool } from '../../../../common/tools/builtinTools/runSubagentTool.js'; +import { RunSubagentTool, GeneralPurposeAgentName } from '../../../../common/tools/builtinTools/runSubagentTool.js'; import { MockLanguageModelToolsService } from '../mockLanguageModelToolsService.js'; import { IChatAgentService } from '../../../../common/participants/chatAgents.js'; import { IChatService } from '../../../../common/chatService/chatService.js'; @@ -22,6 +22,29 @@ import { ExtensionIdentifier } from '../../../../../../../platform/extensions/co suite('RunSubagentTool', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + function createTool(opts?: { + customAgents?: ICustomAgent[]; + }) { + const mockToolsService = testDisposables.add(new MockLanguageModelToolsService()); + const configService = new TestConfigurationService(); + const promptsService = new MockPromptsService(); + if (opts?.customAgents) { + promptsService.setCustomModes(opts.customAgents); + } + + return testDisposables.add(new RunSubagentTool( + {} as IChatAgentService, + {} as IChatService, + mockToolsService, + {} as ILanguageModelsService, + new NullLogService(), + mockToolsService, + configService, + promptsService, + {} as IInstantiationService, + )); + } + suite('resultText trimming', () => { test('trims leading empty codeblocks (```\\n```) from result', () => { // This tests the regex: /^\n*```\n+```\n*/g @@ -44,10 +67,6 @@ suite('RunSubagentTool', () => { suite('prepareToolInvocation', () => { test('returns correct toolSpecificData', async () => { - const mockToolsService = testDisposables.add(new MockLanguageModelToolsService()); - const configService = new TestConfigurationService(); - - const promptsService = new MockPromptsService(); const customMode: ICustomAgent = { uri: URI.parse('file:///test/custom-agent.md'), name: 'CustomAgent', @@ -58,19 +77,8 @@ suite('RunSubagentTool', () => { target: Target.Undefined, visibility: { userInvocable: true, agentInvocable: true } }; - promptsService.setCustomModes([customMode]); - const tool = testDisposables.add(new RunSubagentTool( - {} as IChatAgentService, - {} as IChatService, - mockToolsService, - {} as ILanguageModelsService, - new NullLogService(), - mockToolsService, - configService, - promptsService, - {} as IInstantiationService, - )); + const tool = createTool({ customAgents: [customMode] }); const result = await tool.prepareToolInvocation( { @@ -95,57 +103,98 @@ suite('RunSubagentTool', () => { modelName: undefined, }); }); - }); - suite('getToolData', () => { - test('returns basic tool data', () => { - const mockToolsService = testDisposables.add(new MockLanguageModelToolsService()); - const configService = new TestConfigurationService(); - const promptsService = new MockPromptsService(); + test('treats undefined agentName as General Purpose', async () => { + const tool = createTool(); - const tool = testDisposables.add(new RunSubagentTool( - {} as IChatAgentService, - {} as IChatService, - mockToolsService, - {} as ILanguageModelsService, - new NullLogService(), - mockToolsService, - configService, - promptsService, - {} as IInstantiationService, - )); + const result = await tool.prepareToolInvocation( + { + parameters: { + prompt: 'Test prompt', + description: 'Test task', + agentName: undefined, + }, + toolCallId: 'test-call-undef', + chatSessionResource: URI.parse('test://session'), + }, + CancellationToken.None + ); - const toolData = tool.getToolData(); + assert.ok(result); + assert.deepStrictEqual(result.toolSpecificData, { + kind: 'subagent', + description: 'Test task', + agentName: GeneralPurposeAgentName, + prompt: 'Test prompt', + modelName: undefined, + }); + }); - assert.strictEqual(toolData.id, 'runSubagent'); - assert.ok(toolData.inputSchema); - assert.ok(toolData.inputSchema.properties?.prompt); - assert.ok(toolData.inputSchema.properties?.description); - assert.deepStrictEqual(toolData.inputSchema.required, ['prompt', 'description']); + test('treats empty string agentName as General Purpose', async () => { + const tool = createTool(); + + const result = await tool.prepareToolInvocation( + { + parameters: { + prompt: 'Test prompt', + description: 'Test task', + agentName: '', + }, + toolCallId: 'test-call-empty', + chatSessionResource: URI.parse('test://session'), + }, + CancellationToken.None + ); + + assert.ok(result); + assert.deepStrictEqual(result.toolSpecificData, { + kind: 'subagent', + description: 'Test task', + agentName: GeneralPurposeAgentName, + prompt: 'Test prompt', + modelName: undefined, + }); }); - test('includes agentName property when SubagentToolCustomAgents is enabled', () => { - const mockToolsService = testDisposables.add(new MockLanguageModelToolsService()); - const configService = new TestConfigurationService({ - 'chat.customAgentInSubagent.enabled': true, + test('falls through to raw agentName when agent not found', async () => { + const tool = createTool(); + + const result = await tool.prepareToolInvocation( + { + parameters: { + prompt: 'Test prompt', + description: 'Test task', + agentName: 'NonExistentAgent', + }, + toolCallId: 'test-call-unknown', + chatSessionResource: URI.parse('test://session'), + }, + CancellationToken.None + ); + + assert.ok(result); + assert.deepStrictEqual(result.toolSpecificData, { + kind: 'subagent', + description: 'Test task', + agentName: 'NonExistentAgent', + prompt: 'Test prompt', + modelName: undefined, }); - const promptsService = new MockPromptsService(); + }); + }); - const tool = testDisposables.add(new RunSubagentTool( - {} as IChatAgentService, - {} as IChatService, - mockToolsService, - {} as ILanguageModelsService, - new NullLogService(), - mockToolsService, - configService, - promptsService, - {} as IInstantiationService, - )); + suite('getToolData', () => { + test('returns basic tool data', () => { + const tool = createTool(); const toolData = tool.getToolData(); - assert.ok(toolData.inputSchema?.properties?.agentName, 'agentName should be in schema when custom agents enabled'); + assert.strictEqual(toolData.id, 'runSubagent'); + assert.ok(toolData.inputSchema); + assert.ok(toolData.inputSchema.properties?.prompt); + assert.ok(toolData.inputSchema.properties?.description); + assert.ok(toolData.inputSchema.properties?.agentName); + assert.deepStrictEqual(toolData.inputSchema.required, ['prompt', 'description', 'agentName']); }); }); @@ -438,14 +487,14 @@ suite('RunSubagentTool', () => { }); }); - test('uses main model when no subagent is specified', async () => { + test('uses main model when General Purpose agent is specified', async () => { const mainMeta = createMetadata('GPT-4o', 1); const models = new Map([['main-model-id', mainMeta]]); const tool = createTool({ models }); const result = await tool.prepareToolInvocation({ - parameters: { prompt: 'test', description: 'test task' }, + parameters: { prompt: 'test', description: 'test task', agentName: GeneralPurposeAgentName }, toolCallId: 'call-6', modelId: 'main-model-id', chatSessionResource: URI.parse('test://session'), @@ -455,7 +504,7 @@ suite('RunSubagentTool', () => { assert.deepStrictEqual(result.toolSpecificData, { kind: 'subagent', description: 'test task', - agentName: undefined, + agentName: GeneralPurposeAgentName, prompt: 'test', modelName: 'GPT-4o', }); From 824cc1adf23c7fe39e603f93e5a89396ac703cea Mon Sep 17 00:00:00 2001 From: digitarald Date: Mon, 23 Feb 2026 16:06:14 -0800 Subject: [PATCH 2/2] Remove SubagentToolCustomAgents setting and consolidate duplicate ILanguageModelToolsService injection --- .../contrib/chat/browser/chat.contribution.ts | 9 ---- .../contrib/chat/common/constants.ts | 1 - .../computeAutomaticInstructions.ts | 46 +++++++++---------- .../tools/builtinTools/runSubagentTool.ts | 5 +- .../computeAutomaticInstructions.test.ts | 46 ------------------- .../builtinTools/runSubagentTool.test.ts | 7 --- .../preferences/browser/settingsLayout.ts | 1 - 7 files changed, 23 insertions(+), 92 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 6a6889667d829..aa8ca7b853f55 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -1217,15 +1217,6 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, - [ChatConfiguration.SubagentToolCustomAgents]: { - type: 'boolean', - description: nls.localize('chat.subagentTool.customAgents', "Whether the runSubagent tool is able to use custom agents. When enabled, the tool can take the name of a custom agent, but it must be given the exact name of the agent."), - default: false, - tags: ['experimental'], - experiment: { - mode: 'auto' - } - }, [ChatConfiguration.AICustomizationMenuEnabled]: { type: 'boolean', description: nls.localize('chat.aiCustomizationMenu.enabled', "Controls whether the AI Customization Menu is shown in the Manage menu and Command Palette. When disabled, the AI Customizations editor and related commands are hidden."), diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index c2da0962c921d..25daa3f18c9f1 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -47,7 +47,6 @@ export enum ChatConfiguration { ChatViewSessionsOrientation = 'chat.viewSessions.orientation', ChatViewProgressBadgeEnabled = 'chat.viewProgressBadge.enabled', ChatContextUsageEnabled = 'chat.contextUsage.enabled', - SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled', ShowCodeBlockProgressAnimation = 'chat.agent.codeBlockProgress', RestoreLastPanelSession = 'chat.restoreLastPanelSession', ExitAfterDelegation = 'chat.exitAfterDelegation', diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index 1979817da096e..c8728c5f2218e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -26,7 +26,7 @@ import { PromptsType } from './promptTypes.js'; import { ParsedPromptFile } from './promptFileParser.js'; import { AgentFileType, ICustomAgent, IPromptPath, IPromptsService } from './service/promptsService.js'; import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; -import { ChatConfiguration, ChatModeKind } from '../constants.js'; +import { ChatModeKind } from '../constants.js'; import { UserSelectedTools } from '../participants/chatAgents.js'; import { GeneralPurposeAgentName } from '../tools/builtinTools/runSubagentTool.js'; @@ -404,30 +404,28 @@ export class ComputeAutomaticInstructions { entries.push(`Full-capability agent for complex multi-step tasks requiring the complete toolset and high-quality reasoning. Has access to all tools. Inherits the parent agent's model and system prompt. Use for tasks that don't fit a more specialized agent.`); entries.push(''); - if (this._configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) { - const canUseAgent = (() => { - if (!this._enabledSubagents || this._enabledSubagents.includes('*')) { - return (agent: ICustomAgent) => agent.visibility.agentInvocable; - } else { - const subagents = this._enabledSubagents; - return (agent: ICustomAgent) => subagents.includes(agent.name); + const canUseAgent = (() => { + if (!this._enabledSubagents || this._enabledSubagents.includes('*')) { + return (agent: ICustomAgent) => agent.visibility.agentInvocable; + } else { + const subagents = this._enabledSubagents; + return (agent: ICustomAgent) => subagents.includes(agent.name); + } + })(); + const agents = await this._promptsService.getCustomAgents(token); + for (const agent of agents) { + if (canUseAgent(agent)) { + entries.push(''); + entries.push(`${agent.name}`); + if (agent.description) { + entries.push(`${agent.description}`); } - })(); - const agents = await this._promptsService.getCustomAgents(token); - for (const agent of agents) { - if (canUseAgent(agent)) { - entries.push(''); - entries.push(`${agent.name}`); - if (agent.description) { - entries.push(`${agent.description}`); - } - if (agent.argumentHint) { - entries.push(`${agent.argumentHint}`); - } - entries.push(''); - if (isInClaudeAgentsFolder(agent.uri)) { - telemetryEvent.claudeAgentsCount++; - } + if (agent.argumentHint) { + entries.push(`${agent.argumentHint}`); + } + entries.push(''); + if (isInClaudeAgentsFolder(agent.uri)) { + telemetryEvent.claudeAgentsCount++; } } } diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts index 151f437d35491..9686d62585be7 100644 --- a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts @@ -12,7 +12,6 @@ import { Disposable, DisposableStore } from '../../../../../../base/common/lifec import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { localize } from '../../../../../../nls.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; import { ChatRequestVariableSet } from '../../attachments/chatVariableEntries.js'; @@ -77,8 +76,6 @@ export class RunSubagentTool extends Disposable implements IToolImpl { @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @ILogService private readonly logService: ILogService, - @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, - @IConfigurationService _configurationService: IConfigurationService, @IPromptsService private readonly promptsService: IPromptsService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { @@ -191,7 +188,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl { modeInstructions = instructions && { name: subAgentName, content: instructions.content, - toolReferences: this.toolsService.toToolReferences(instructions.toolReferences), + toolReferences: this.languageModelToolsService.toToolReferences(instructions.toolReferences), metadata: instructions.metadata, }; } else { diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts index 096e83b3cc7c9..1f2927d9fd4d0 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts @@ -1037,7 +1037,6 @@ suite('ComputeAutomaticInstructions', () => { const rootFolderUri = URI.file(rootFolder); workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - testConfigService.setUserConfiguration('chat.customAgentInSubagent.enabled', true); testConfigService.setUserConfiguration(PromptsConfig.AGENTS_LOCATION_KEY, { [AGENTS_SOURCE_FOLDER]: true, '.claude/agents': true, @@ -1160,9 +1159,6 @@ suite('ComputeAutomaticInstructions', () => { workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - // Enable the config for custom agents - testConfigService.setUserConfiguration('chat.customAgentInSubagent.enabled', true); - await mockFiles(fileService, [ { path: `${rootFolder}/.github/agents/test-agent-1.agent.md`, @@ -1251,48 +1247,6 @@ suite('ComputeAutomaticInstructions', () => { assert.equal(xmlContents(agents[3], 'name')[0], `test-agent-5`); }); - test('should include only General Purpose agent when custom agents config is disabled', async () => { - const rootFolderName = 'agents-list-disabled-test'; - const rootFolder = `/${rootFolderName}`; - const rootFolderUri = URI.file(rootFolder); - - workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - - // Config for custom agents is NOT enabled (default is false) - - await mockFiles(fileService, [ - { - path: `${rootFolder}/.github/agents/test-agent-1.agent.md`, - contents: [ - '---', - 'description: \'Test agent 1\'', - '---', - 'Test agent content', - ] - }, - ]); - - const contextComputer = instaService.createInstance( - ComputeAutomaticInstructions, - ChatModeKind.Agent, - { 'vscode_runSubagent': true }, - ['*'] - ); - const variables = new ChatRequestVariableSet(); - - await contextComputer.collect(variables, CancellationToken.None); - - const textVariables = variables.asArray().filter(v => isPromptTextVariableEntry(v)); - assert.equal(textVariables.length, 1, 'There should be one text variable for agents list'); - - const agentsList = xmlContents(textVariables[0].value, 'agents'); - assert.equal(agentsList.length, 1, 'There should be one agents list'); - - const agents = xmlContents(agentsList[0], 'agent'); - assert.equal(agents.length, 1, 'There should be only the General Purpose agent'); - assert.equal(xmlContents(agents[0], 'name')[0], GeneralPurposeAgentName); - }); - test('should include skills list when readFile tool available', async () => { const rootFolderName = 'skills-list-test'; const rootFolder = `/${rootFolderName}`; diff --git a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts index 5f94a776ac9a0..cf0d3255d1a75 100644 --- a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts @@ -8,7 +8,6 @@ import { CancellationToken } from '../../../../../../../base/common/cancellation import { URI } from '../../../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { NullLogService } from '../../../../../../../platform/log/common/log.js'; -import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { RunSubagentTool, GeneralPurposeAgentName } from '../../../../common/tools/builtinTools/runSubagentTool.js'; import { MockLanguageModelToolsService } from '../mockLanguageModelToolsService.js'; import { IChatAgentService } from '../../../../common/participants/chatAgents.js'; @@ -26,7 +25,6 @@ suite('RunSubagentTool', () => { customAgents?: ICustomAgent[]; }) { const mockToolsService = testDisposables.add(new MockLanguageModelToolsService()); - const configService = new TestConfigurationService(); const promptsService = new MockPromptsService(); if (opts?.customAgents) { promptsService.setCustomModes(opts.customAgents); @@ -38,8 +36,6 @@ suite('RunSubagentTool', () => { mockToolsService, {} as ILanguageModelsService, new NullLogService(), - mockToolsService, - configService, promptsService, {} as IInstantiationService, )); @@ -285,7 +281,6 @@ suite('RunSubagentTool', () => { customAgents?: ICustomAgent[]; }) { const mockToolsService = testDisposables.add(new MockLanguageModelToolsService()); - const configService = new TestConfigurationService(); const promptsService = new MockPromptsService(); if (opts.customAgents) { promptsService.setCustomModes(opts.customAgents); @@ -306,8 +301,6 @@ suite('RunSubagentTool', () => { mockToolsService, mockLanguageModelsService as ILanguageModelsService, new NullLogService(), - mockToolsService, - configService, promptsService, {} as IInstantiationService, )); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 63a328d454b56..ea3b7cd1d5452 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -184,7 +184,6 @@ export const tocData: ITOCEntry = { 'chat.editRequests', 'chat.requestQueuing.*', 'chat.undoRequests.*', - 'chat.customAgentInSubagent.*', 'chat.editing.autoAcceptDelay', 'chat.editing.confirmEditRequest*', 'chat.planAgent.defaultModel'