mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-27 10:48:28 +01:00
0af20a790c
* search subagent tool added * cleaning up description of search subagent * additional changes * update linting issue * Exit early on search subagent call * search subagent tool added * cleaning up description of search subagent * additional changes * update linting issue * Add fixes for subagent * describe read file tool in its prompt * fixing copilot cli issues? * resolve merge conflicts with main * explicit any pt 2 * update explicit any to unknown * demo * updating prompt to include description * fixing newline bug * added correct input params for subagent * update to add final turn warning injection * code snippet hydration * adding details to toolMetadata (untested) * commented out until testing * remove exit from main PR * actuallly terminate loop * end loop after round is added and run * remove early exit handling * add experiment flags * update code to check for exp + auto mode * update to only use gpt 5 mini for search subagent * Update src/extension/prompts/node/agent/searchSubagentPrompt.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update docs/tools.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update package.nls.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * update tests to handle new prompts * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * deleting vestigial file * fix merge conflict * update to default to using the main agent model as fallback for subagent, rather than hardcoding gpt4.1 * remove extra whitespace * remove runSubagent from package.json and update prompt for final snippet clarity * reset default to false * add clearer injection prompt * updating to work with main branch changes * rewrite search subagent to have its own tool calling loop file + ensure no nested loops with codebase * remove copilot-added search subagent doc * update toolResultMessage * handle exp configuration for search subagent in the right place * use searchSubagentLoop instead of subagentLoop * update to be in line with main * remove CCA agents * Some minor cleanup * Update import for ChatToolInvocationPart in SearchSubagentTool * Replace inSubAgent flag with subAgentInvocationId for tool calling loop checks --------- Co-authored-by: Anisha Agarwal <anishaagarwal@Anishas-MacBook-Pro.local> Co-authored-by: Vritant Bhardwaj <vrtoku@gmail.com> Co-authored-by: root <root@perflens-vm7.e4rbrboag42enkzhvodo1frcqh.xx.internal.cloudapp.net> Co-authored-by: Anisha Agarwal <anishaagarwal@MacBookPro.hsd1.ma.comcast.net> Co-authored-by: Zhichao Li <zhichli@microsoft.com> Co-authored-by: vritant24 <vritoku@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: bhavyaus <bhavyau@microsoft.com>
178 lines
7.6 KiB
TypeScript
178 lines
7.6 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as l10n from '@vscode/l10n';
|
|
import { PromptElement, PromptReference, TokenLimit } from '@vscode/prompt-tsx';
|
|
import type * as vscode from 'vscode';
|
|
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';
|
|
import { basename } from '../../../util/vs/base/common/path';
|
|
import { URI } from '../../../util/vs/base/common/uri';
|
|
import { generateUuid } from '../../../util/vs/base/common/uuid';
|
|
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
|
|
import { ExtendedLanguageModelToolResult, LanguageModelPromptTsxPart, MarkdownString } from '../../../vscodeTypes';
|
|
import { ChatVariablesCollection } from '../../prompt/common/chatVariablesCollection';
|
|
import { getUniqueReferences } from '../../prompt/common/conversation';
|
|
import { IBuildPromptContext } from '../../prompt/common/intents';
|
|
import { CodebaseToolCallingLoop } from '../../prompt/node/codebaseToolCalling';
|
|
import { renderPromptElementJSON } from '../../prompts/node/base/promptRenderer';
|
|
import { ToolCallResultWrapper } from '../../prompts/node/panel/toolCalling';
|
|
import { WorkspaceContext, WorkspaceContextProps } from '../../prompts/node/panel/workspace/workspaceContext';
|
|
import { ToolName } from '../common/toolNames';
|
|
import { ToolRegistry } from '../common/toolsRegistry';
|
|
import { checkCancellation } from './toolUtils';
|
|
|
|
export interface ICodebaseToolParams {
|
|
query: string;
|
|
|
|
// Internal parameter only.
|
|
includeFileStructure?: boolean;
|
|
scopedDirectories?: string[]; // Allows to scope the search to a specific set of directories.
|
|
}
|
|
|
|
export class CodebaseTool implements vscode.LanguageModelTool<ICodebaseToolParams> {
|
|
public static readonly toolName = ToolName.Codebase;
|
|
|
|
constructor(
|
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
|
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
|
) { }
|
|
|
|
async invoke(options: vscode.LanguageModelToolInvocationOptions<ICodebaseToolParams>, token: CancellationToken) {
|
|
if (this._input && this._isCodebaseAgentCall(options)) {
|
|
const input = this._input;
|
|
this._input = undefined; // consumed
|
|
return this.invokeCodebaseAgent(input, token);
|
|
}
|
|
|
|
if (!options.input.query) {
|
|
throw new Error('Invalid input');
|
|
}
|
|
|
|
checkCancellation(token);
|
|
|
|
let references: PromptReference[] = [];
|
|
const id = generateUuid();
|
|
const promptTsxResult = await renderPromptElementJSON(this.instantiationService, WorkspaceContextWrapper, {
|
|
telemetryInfo: new TelemetryCorrelationId('codebaseTool', id),
|
|
promptContext: {
|
|
requestId: id,
|
|
chatVariables: new ChatVariablesCollection([]),
|
|
query: options.input.query,
|
|
history: [],
|
|
},
|
|
maxResults: 32,
|
|
include: {
|
|
workspaceChunks: true,
|
|
workspaceStructure: options.input.includeFileStructure ?? false
|
|
},
|
|
scopedDirectories: options.input.scopedDirectories?.map(dir => URI.file(dir)),
|
|
referencesOut: references,
|
|
isToolCall: true,
|
|
lines1Indexed: true,
|
|
absolutePaths: true,
|
|
priority: 100,
|
|
}, undefined, token);
|
|
const result = new ExtendedLanguageModelToolResult([
|
|
new LanguageModelPromptTsxPart(promptTsxResult)
|
|
]);
|
|
references = getUniqueReferences(references);
|
|
result.toolResultMessage = references.length === 0 ?
|
|
new MarkdownString(l10n.t`Searched ${this.getDisplaySearchTarget(options.input)} for "${options.input.query}", no results`) :
|
|
references.length === 1 ?
|
|
new MarkdownString(l10n.t`Searched ${this.getDisplaySearchTarget(options.input)} for "${options.input.query}", 1 result`) :
|
|
new MarkdownString(l10n.t`Searched ${this.getDisplaySearchTarget(options.input)} for "${options.input.query}", ${references.length} results`);
|
|
result.toolResultDetails = references
|
|
.map(r => r.anchor)
|
|
.filter(r => isUri(r) || isLocation(r));
|
|
return result;
|
|
}
|
|
|
|
private async invokeCodebaseAgent(input: IBuildPromptContext, token: CancellationToken) {
|
|
if (!input.request || !input.conversation) {
|
|
throw new Error('Invalid input');
|
|
}
|
|
|
|
const codebaseTool = this.instantiationService.createInstance(CodebaseToolCallingLoop, {
|
|
toolCallLimit: 5,
|
|
conversation: input.conversation,
|
|
request: input.request,
|
|
location: input.request.location,
|
|
});
|
|
|
|
const toolCallLoopResult = await codebaseTool.run(undefined, token);
|
|
const promptElement = await renderPromptElementJSON(this.instantiationService, ToolCallResultWrapper, { toolCallResults: toolCallLoopResult.toolCallResults });
|
|
|
|
return { content: [new LanguageModelPromptTsxPart(promptElement)] };
|
|
}
|
|
|
|
private _input: IBuildPromptContext | undefined;
|
|
async provideInput(promptContext: IBuildPromptContext): Promise<IBuildPromptContext> {
|
|
this._input = promptContext; // TODO@joyceerhl @roblourens HACK: Avoid types in the input being serialized and not deserialized when they go through invokeTool
|
|
return promptContext;
|
|
}
|
|
|
|
prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<ICodebaseToolParams>, token: vscode.CancellationToken): vscode.ProviderResult<vscode.PreparedToolInvocation> {
|
|
if (this._input && this._isCodebaseAgentCall(options)) {
|
|
return {
|
|
presentation: 'hidden'
|
|
};
|
|
}
|
|
|
|
return {
|
|
invocationMessage: new MarkdownString(l10n.t`Searching ${this.getDisplaySearchTarget(options.input)} for "${options.input.query}"`),
|
|
};
|
|
}
|
|
|
|
private getDisplaySearchTarget(input: ICodebaseToolParams): string {
|
|
let targetSearch;
|
|
if (input.scopedDirectories && input.scopedDirectories.length === 1) {
|
|
targetSearch = `${basename(input.scopedDirectories[0])}`;
|
|
} else if (input.scopedDirectories && input.scopedDirectories.length > 1) {
|
|
targetSearch = l10n.t("{0} directories", input.scopedDirectories.length);
|
|
} else {
|
|
targetSearch = l10n.t("codebase");
|
|
}
|
|
|
|
return targetSearch;
|
|
}
|
|
|
|
private _isCodebaseAgentCall(options: vscode.LanguageModelToolInvocationPrepareOptions<ICodebaseToolParams> | vscode.LanguageModelToolInvocationOptions<ICodebaseToolParams>): boolean {
|
|
const input = options.input;
|
|
const agentEnabled = this.configurationService.getConfig(ConfigKey.CodeSearchAgentEnabled);
|
|
const noScopedDirectories = input.scopedDirectories === undefined || input.scopedDirectories.length === 0;
|
|
|
|
// When anonymous (no GitHub session), always force agent path so we avoid relying on semantic index features.
|
|
const isAnonymous = !this.authenticationService.anyGitHubSession;
|
|
|
|
// Don't trigger nested tool calling loop if we're already in a subagent
|
|
if (this._input?.tools?.subAgentInvocationId) {
|
|
return false;
|
|
}
|
|
|
|
return (isAnonymous || agentEnabled) && noScopedDirectories;
|
|
}
|
|
}
|
|
|
|
ToolRegistry.registerTool(CodebaseTool);
|
|
|
|
class WorkspaceContextWrapper extends PromptElement<WorkspaceContextProps> {
|
|
constructor(
|
|
props: WorkspaceContextProps,
|
|
) {
|
|
super(props);
|
|
}
|
|
|
|
render() {
|
|
// Main limit is set via maxChunks. Set a TokenLimit just to be sure.
|
|
return <TokenLimit max={28_000}>
|
|
<WorkspaceContext {...this.props} />
|
|
</TokenLimit>;
|
|
}
|
|
} |