runSubagentTool.ts ×16

Frontier kind: Code frontier

unlabeled · c_c891cf2403f4

8 tests · 50276 LOC · 214 files · introduces 0 tests · 146 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
16 ranges146 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
3897 ranges50276 lines · 214 files · Browse complete extent
All tests (intent)
8 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 146 introduced LOC across 16 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts 146 introduced LOC · 16 ranges

Open complete file

134
135 async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {
136 > const args = invocation.parameters as IRunSubagentToolInputParams; runSubagentTool.ts
137 >
138 > this.logService.debug(`RunSubagentTool: Invoking with prompt: ${args.prompt.substring(0, 100)}...`);
139 >
140 > if (!invocation.context) {
141 throw new Error('toolInvocationToken is required for this tool');
142 }
144 > // Get the chat model and request for writing progress
145 > const model = this.chatService.getSession(invocation.context.sessionResource) as ChatModel | undefined;
146 > if (!model) {
147 throw new Error('Chat model not found for session');
148 }
150 > const request = model.getRequests().at(-1)!;
151 > let subagentCredits: number | undefined;
152 >
153 > const store = new DisposableStore();
154 >
155 > try {
156 > // Get the default agent
157 > const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, ChatModeKind.Agent);
158 > if (!defaultAgent) {
159 return createToolSimpleTextResult('Error: No default agent available');
160 }
162 > // Resolve mode-specific configuration if subagentId is provided
163 > let modeModelId = invocation.modelId;
164 > let modeTools = invocation.userSelectedTools;
165 > let modeInstructions: IChatRequestModeInstructions | undefined;
166 > let subagent: ICustomAgent | undefined;
167 > let resolvedModelName: string | undefined;
168 > const currentModeInstructions = request.modeInfo?.modeInstructions;
169 >
170 > const subAgentName = this.normalizeRequestedAgentName(args.agentName);
171 > const effectiveSubAgentName = subAgentName ?? currentModeInstructions?.name;
172 >
173 > if (subAgentName) {
174 subagent = await this.getSubAgentByName(subAgentName);
175 if (subagent) {
214 throw new Error(`Requested agent '${subAgentName}' not found. Try again with the correct agent name, or omit agentName to use the current agent.`);
215 }
216 > } else { runSubagentTool.ts
217 > modeInstructions = currentModeInstructions;
218 >
219 > // No subagent name - clean up any cached entry and resolve model from explicit parameter or main model
220 > const cached = this._resolvedModels.get(invocation.callId);
221 > if (cached) {
222 this._resolvedModels.delete(invocation.callId);
223 modeModelId = cached.modeModelId;
224 resolvedModelName = cached.resolvedModelName;
225 > } else { runSubagentTool.ts
226 > const resolved = this.resolveSubagentModel(undefined, invocation.modelId, args.model);
227 > modeModelId = resolved.modeModelId;
228 > resolvedModelName = resolved.resolvedModelName;
229 > }
230 > }
231 >
232 > // Track whether we should collect markdown (after the last tool invocation)
233 > const markdownParts: string[] = [];
234 >
235 > // Generate a stable subAgentInvocationId for routing edits to this subagent's content part.
236 > // Use chatStreamToolCallId when available because that is what ChatToolInvocation.toolCallId
237 > // uses in the renderer (see PR #302863), and the subagent grouping matches on toolCallId.
238 > const subAgentInvocationId = invocation.chatStreamToolCallId ?? invocation.callId ?? `subagent-${generateUuid()}`;
239 >
240 > let inEdit = false;
241 > const progressCallback = (parts: IChatProgress[]) => {
242 for (const part of parts) {
243 // Usage events carry the subagent's running credit total; keep the
274 }
275 };
277 > // Determine whether the subagent should be allowed to spawn its own subagents.
278 > const allowInvocationsFromSubagents = this.configurationService.getValue<boolean>(ChatConfiguration.SubagentsAllowInvocationsFromSubagents) ?? false;
279 > const maxDepth = allowInvocationsFromSubagents ? RUN_SUBAGENT_MAX_NESTING_DEPTH : 0;
280 > const sessionKey = invocation.context.sessionResource.toString();
281 > const currentDepth = this._sessionDepth.get(sessionKey) ?? 0;
282 > const depthAllowed = currentDepth + 1 <= maxDepth;
283 >
284 > if (!modeTools) {
285 // Initialize modeTools so that we can still enforce the max depth restriction
286 modeTools = {};
287 }
289 > // Only further-restrict RunSubagentTool: do not re-enable it if it was explicitly disabled.
290 > const existingRunSubagentEnablement = modeTools[RunSubagentTool.Id];
291 > if (existingRunSubagentEnablement !== false) {
292 > modeTools[RunSubagentTool.Id] = depthAllowed; // only enable the Run Subagent tool if we are under the max depth limit
293 > }
294 >
295 > modeTools[ManageTodoListToolToolId] = false;
296 > modeTools['copilot_askQuestions'] = false;
297 >
298 > if (maxDepth > 0) {
299 this.logService.debug(`RunSubagentTool: Nested subagents enabling ${modeTools[RunSubagentTool.Id]}: session ${sessionKey}, currentDepth: ${currentDepth}, maxDepth: ${maxDepth}, allowInvocationsFromSubagents: ${allowInvocationsFromSubagents}`);
300 }
302 > const variableSet = new ChatRequestVariableSet();
303 > // When the extension is responsible for instruction collection, skip the core path entirely.
304 > if (this.configurationService.getValue<boolean>(ChatConfiguration.CollectInstructionsInExtension) !== true) {
305 > const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, modeTools, undefined, getChatSessionType(invocation.context.sessionResource));
306 > await computer.collect(variableSet, token);
307 > }
308 >
309 > // Collect hooks from hook .json files
310 > let collectedHooks: ChatRequestHooks | undefined;
311 > try {
312 > const info = await this.promptsService.getHooks(token);
313 collectedHooks = info?.hooks;
314 > } catch (error) { runSubagentTool.ts
315 > this.logService.warn('[ChatService] Failed to collect hooks:', error);
316 > }
317 >
318 > // Merge subagent-level hooks (from the agent's frontmatter) with global hooks.
319 > // Remap Stop hooks to SubagentStop since the agent is running as a subagent.
320 > if (subagent?.hooks) {
321 const remapped: ChatRequestHooks = { ...subagent.hooks };
322 if (remapped[HookType.Stop]) {
329 collectedHooks = mergeHooks(collectedHooks, remapped);
330 }
332 > // Build the agent request
333 > const agentRequest: IChatAgentRequest = {
334 > sessionResource: invocation.context.sessionResource,
335 > requestId: invocation.callId ?? `subagent-${Date.now()}`,
336 > agentId: defaultAgent.id,
337 > message: args.prompt,
338 > variables: { variables: variableSet.asArray() },
339 > location: ChatAgentLocation.Chat,
340 > subAgentInvocationId: subAgentInvocationId,
341 > subAgentName: effectiveSubAgentName,
342 > userSelectedModelId: modeModelId,
343 > modelConfiguration: modeModelId ? this.languageModelsService.getModelConfiguration(modeModelId) : undefined,
344 > userSelectedTools: modeTools,
345 > modeInstructions,
346 > parentRequestId: invocation.chatRequestId,
347 > hooks: collectedHooks,
348 > hasHooksEnabled: !!collectedHooks && Object.values(collectedHooks).some(arr => arr && arr.length > 0),
349 > };
350 >
351 > // Subscribe to tool invocations to clear markdown parts when a tool is invoked
352 > store.add(this.languageModelToolsService.onDidInvokeTool(e => {
353 if (e.subagentInvocationId === subAgentInvocationId) {
354 markdownParts.length = 0;
355 }
356 > })); runSubagentTool.ts
357 >
358 > // Invoke the agent, tracking nesting depth for recursion detection
359 > this._sessionDepth.set(sessionKey, currentDepth + 1);
360 > let result: IChatAgentResult | undefined;
361 > try {
362 > result = await this.chatAgentService.invokeAgent(
363 > defaultAgent.id,
364 > agentRequest,
365 > progressCallback,
366 > [],
367 > token
368 > );
369 > } finally {
370 > const newDepth = (this._sessionDepth.get(sessionKey) ?? 1) - 1;
371 > if (newDepth <= 0) {
372 > this._sessionDepth.delete(sessionKey);
373 > } else {
374 this._sessionDepth.set(sessionKey, newDepth);
375 }
377 >
378 > // Check for errors
379 > if (result?.errorDetails) {
380 return createToolSimpleTextResult(`Agent error: ${result.errorDetails.message}`);
381 }
384 // in the meantime, just strip an empty codeblock left behind.
385 const resultText = markdownParts.join('').replace(/^\n*```\n+```\n*/g, '').trim() || 'Agent completed with no output';
387 > // Store result in toolSpecificData for serialization
388 > if (invocation.toolSpecificData?.kind === 'subagent') {
389 invocation.toolSpecificData.result = resultText;
390 invocation.toolSpecificData.modelName = resolvedModelName;
409 this.logService.error(errorMessage, error);
410 return createToolSimpleTextResult(errorMessage);
411 > } finally { runSubagentTool.ts
412 > if (subagentCredits !== undefined) {
413 request.response?.setSubagentCopilotCredits(invocation.callId, subagentCredits);
414 if (invocation.toolSpecificData?.kind === 'subagent') {
416 }
417 }
418 > store.dispose(); runSubagentTool.ts
419 > }
420 > }
421
422 private async getSubAgentByName(name: string): Promise<ICustomAgent | undefined> {