Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/node/searchSubagentToolCallingLoop.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { randomUUID } from 'crypto';
7
import type { CancellationToken, ChatRequest, ChatResponseStream, LanguageModelToolInformation, Progress } from 'vscode';
8
import { IAuthenticationChatUpgradeService } from '../../../platform/authentication/common/authenticationUpgrade';
9
import { IChatHookService } from '../../../platform/chat/common/chatHookService';
10
import { ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes';
11
import { ISessionTranscriptService } from '../../../platform/chat/common/sessionTranscriptService';
12
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
13
import { ChatEndpointFamily, IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
14
import { ProxyAgenticEndpoint } from '../../../platform/endpoint/node/proxyAgenticEndpoint';
15
import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
16
import { IGitService } from '../../../platform/git/common/gitService';
17
import { ILogService } from '../../../platform/log/common/logService';
18
import { IOTelService } from '../../../platform/otel/common/otelService';
19
import { IRequestLogger } from '../../../platform/requestLogger/common/requestLogger';
20
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
21
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
22
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
23
import { ChatResponseProgressPart, ChatResponseReferencePart } from '../../../vscodeTypes';
24
import { IToolCallingLoopOptions, ToolCallingLoop, ToolCallingLoopFetchOptions } from '../../intents/node/toolCallingLoop';
25
import { SearchSubagentPrompt } from '../../prompts/node/agent/searchSubagentPrompt';
26
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
27
import { ToolName } from '../../tools/common/toolNames';
28
import { IToolsService } from '../../tools/common/toolsService';
29
import { IBuildPromptContext } from '../common/intents';
30
import { IBuildPromptResult } from './intents';
31
32
export interface ISearchSubagentToolCallingLoopOptions extends IToolCallingLoopOptions {
33
request: ChatRequest;
34
location: ChatLocation;
35
promptText: string;
36
/** Optional pre-generated subagent invocation ID. If not provided, a new UUID will be generated. */
37
subAgentInvocationId?: string;
38
/** The tool_call_id from the parent agent's LLM response that triggered this subagent invocation. */
39
parentToolCallId?: string;
40
/** The headerRequestId from the parent agent's fetch response that triggered this subagent invocation. */
41
parentHeaderRequestId?: string;
42
/** Thoroughness level for the search, passed through to the prompt when thoroughnessEnabled config is on. */
43
thoroughness?: 'normal' | 'deep';
44
}
45
46
export class SearchSubagentToolCallingLoop extends ToolCallingLoop<ISearchSubagentToolCallingLoopOptions> {
47
48
public static readonly ID = 'searchSubagentTool';
49
50
constructor(
51
options: ISearchSubagentToolCallingLoopOptions,
52
@IInstantiationService private readonly instantiationService: IInstantiationService,
53
@ILogService logService: ILogService,
54
@IRequestLogger requestLogger: IRequestLogger,
55
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
56
@IToolsService private readonly toolsService: IToolsService,
57
@IAuthenticationChatUpgradeService authenticationChatUpgradeService: IAuthenticationChatUpgradeService,
58
@ITelemetryService telemetryService: ITelemetryService,
59
@IConfigurationService configurationService: IConfigurationService,
60
@IExperimentationService experimentationService: IExperimentationService,
61
@IChatHookService chatHookService: IChatHookService,
62
@ISessionTranscriptService sessionTranscriptService: ISessionTranscriptService,
63
@IFileSystemService fileSystemService: IFileSystemService,
64
@IOTelService otelService: IOTelService,
65
@IGitService gitService: IGitService,
66
) {
67
super(options, instantiationService, endpointProvider, logService, requestLogger, authenticationChatUpgradeService, telemetryService, configurationService, experimentationService, chatHookService, sessionTranscriptService, fileSystemService, otelService, gitService);
68
}
69
70
protected override createPromptContext(availableTools: LanguageModelToolInformation[], outputStream: ChatResponseStream | undefined): IBuildPromptContext {
71
const context = super.createPromptContext(availableTools, outputStream);
72
if (context.tools) {
73
context.tools = {
74
...context.tools,
75
toolReferences: [],
76
subAgentInvocationId: this.options.subAgentInvocationId ?? randomUUID(),
77
subAgentName: 'search'
78
};
79
}
80
context.query = this.options.promptText;
81
return context;
82
}
83
84
private static readonly DEFAULT_AGENTIC_PROXY_MODEL = 'vscode-agentic-search-router-a';
85
86
/**
87
* Get the endpoint to use for the search subagent
88
*/
89
private async getEndpoint() {
90
const modelName = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SearchSubagentModel, this._experimentationService) as ChatEndpointFamily | undefined;
91
const useAgenticProxy = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SearchSubagentUseAgenticProxy, this._experimentationService);
92
93
if (useAgenticProxy) {
94
// Use agentic proxy with SearchSubagentModel or default to 'agentic-search-v3'
95
const agenticProxyModel = modelName || SearchSubagentToolCallingLoop.DEFAULT_AGENTIC_PROXY_MODEL;
96
return this.instantiationService.createInstance(ProxyAgenticEndpoint, agenticProxyModel);
97
}
98
99
if (modelName) {
100
try {
101
// Try to get the specified model
102
return await this.endpointProvider.getChatEndpoint(modelName);
103
} catch (error) {
104
// Model not available or doesn't support tool calls, fallback to main agent
105
this._logService.warn(`Failed to get model ${modelName}, falling back to main agent endpoint: ${error}`);
106
return await this.endpointProvider.getChatEndpoint(this.options.request);
107
}
108
} else {
109
// No model name specified, use main agent endpoint
110
return await this.endpointProvider.getChatEndpoint(this.options.request);
111
}
112
}
113
114
protected async buildPrompt(buildPromptContext: IBuildPromptContext, progress: Progress<ChatResponseReferencePart | ChatResponseProgressPart>, token: CancellationToken): Promise<IBuildPromptResult> {
115
const endpoint = await this.getEndpoint();
116
// Use the effective tool call limit from options (already adjusted for thoroughness in the tool)
117
const maxSearchTurns = this.options.toolCallLimit;
118
const renderer = PromptRenderer.create(
119
this.instantiationService,
120
endpoint,
121
SearchSubagentPrompt,
122
{
123
promptContext: buildPromptContext,
124
maxSearchTurns,
125
thoroughness: this.options.thoroughness,
126
}
127
);
128
return await renderer.render(progress, token);
129
}
130
131
protected async getAvailableTools(): Promise<LanguageModelToolInformation[]> {
132
const endpoint = await this.getEndpoint();
133
const allTools = this.toolsService.getEnabledTools(this.options.request, endpoint);
134
135
// Only include tools relevant for search operations.
136
// We include semantic_search (Codebase) and the basic search primitives.
137
// The Codebase tool checks for inSubAgent context to prevent nested tool calling loops.
138
const allowedSearchTools = new Set([
139
ToolName.Codebase, // Semantic search
140
ToolName.FindFiles,
141
ToolName.FindTextInFiles,
142
ToolName.ReadFile
143
]);
144
145
return allTools.filter(tool => allowedSearchTools.has(tool.name as ToolName));
146
}
147
148
protected async fetch({ messages, finishedCb, requestOptions, modelCapabilities }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise<ChatResponse> {
149
const endpoint = await this.getEndpoint();
150
return endpoint.makeChatRequest2({
151
debugName: SearchSubagentToolCallingLoop.ID,
152
messages,
153
finishedCb,
154
location: this.options.location,
155
modelCapabilities: { ...modelCapabilities, reasoningEffort: undefined },
156
requestOptions: {
157
...requestOptions,
158
temperature: 0
159
},
160
// This loop is inside a tool called from another request, so never user initiated
161
userInitiatedRequest: false,
162
telemetryProperties: {
163
requestId: this.options.subAgentInvocationId,
164
messageId: randomUUID(),
165
messageSource: 'chat.editAgent',
166
subType: 'subagent/search',
167
conversationId: this.options.conversation.sessionId,
168
parentToolCallId: this.options.parentToolCallId,
169
parentHeaderRequestId: this.options.parentHeaderRequestId,
170
},
171
requestKindOptions: { kind: 'subagent' }
172
}, token);
173
}
174
}
175
176