diff --git a/package.json b/package.json index 032f37c765..750a0e7c79 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,40 @@ ] } }, + { + "name": "search_subagent", + "toolReferenceName": "searchSubagent", + "displayName": "%copilot.tools.searchSubagent.name%", + "icon": "$(search)", + "canBeReferencedInPrompt": true, + "userDescription": "%copilot.tools.searchSubagent.description%", + "modelDescription": "Launch an iterative search-focused subagent that orchestrates semantic_search, grep_search, file_search and read_file tool calls to explore and gather relevant workspace code for a natural language query.\n CALL THIS FOR ANY TASK THAT REQUIRES CODEBASE EXPLORATION!\n\nRemember:\n1. Perform tool calls in parallel whenever possible\n2. Avoid redundant calls -- don't repeat identical searches.\n3. Stop once you have high-confidence, non-duplicative snippets covering the query scope.\n\nReturns: A list of relevant files/snippet locations in the workspace.\n\nInput fields:\n- query: Natural language description of what to search for.\n- description: Short user-visible invocation message. \n- details: 2-3 sentences detailing the objective of the search agent.", + "tags": [ + "vscode_codesearch" + ], + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Natural language description of what to search for." + }, + "description": { + "type": "string", + "description": "A short (3-5 word) description of the task." + }, + "details": { + "type": "string", + "description": "A more detailed description of the objective for the search subagent. This helps the sub-agent remain on task and understand its purpose." + } + }, + "required": [ + "query", + "description", + "details" + ] + } + }, { "name": "copilot_searchWorkspaceSymbols", "toolReferenceName": "symbols", @@ -1234,7 +1268,8 @@ "listDirectory", "searchResults", "textSearch", - "usages" + "usages", + "searchSubagent" ] }, { @@ -3921,6 +3956,16 @@ "advanced", "experimental" ] + }, + "github.copilot.chat.searchSubagent.enabled": { + "type": "boolean", + "default": false, + "markdownDescription": "%github.copilot.config.searchSubagent.enabled%", + "tags": [ + "advanced", + "experimental", + "onExp" + ] } } } diff --git a/package.nls.json b/package.nls.json index fc9161d376..42ac10d64f 100644 --- a/package.nls.json +++ b/package.nls.json @@ -377,5 +377,10 @@ "github.copilot.config.githubMcpServer.enabled": "Enable built-in support for the GitHub MCP Server.", "github.copilot.config.githubMcpServer.toolsets": "Specify toolsets to use from the GitHub MCP Server. [Learn more](https://aka.ms/vscode-gh-mcp-toolsets).", "github.copilot.config.githubMcpServer.readonly": "Enable read-only mode for the GitHub MCP Server. When enabled, only read tools are available. [Learn more](https://aka.ms/vscode-gh-mcp-readonly).", - "github.copilot.config.githubMcpServer.lockdown": "Enable lockdown mode for the GitHub MCP Server. When enabled, hides public issue details created by users without push access. [Learn more](https://aka.ms/vscode-gh-mcp-lockdown)." + "github.copilot.config.githubMcpServer.lockdown": "Enable lockdown mode for the GitHub MCP Server. When enabled, hides public issue details created by users without push access. [Learn more](https://aka.ms/vscode-gh-mcp-lockdown).", + "copilot.tools.runSubagent.name": "Run Subagent", + "copilot.tools.runSubagent.description": "Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.", + "copilot.tools.searchSubagent.name": "Search Subagent", + "copilot.tools.searchSubagent.description": "Launch an iterative search-focused subagent to find relevant code in your workspace.", + "github.copilot.config.searchSubagent.enabled": "Enable the search subagent tool for iterative code exploration in the workspace." } diff --git a/src/extension/intents/node/agentIntent.ts b/src/extension/intents/node/agentIntent.ts index 921d3c40e8..5f129cd22f 100644 --- a/src/extension/intents/node/agentIntent.ts +++ b/src/extension/intents/node/agentIntent.ts @@ -10,7 +10,7 @@ import { BudgetExceededError } from '@vscode/prompt-tsx/dist/base/materialized'; import type * as vscode from 'vscode'; import { ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes'; import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; -import { isAnthropicFamily, modelCanUseApplyPatchExclusively, modelCanUseReplaceStringExclusively, modelSupportsApplyPatch, modelSupportsMultiReplaceString, modelSupportsReplaceString, modelSupportsSimplifiedApplyPatchInstructions } from '../../../platform/endpoint/common/chatModelCapabilities'; +import { isAnthropicFamily, isGptFamily, modelCanUseApplyPatchExclusively, modelCanUseReplaceStringExclusively, modelSupportsApplyPatch, modelSupportsMultiReplaceString, modelSupportsReplaceString, modelSupportsSimplifiedApplyPatchInstructions } from '../../../platform/endpoint/common/chatModelCapabilities'; import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider'; import { IEnvService } from '../../../platform/env/common/envService'; import { ILogService } from '../../../platform/log/common/logService'; @@ -88,6 +88,7 @@ export const getAgentTools = async (accessor: ServicesAccessor, request: vscode. allowTools[ToolName.CoreRunTest] = await testService.hasAnyTests(); allowTools[ToolName.CoreRunTask] = tasksService.getTasks().length > 0; + allowTools[ToolName.SearchSubagent] = (isGptFamily(model) || isAnthropicFamily(model)) && configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SearchSubagentToolEnabled, experimentationService); if (model.family.includes('grok-code')) { allowTools[ToolName.CoreManageTodoList] = false; diff --git a/src/extension/prompt/node/searchSubagentToolCallingLoop.ts b/src/extension/prompt/node/searchSubagentToolCallingLoop.ts new file mode 100644 index 0000000000..c1778fe22f --- /dev/null +++ b/src/extension/prompt/node/searchSubagentToolCallingLoop.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { randomUUID } from 'crypto'; +import type { CancellationToken, ChatRequest, ChatResponseStream, LanguageModelToolInformation, Progress } from 'vscode'; +import { IAuthenticationChatUpgradeService } from '../../../platform/authentication/common/authenticationUpgrade'; +import { ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes'; +import { IConfigurationService } from '../../../platform/configuration/common/configurationService'; +import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider'; +import { ILogService } from '../../../platform/log/common/logService'; +import { IRequestLogger } from '../../../platform/requestLogger/node/requestLogger'; +import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService'; +import { ITelemetryService } from '../../../platform/telemetry/common/telemetry'; +import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation'; +import { ChatResponseProgressPart, ChatResponseReferencePart } from '../../../vscodeTypes'; +import { IToolCallingLoopOptions, ToolCallingLoop, ToolCallingLoopFetchOptions } from '../../intents/node/toolCallingLoop'; +import { SearchSubagentPrompt } from '../../prompts/node/agent/searchSubagentPrompt'; +import { PromptRenderer } from '../../prompts/node/base/promptRenderer'; +import { ToolName } from '../../tools/common/toolNames'; +import { IToolsService } from '../../tools/common/toolsService'; +import { IBuildPromptContext } from '../common/intents'; +import { IBuildPromptResult } from './intents'; + +export interface ISearchSubagentToolCallingLoopOptions extends IToolCallingLoopOptions { + request: ChatRequest; + location: ChatLocation; + promptText: string; +} + +export class SearchSubagentToolCallingLoop extends ToolCallingLoop { + + public static readonly ID = 'searchSubagentTool'; + + constructor( + options: ISearchSubagentToolCallingLoopOptions, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService logService: ILogService, + @IRequestLogger requestLogger: IRequestLogger, + @IEndpointProvider private readonly endpointProvider: IEndpointProvider, + @IToolsService private readonly toolsService: IToolsService, + @IAuthenticationChatUpgradeService authenticationChatUpgradeService: IAuthenticationChatUpgradeService, + @ITelemetryService telemetryService: ITelemetryService, + @IConfigurationService configurationService: IConfigurationService, + @IExperimentationService experimentationService: IExperimentationService, + ) { + super(options, instantiationService, endpointProvider, logService, requestLogger, authenticationChatUpgradeService, telemetryService, configurationService, experimentationService); + } + + protected override createPromptContext(availableTools: LanguageModelToolInformation[], outputStream: ChatResponseStream | undefined): IBuildPromptContext { + const context = super.createPromptContext(availableTools, outputStream); + if (context.tools) { + context.tools = { + ...context.tools, + toolReferences: [], + subAgentInvocationId: randomUUID() + }; + } + context.query = this.options.promptText; + return context; + } + + private async getEndpoint(request: ChatRequest) { + let endpoint = await this.endpointProvider.getChatEndpoint(this.options.request); + if (!endpoint.supportsToolCalls) { + endpoint = await this.endpointProvider.getChatEndpoint('gpt-4.1'); + } + return endpoint; + } + + protected async buildPrompt(buildPromptContext: IBuildPromptContext, progress: Progress, token: CancellationToken): Promise { + const endpoint = await this.getEndpoint(this.options.request); + const renderer = PromptRenderer.create( + this.instantiationService, + endpoint, + SearchSubagentPrompt, + { + promptContext: buildPromptContext + } + ); + return await renderer.render(progress, token); + } + + protected async getAvailableTools(): Promise { + const endpoint = await this.getEndpoint(this.options.request); + const allTools = this.toolsService.getEnabledTools(this.options.request, endpoint); + + // Only include tools relevant for search operations. + // We include semantic_search (Codebase) and the basic search primitives. + // The Codebase tool checks for inSubAgent context to prevent nested tool calling loops. + const allowedSearchTools = new Set([ + ToolName.Codebase, // Semantic search + ToolName.FindFiles, + ToolName.FindTextInFiles, + ToolName.ReadFile + ]); + + return allTools.filter(tool => allowedSearchTools.has(tool.name as ToolName)); + } + + protected async fetch({ messages, finishedCb, requestOptions }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise { + const endpoint = await this.getEndpoint(this.options.request); + return endpoint.makeChatRequest2({ + debugName: SearchSubagentToolCallingLoop.ID, + messages, + finishedCb, + location: this.options.location, + requestOptions: { + ...requestOptions, + temperature: 0 + }, + // This loop is inside a tool called from another request, so never user initiated + userInitiatedRequest: false, + telemetryProperties: { + messageId: randomUUID(), + messageSource: SearchSubagentToolCallingLoop.ID + }, + }, token); + } +} diff --git a/src/extension/prompts/node/agent/anthropicPrompts.tsx b/src/extension/prompts/node/agent/anthropicPrompts.tsx index b9966d2769..57cfcdae75 100644 --- a/src/extension/prompts/node/agent/anthropicPrompts.tsx +++ b/src/extension/prompts/node/agent/anthropicPrompts.tsx @@ -23,6 +23,7 @@ class DefaultAnthropicAgentPrompt extends PromptElement You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.
The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.
+ {tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the {ToolName.ReadFile} tool to read more context if needed. Never pass this omitted line marker to an edit tool.}
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.
{!this.props.codesearchMode && <>If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept.
} @@ -149,6 +150,7 @@ class Claude45DefaultPrompt extends PromptElement { No need to ask permission before using a tool.
NEVER say the name of a tool to a user. For example, instead of saying that you'll use the {ToolName.CoreRunInTerminal} tool, say "I'll run the command in a terminal".
If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible{tools[ToolName.Codebase] && <>, but do not call {ToolName.Codebase} in parallel.}
+ {tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} {tools[ToolName.ReadFile] && <>When using the {ToolName.ReadFile} tool, prefer reading a large section over calling the {ToolName.ReadFile} tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need.
} {tools[ToolName.Codebase] && <>If {ToolName.Codebase} returns the full contents of the text files in the workspace, you have all the workspace context.
} {tools[ToolName.FindTextInFiles] && <>You can use the {ToolName.FindTextInFiles} to get an overview of a file by searching for a string within that one file, instead of using {ToolName.ReadFile} many times.
} diff --git a/src/extension/prompts/node/agent/defaultAgentInstructions.tsx b/src/extension/prompts/node/agent/defaultAgentInstructions.tsx index 79bc0ac932..a9e4009f3f 100644 --- a/src/extension/prompts/node/agent/defaultAgentInstructions.tsx +++ b/src/extension/prompts/node/agent/defaultAgentInstructions.tsx @@ -115,6 +115,7 @@ export class DefaultAgentPrompt extends PromptElement { You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.
The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.
+ {tools[ToolName.SearchSubagent] && <>For any context searching, use {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the {ToolName.ReadFile} tool to read more context if needed. Never pass this omitted line marker to an edit tool.}
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.
{!this.props.codesearchMode && <>If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept.
} @@ -132,6 +133,7 @@ export class DefaultAgentPrompt extends PromptElement { When using a tool, follow the JSON schema very carefully and make sure to include ALL required properties.
No need to ask permission before using a tool.
NEVER say the name of a tool to a user. For example, instead of saying that you'll use the {ToolName.CoreRunInTerminal} tool, say "I'll run the command in a terminal".
+ {tools[ToolName.SearchSubagent] && <>For any context searching, use {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible{tools[ToolName.Codebase] && <>, but do not call {ToolName.Codebase} in parallel.}
{tools[ToolName.ReadFile] && <>When using the {ToolName.ReadFile} tool, prefer reading a large section over calling the {ToolName.ReadFile} tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need.
} {tools[ToolName.Codebase] && <>If {ToolName.Codebase} returns the full contents of the text files in the workspace, you have all the workspace context.
} diff --git a/src/extension/prompts/node/agent/openai/defaultOpenAIPrompt.tsx b/src/extension/prompts/node/agent/openai/defaultOpenAIPrompt.tsx index 99a9b9251a..10c00732b3 100644 --- a/src/extension/prompts/node/agent/openai/defaultOpenAIPrompt.tsx +++ b/src/extension/prompts/node/agent/openai/defaultOpenAIPrompt.tsx @@ -33,6 +33,7 @@ export class DefaultOpenAIAgentPrompt extends PromptElement The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.
+ {tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the {ToolName.ReadFile} tool to read more context if needed. Never pass this omitted line marker to an edit tool.}
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.
{!this.props.codesearchMode && <>If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept.
} diff --git a/src/extension/prompts/node/agent/openai/gpt51Prompt.tsx b/src/extension/prompts/node/agent/openai/gpt51Prompt.tsx index e1e18c3e87..d5cc9f665a 100644 --- a/src/extension/prompts/node/agent/openai/gpt51Prompt.tsx +++ b/src/extension/prompts/node/agent/openai/gpt51Prompt.tsx @@ -144,6 +144,7 @@ class Gpt51Prompt extends PromptElement { - Working on the repo(s) in the current environment is allowed, even if they are proprietary.
- Analyzing code for vulnerabilities is allowed.
- Showing user code and tool call details is allowed.
+ {tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} - Use the {ToolName.ApplyPatch} tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {`{"input":"*** Begin Patch\\n*** Update File: path/to/file.py\\n@@ def example():\\n- pass\\n+ return 123\\n*** End Patch"}`}.

If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines:
diff --git a/src/extension/prompts/node/agent/openai/gpt5Prompt.tsx b/src/extension/prompts/node/agent/openai/gpt5Prompt.tsx index ddcb8fe031..a33f88ac54 100644 --- a/src/extension/prompts/node/agent/openai/gpt5Prompt.tsx +++ b/src/extension/prompts/node/agent/openai/gpt5Prompt.tsx @@ -130,6 +130,7 @@ class DefaultGpt5AgentPrompt extends PromptElement { - Working on the repo(s) in the current environment is allowed, even if they are proprietary.
- Analyzing code for vulnerabilities is allowed.
- Showing user code and tool call details is allowed.
+ {tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} {tools[ToolName.ApplyPatch] && <>- Use the apply_patch tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {`{"command":["apply_patch","*** Begin Patch\\n*** Update File: path/to/file.py\\n@@ def example():\\n- pass\\n+ return 123\\n*** End Patch"]}`}.
} {!tools[ToolName.ApplyPatch] && tools[ToolName.ReplaceString] && <>- Use the replace_string_in_file tool to edit files precisely.
}
diff --git a/src/extension/prompts/node/agent/searchSubagentPrompt.tsx b/src/extension/prompts/node/agent/searchSubagentPrompt.tsx new file mode 100644 index 0000000000..6360d81847 --- /dev/null +++ b/src/extension/prompts/node/agent/searchSubagentPrompt.tsx @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { AssistantMessage, PromptElement, PromptSizing, SystemMessage, UserMessage } from '@vscode/prompt-tsx'; +import { GenericBasePromptElementProps } from '../../../context/node/resolvers/genericPanelIntentInvocation'; +import { CopilotToolMode } from '../../../tools/common/toolsRegistry'; +import { ChatToolCalls } from '../panel/toolCalling'; + +const MAX_SEARCH_TURNS = 4; + +/** + * Prompt for the search subagent that uses custom search instructions + * instead of the default agent system prompt. + */ +export class SearchSubagentPrompt extends PromptElement { + async render(state: void, sizing: PromptSizing) { + const { conversation, toolCallRounds, toolCallResults } = this.props.promptContext; + + // Render the search instruction from the conversation + const searchInstruction = conversation?.turns[0]?.request.message; + + // Check if we're at the last turn (to align with training where we coax final answer) + const currentTurn = toolCallRounds?.length ?? 0; + const isLastTurn = currentTurn >= MAX_SEARCH_TURNS - 1; + + return ( + <> + + You are an AI coding research assistant that uses search tools to gather information. You can call tools to search for information and read files across a codebase.
+
+ Once you have thoroughly searched the repository, return a message with ONLY: the <final_answer> tag to provide paths and line ranges of relevant code snippets.
+
+ Example:
+
+ <final_answer>
+ /absolute/path/to/file.py:10-20
+ /absolute/path/to/another/file.cc:100-120
+ </final_answer> +
+ {searchInstruction} + + {isLastTurn && ( + + OK, my allotted iterations are finished -- I must produce a list of code references as the final answer. <final_answer> + + )} + + ); + } +} \ No newline at end of file diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_non_edit_tools.spec.snap index 877f3fa34f..28a3f0dc43 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_non_edit_tools.spec.snap @@ -10,6 +10,7 @@ Keep your answers short and impersonal. You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks. The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the read_file tool to read more context if needed. Never pass this omitted line marker to an edit tool. If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes. If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_tools.spec.snap index 32c6fc12d1..e7e71683d0 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-opus-4.5/all_tools.spec.snap @@ -10,6 +10,7 @@ Keep your answers short and impersonal. You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks. The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the read_file tool to read more context if needed. Never pass this omitted line marker to an edit tool. If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes. If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_non_edit_tools.spec.snap index 6ad468d275..ffcfe84821 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_non_edit_tools.spec.snap @@ -29,6 +29,7 @@ When using a tool, follow the JSON schema very carefully and make sure to includ No need to ask permission before using a tool. NEVER say the name of a tool to a user. For example, instead of saying that you'll use the run_in_terminal tool, say "I'll run the command in a terminal". If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible, but do not call semantic_search in parallel. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. When using the read_file tool, prefer reading a large section over calling the read_file tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need. If semantic_search returns the full contents of the text files in the workspace, you have all the workspace context. You can use the grep_search to get an overview of a file by searching for a string within that one file, instead of using read_file many times. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_tools.spec.snap index b327de1049..ef8b5af708 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-claude-sonnet-4.5/all_tools.spec.snap @@ -29,6 +29,7 @@ When using a tool, follow the JSON schema very carefully and make sure to includ No need to ask permission before using a tool. NEVER say the name of a tool to a user. For example, instead of saying that you'll use the run_in_terminal tool, say "I'll run the command in a terminal". If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible, but do not call semantic_search in parallel. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. When using the read_file tool, prefer reading a large section over calling the read_file tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need. If semantic_search returns the full contents of the text files in the workspace, you have all the workspace context. You can use the grep_search to get an overview of a file by searching for a string within that one file, instead of using read_file many times. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_non_edit_tools.spec.snap index 655e5bcdea..9f9292736e 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_non_edit_tools.spec.snap @@ -10,6 +10,7 @@ Keep your answers short and impersonal. You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks. The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. +For any context searching, use search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the read_file tool to read more context if needed. Never pass this omitted line marker to an edit tool. If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes. If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. @@ -25,6 +26,7 @@ If the user is requesting a code sample, you can answer it directly without usin When using a tool, follow the JSON schema very carefully and make sure to include ALL required properties. No need to ask permission before using a tool. NEVER say the name of a tool to a user. For example, instead of saying that you'll use the run_in_terminal tool, say "I'll run the command in a terminal". +For any context searching, use search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible, but do not call semantic_search in parallel. When using the read_file tool, prefer reading a large section over calling the read_file tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need. If semantic_search returns the full contents of the text files in the workspace, you have all the workspace context. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_tools.spec.snap index 77c24ce724..00a55f9ac5 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-default/all_tools.spec.snap @@ -10,6 +10,7 @@ Keep your answers short and impersonal. You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks. The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. +For any context searching, use search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the read_file tool to read more context if needed. Never pass this omitted line marker to an edit tool. If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes. If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. @@ -26,6 +27,7 @@ If the user is requesting a code sample, you can answer it directly without usin When using a tool, follow the JSON schema very carefully and make sure to include ALL required properties. No need to ask permission before using a tool. NEVER say the name of a tool to a user. For example, instead of saying that you'll use the run_in_terminal tool, say "I'll run the command in a terminal". +For any context searching, use search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible, but do not call semantic_search in parallel. When using the read_file tool, prefer reading a large section over calling the read_file tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need. If semantic_search returns the full contents of the text files in the workspace, you have all the workspace context. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_non_edit_tools.spec.snap index cfe86f2d4e..94bef18296 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_non_edit_tools.spec.snap @@ -12,6 +12,7 @@ You are a highly sophisticated automated coding agent with expert-level knowledg The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. You are an agent - you must keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. ONLY terminate your turn when you are sure that the problem is solved, or you absolutely cannot continue. You take action when possible- the user is expecting YOU to take action and go to work for them. Don't ask unnecessary questions about the details if you can simply DO something useful instead. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the read_file tool to read more context if needed. Never pass this omitted line marker to an edit tool. If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes. If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_tools.spec.snap index 5c53dee9ab..a49d5bea49 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-4.1/all_tools.spec.snap @@ -12,6 +12,7 @@ You are a highly sophisticated automated coding agent with expert-level knowledg The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. You are an agent - you must keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. ONLY terminate your turn when you are sure that the problem is solved, or you absolutely cannot continue. You take action when possible- the user is expecting YOU to take action and go to work for them. Don't ask unnecessary questions about the details if you can simply DO something useful instead. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the read_file tool to read more context if needed. Never pass this omitted line marker to an edit tool. If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes. If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_non_edit_tools.spec.snap index cce6ffdb0b..dfd5a3f7dd 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_non_edit_tools.spec.snap @@ -112,6 +112,7 @@ You MUST adhere to the following criteria when solving queries: - Working on the repo(s) in the current environment is allowed, even if they are proprietary. - Analyzing code for vulnerabilities is allowed. - Showing user code and tool call details is allowed. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines - Fix the problem at the root cause rather than applying surface-level patches, when possible. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_tools.spec.snap index 6ed44a842a..d59524982a 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5-mini/all_tools.spec.snap @@ -112,6 +112,7 @@ You MUST adhere to the following criteria when solving queries: - Working on the repo(s) in the current environment is allowed, even if they are proprietary. - Analyzing code for vulnerabilities is allowed. - Showing user code and tool call details is allowed. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. - Use the apply_patch tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"command":["apply_patch","*** Begin Patch/n*** Update File: path/to/file.py/n@@ def example():/n- pass/n+ return 123/n*** End Patch"]}. If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_non_edit_tools.spec.snap index 57bf017499..dd149dbd62 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_non_edit_tools.spec.snap @@ -119,6 +119,7 @@ You MUST adhere to the following criteria when solving queries: - Working on the repo(s) in the current environment is allowed, even if they are proprietary. - Analyzing code for vulnerabilities is allowed. - Showing user code and tool call details is allowed. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. - Use the apply_patch tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"input":"*** Begin Patch/n*** Update File: path/to/file.py/n@@ def example():/n- pass/n+ return 123/n*** End Patch"}. If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines: diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_tools.spec.snap index 1c422178a6..81d50dcf97 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5.1/all_tools.spec.snap @@ -119,6 +119,7 @@ You MUST adhere to the following criteria when solving queries: - Working on the repo(s) in the current environment is allowed, even if they are proprietary. - Analyzing code for vulnerabilities is allowed. - Showing user code and tool call details is allowed. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. - Use the apply_patch tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"input":"*** Begin Patch/n*** Update File: path/to/file.py/n@@ def example():/n- pass/n+ return 123/n*** End Patch"}. If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines: diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_non_edit_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_non_edit_tools.spec.snap index 3e9af03c15..16b30c404d 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_non_edit_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_non_edit_tools.spec.snap @@ -112,6 +112,7 @@ You MUST adhere to the following criteria when solving queries: - Working on the repo(s) in the current environment is allowed, even if they are proprietary. - Analyzing code for vulnerabilities is allowed. - Showing user code and tool call details is allowed. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines - Fix the problem at the root cause rather than applying surface-level patches, when possible. diff --git a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_tools.spec.snap b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_tools.spec.snap index 1c5389887c..536b6cc407 100644 --- a/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_tools.spec.snap +++ b/src/extension/prompts/node/agent/test/__snapshots__/agentPrompts-gpt-5/all_tools.spec.snap @@ -112,6 +112,7 @@ You MUST adhere to the following criteria when solving queries: - Working on the repo(s) in the current environment is allowed, even if they are proprietary. - Analyzing code for vulnerabilities is allowed. - Showing user code and tool call details is allowed. +For codebase exploration, prefer search_subagent to search and gather data instead of directly calling grep_search, semantic_search or file_search. - Use the apply_patch tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {"command":["apply_patch","*** Begin Patch/n*** Update File: path/to/file.py/n@@ def example():/n- pass/n+ return 123/n*** End Patch"]}. If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines diff --git a/src/extension/tools/common/toolNames.ts b/src/extension/tools/common/toolNames.ts index a6601c45a6..639ecc0395 100644 --- a/src/extension/tools/common/toolNames.ts +++ b/src/extension/tools/common/toolNames.ts @@ -67,7 +67,8 @@ export enum ToolName { EditFilesPlaceholder = 'edit_files', CoreRunSubagent = 'runSubagent', CoreConfirmationTool = 'vscode_get_confirmation', - CoreTerminalConfirmationTool = 'vscode_get_terminal_confirmation' + CoreTerminalConfirmationTool = 'vscode_get_terminal_confirmation', + SearchSubagent = 'search_subagent' } export enum ContributedToolName { @@ -173,6 +174,7 @@ export const toolCategories: Record = { [ToolName.CreateDirectory]: ToolCategory.Core, [ToolName.ReadProjectStructure]: ToolCategory.Core, [ToolName.CoreRunSubagent]: ToolCategory.Core, + [ToolName.SearchSubagent]: ToolCategory.Core, [ToolName.Memory]: ToolCategory.Core, // already enabled only when tasks are enabled diff --git a/src/extension/tools/node/allTools.ts b/src/extension/tools/node/allTools.ts index 957b4098fc..f765c1c7ee 100644 --- a/src/extension/tools/node/allTools.ts +++ b/src/extension/tools/node/allTools.ts @@ -31,6 +31,7 @@ import './readProjectStructureTool'; import './replaceStringTool'; import './runNotebookCellTool'; import './scmChangesTool'; +import './searchSubagentTool'; import './searchWorkspaceSymbolsTool'; import './simpleBrowserTool'; import './testFailureTool'; diff --git a/src/extension/tools/node/codebaseTool.tsx b/src/extension/tools/node/codebaseTool.tsx index 58efaf4137..347efadbd1 100644 --- a/src/extension/tools/node/codebaseTool.tsx +++ b/src/extension/tools/node/codebaseTool.tsx @@ -6,8 +6,8 @@ import * as l10n from '@vscode/l10n'; import { PromptElement, PromptReference, TokenLimit } from '@vscode/prompt-tsx'; import type * as vscode from 'vscode'; -import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; import { IAuthenticationService } from '../../../platform/authentication/common/authentication'; +import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; import { TelemetryCorrelationId } from '../../../util/common/telemetryCorrelationId'; import { isLocation, isUri } from '../../../util/common/types'; import { CancellationToken } from '../../../util/vs/base/common/cancellation'; @@ -150,6 +150,12 @@ export class CodebaseTool implements vscode.LanguageModelTool { + public static readonly toolName = ToolName.SearchSubagent; + private _inputContext: IBuildPromptContext | undefined; + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IRequestLogger private readonly requestLogger: IRequestLogger, + @IWorkspaceService private readonly workspaceService: IWorkspaceService + ) { } + async invoke(options: vscode.LanguageModelToolInvocationOptions, token: vscode.CancellationToken) { + const searchInstruction = [ + `Find relevant code snippets for: ${options.input.query}`, + '', + 'More detailed instructions: ', + `${options.input.details}`, + '', + ].join('\n'); + + const request = this._inputContext!.request!; + const parentSessionId = this._inputContext?.conversation?.sessionId ?? generateUuid(); + + const loop = this.instantiationService.createInstance(SearchSubagentToolCallingLoop, { + toolCallLimit: 4, + conversation: new Conversation(parentSessionId, [new Turn(generateUuid(), { type: 'user', message: searchInstruction })]), + request: request, + location: request.location, + promptText: options.input.query, + }); + + const stream = this._inputContext?.stream && ChatResponseStreamImpl.filter( + this._inputContext.stream, + part => part instanceof ChatToolInvocationPart || part instanceof ChatResponseTextEditPart || part instanceof ChatResponseNotebookEditPart + ); + + // Create a new capturing token to group this search subagent and all its nested tool calls + // Similar to how DefaultIntentRequestHandler does it + const searchSubagentToken = new CapturingToken( + `Search: ${options.input.query.substring(0, 50)}${options.input.query.length > 50 ? '...' : ''}`, + 'search', + false + ); + + // Wrap the loop execution in captureInvocation with the new token + // All nested tool calls will now be logged under this same CapturingToken + const loopResult = await this.requestLogger.captureInvocation(searchSubagentToken, () => loop.run(stream, token)); + + // Build subagent trajectory metadata that will be logged via toolMetadata + // All nested tool calls are already logged by ToolCallingLoop.logToolResult() + const toolMetadata = { + query: options.input.query, + description: options.input.description + // details: options.input.details + }; + + let subagentResponse = ''; + if (loopResult.response.type === ChatFetchResponseType.Success) { + subagentResponse = loopResult.toolCallRounds.at(-1)?.response ?? loopResult.round.response ?? ''; + } else { + subagentResponse = `The search subagent request failed with this message:\n${loopResult.response.type}: ${loopResult.response.reason}`; + } + + // Parse and hydrate code snippets from tags + const hydratedResponse = await this.parseFinalAnswerAndHydrate(subagentResponse, token); + + // toolMetadata will be automatically included in exportAllPromptLogsAsJsonCommand + const result = new ExtendedLanguageModelToolResult([new LanguageModelTextPart(hydratedResponse)]); + result.toolMetadata = toolMetadata; + result.toolResultMessage = new MarkdownString(l10n.t`Search complete: ${options.input.description}`); + return result; + } + + /** + * Parse the path and line range subagent response and hydrate code snippets + * @param response The subagent response containing paths and line ranges + * @param token Cancellation token + * @returns The response with actual code snippets appended to file paths + */ + private async parseFinalAnswerAndHydrate(response: string, token: vscode.CancellationToken): Promise { + const lines = response.split('\n'); + + // Parse file:line-line format + const fileRangePattern = /^(.+):(\d+)-(\d+)$/; + const processedLines: string[] = []; + + for (const line of lines) { + const trimmedLine = line.trim(); + + const match = trimmedLine.match(fileRangePattern); + if (!match) { + // I decided to keep non-matching lines as-is, since non-SFTed models sometimes return added info + processedLines.push(line); + continue; + } + + const [, filePath, startLineStr, endLineStr] = match; + const startLine = parseInt(startLineStr, 10); + const endLine = parseInt(endLineStr, 10); + + try { + const uri = URI.file(filePath); + const document = await this.workspaceService.openTextDocument(uri); + const snapshot = TextDocumentSnapshot.create(document); + + const clampedStartLine = Math.max(1, Math.min(startLine, snapshot.lineCount)); + const clampedEndLine = Math.max(1, Math.min(endLine, snapshot.lineCount)); + + const range = new Range( + clampedStartLine - 1, 0, + clampedEndLine - 1, Number.MAX_SAFE_INTEGER + ); + + const code = snapshot.getText(range); + processedLines.push(`File: \`${filePath}\`, lines ${clampedStartLine}-${clampedEndLine}:\n\`\`\`\n${code}\n\`\`\``); + } catch (err) { + // If we can't read the file, keep the original line + processedLines.push(`${trimmedLine} (unable to read file: ${err})`); + } + + if (token.isCancellationRequested) { + break; + } + } + + return processedLines.join('\n'); + } + + prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions, _token: vscode.CancellationToken): vscode.ProviderResult { + return { + invocationMessage: options.input.description, + }; + } + + async resolveInput(input: ISearchSubagentParams, promptContext: IBuildPromptContext, _mode: CopilotToolMode): Promise { + this._inputContext = promptContext; + return input; + } +} + +ToolRegistry.registerTool(SearchSubagentTool); diff --git a/src/extension/tools/node/test/searchSubagentTool.spec.ts b/src/extension/tools/node/test/searchSubagentTool.spec.ts new file mode 100644 index 0000000000..23a243a849 --- /dev/null +++ b/src/extension/tools/node/test/searchSubagentTool.spec.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { expect, suite, test } from 'vitest'; +import { toolCategories, ToolCategory, ToolName } from '../../common/toolNames'; +import { ToolRegistry } from '../../common/toolsRegistry'; + +// Ensure side-effect registration +import '../searchSubagentTool'; + +suite('SearchSubagentTool', () => { + test('is registered and categorized as Core', () => { + const isRegistered = ToolRegistry.getTools().some(t => t.toolName === ToolName.SearchSubagent); + expect(isRegistered).toBe(true); + expect(toolCategories[ToolName.SearchSubagent]).toBe(ToolCategory.Core); + }); +}); diff --git a/src/platform/configuration/common/configurationService.ts b/src/platform/configuration/common/configurationService.ts index 11cd9cd327..42b84035fc 100644 --- a/src/platform/configuration/common/configurationService.ts +++ b/src/platform/configuration/common/configurationService.ts @@ -689,6 +689,7 @@ export namespace ConfigKey { export const PromptFileContext = defineAndMigrateExpSetting('chat.advanced.promptFileContextProvider.enabled', 'chat.promptFileContextProvider.enabled', true); export const DefaultToolsGrouped = defineAndMigrateExpSetting('chat.advanced.tools.defaultToolsGrouped', 'chat.tools.defaultToolsGrouped', false); export const Gpt5AlternativePatch = defineAndMigrateExpSetting('chat.advanced.gpt5AlternativePatch', 'chat.gpt5AlternativePatch', false); + export const SearchSubagentToolEnabled = defineSetting('chat.searchSubagent.enabled', ConfigType.ExperimentBased, false); export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', undefined); export const InlineEditsNextCursorPredictionDisplayLine = defineAndMigrateExpSetting('chat.advanced.inlineEdits.nextCursorPrediction.displayLine', 'chat.inlineEdits.nextCursorPrediction.displayLine', true); export const InlineEditsNextCursorPredictionCurrentFileMaxTokens = defineAndMigrateExpSetting('chat.advanced.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 'chat.inlineEdits.nextCursorPrediction.currentFileMaxTokens', xtabPromptOptions.DEFAULT_OPTIONS.currentFile.maxTokens);