Skip to content

Commit fcbefbf

Browse files
authored
Refactor agent prompt system to use centralized customization resolution (#2202)
* Refactor agent prompt system to use centralized customization resolution * Update tests
1 parent ecf836d commit fcbefbf

29 files changed

+1487
-264
lines changed

src/extension/intents/node/agentIntent.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { IDefaultIntentRequestHandlerOptions } from '../../prompt/node/defaultIn
3838
import { IDocumentContext } from '../../prompt/node/documentContext';
3939
import { IBuildPromptResult, IIntent, IIntentInvocation } from '../../prompt/node/intents';
4040
import { AgentPrompt, AgentPromptProps } from '../../prompts/node/agent/agentPrompt';
41+
import { AgentPromptCustomizations, PromptRegistry, ToolAllowlist } from '../../prompts/node/agent/promptRegistry';
4142
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
4243
import { ICodeMapperService } from '../../prompts/node/codeMapper/codeMapperService';
4344
import { EditCodePrompt2 } from '../../prompts/node/panel/editCodePrompt2';
@@ -52,7 +53,7 @@ import { applyPatch5Description } from '../../tools/node/applyPatchTool';
5253
import { addCacheBreakpoints } from './cacheBreakpoints';
5354
import { EditCodeIntent, EditCodeIntentInvocation, EditCodeIntentInvocationOptions, mergeMetadata, toNewChatReferences } from './editCodeIntent';
5455

55-
export const getAgentTools = (instaService: IInstantiationService, request: vscode.ChatRequest) =>
56+
export const getAgentTools = (instaService: IInstantiationService, request: vscode.ChatRequest, registryToolAllowlist?: ToolAllowlist) =>
5657
instaService.invokeFunction(async accessor => {
5758
const toolsService = accessor.get<IToolsService>(IToolsService);
5859
const testService = accessor.get<ITestProvider>(ITestProvider);
@@ -65,6 +66,14 @@ export const getAgentTools = (instaService: IInstantiationService, request: vsco
6566

6667
const allowTools: Record<string, boolean> = {};
6768

69+
if (registryToolAllowlist) {
70+
for (const [toolName, allowed] of Object.entries(registryToolAllowlist)) {
71+
if (typeof allowed === 'boolean') {
72+
allowTools[toolName] = allowed;
73+
}
74+
}
75+
}
76+
6877
const learned = editToolLearningService.getPreferredEndpointEditTool(model);
6978
if (learned) { // a learning-enabled (BYOK) model, we should go with what it prefers
7079
allowTools[ToolName.EditFile] = learned.includes(ToolName.EditFile);
@@ -209,6 +218,8 @@ export class AgentIntentInvocation extends EditCodeIntentInvocation implements I
209218

210219
protected extraPromptProps: Partial<AgentPromptProps> | undefined;
211220

221+
private _resolvedCustomizations: AgentPromptCustomizations | undefined;
222+
212223
constructor(
213224
intent: IIntent,
214225
location: ChatLocation,
@@ -233,14 +244,15 @@ export class AgentIntentInvocation extends EditCodeIntentInvocation implements I
233244
}
234245

235246
public override getAvailableTools(): Promise<vscode.LanguageModelToolInformation[]> {
236-
return getAgentTools(this.instantiationService, this.request);
247+
return getAgentTools(this.instantiationService, this.request, this._resolvedCustomizations?.toolAllowlist);
237248
}
238249

239250
override async buildPrompt(
240251
promptContext: IBuildPromptContext,
241252
progress: vscode.Progress<vscode.ChatResponseReferencePart | vscode.ChatResponseProgressPart>,
242253
token: vscode.CancellationToken
243254
): Promise<IBuildPromptResult> {
255+
this._resolvedCustomizations = await PromptRegistry.resolveAllCustomizations(this.instantiationService, this.endpoint);
244256
// Add any references from the codebase invocation to the request
245257
const codebase = await this._getCodebaseReferences(promptContext, token);
246258

@@ -283,7 +295,8 @@ export class AgentIntentInvocation extends EditCodeIntentInvocation implements I
283295
},
284296
location: this.location,
285297
enableCacheBreakpoints: summarizationEnabled,
286-
...this.extraPromptProps
298+
...this.extraPromptProps,
299+
customizations: this._resolvedCustomizations
287300
};
288301
try {
289302
const renderer = PromptRenderer.create(this.instantiationService, endpoint, this.prompt, props);

src/extension/prompts/node/agent/agentPrompt.tsx

Lines changed: 64 additions & 197 deletions
Large diffs are not rendered by default.

src/extension/prompts/node/agent/anthropicPrompts.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import { ResponseTranslationRules } from '../base/responseTranslationRules';
1111
import { Tag } from '../base/tag';
1212
import { EXISTING_CODE_MARKER } from '../panel/codeBlockFormattingRules';
1313
import { MathIntegrationRules } from '../panel/editorIntegrationRules';
14-
import { KeepGoingReminder } from './agentPrompt';
15-
import { CodesearchModeInstructions, DefaultAgentPromptProps, detectToolCapabilities, GenericEditingTips, McpToolInstructions, NotebookInstructions } from './defaultAgentInstructions';
16-
import { IAgentPrompt, PromptConstructor, PromptRegistry } from './promptRegistry';
14+
import { CodesearchModeInstructions, DefaultAgentPromptProps, detectToolCapabilities, GenericEditingTips, getEditingReminder, McpToolInstructions, NotebookInstructions, ReminderInstructionsProps } from './defaultAgentInstructions';
15+
import { IAgentPrompt, PromptRegistry, ReminderInstructionsConstructor, SystemPrompt } from './promptRegistry';
1716

1817
class DefaultAnthropicAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
1918
async render(state: void, sizing: PromptSizing) {
@@ -23,7 +22,6 @@ class DefaultAnthropicAgentPrompt extends PromptElement<DefaultAgentPromptProps>
2322
<Tag name='instructions'>
2423
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
2524
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.<br />
26-
<KeepGoingReminder modelFamily={this.props.modelFamily} />
2725
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.</>}<br />
2826
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.<br />
2927
{!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.<br /></>}
@@ -210,14 +208,27 @@ class Claude45DefaultPrompt extends PromptElement<DefaultAgentPromptProps> {
210208
class AnthropicPromptResolver implements IAgentPrompt {
211209
static readonly familyPrefixes = ['claude', 'Anthropic'];
212210

213-
resolvePrompt(endpoint: IChatEndpoint): PromptConstructor | undefined {
211+
resolveSystemPrompt(endpoint: IChatEndpoint): SystemPrompt | undefined {
214212
const normalizedModel = endpoint.model?.replace(/\./g, '-');
215213
if (normalizedModel?.startsWith('claude-sonnet-4-5') ||
216214
normalizedModel?.startsWith('claude-haiku-4-5')) {
217215
return Claude45DefaultPrompt;
218216
}
219217
return DefaultAnthropicAgentPrompt;
220218
}
219+
220+
resolveReminderInstructions(endpoint: IChatEndpoint): ReminderInstructionsConstructor | undefined {
221+
return AnthropicReminderInstructions;
222+
}
223+
}
224+
225+
class AnthropicReminderInstructions extends PromptElement<ReminderInstructionsProps> {
226+
async render(state: void, sizing: PromptSizing) {
227+
return <>
228+
{getEditingReminder(this.props.hasEditFileTool, this.props.hasReplaceStringTool, false /* useStrongReplaceStringHint */, this.props.hasMultiReplaceStringTool)}
229+
Do NOT create a new markdown file to document each change or summarize your work unless specifically requested by the user.<br />
230+
</>;
231+
}
221232
}
222233

223234
PromptRegistry.registerPrompt(AnthropicPromptResolver);

src/extension/prompts/node/agent/defaultAgentInstructions.tsx

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { BasePromptElementProps, PromptElement, PromptSizing } from '@vscode/pro
77
import type { LanguageModelToolInformation } from 'vscode';
88
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
99
import { isGpt5PlusFamily } from '../../../../platform/endpoint/common/chatModelCapabilities';
10+
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
1011
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
1112
import { LanguageModelToolMCPSource } from '../../../../vscodeTypes';
1213
import { ToolName } from '../../../tools/common/toolNames';
@@ -16,10 +17,9 @@ import { ResponseTranslationRules } from '../base/responseTranslationRules';
1617
import { Tag } from '../base/tag';
1718
import { CodeBlockFormattingRules, EXISTING_CODE_MARKER } from '../panel/codeBlockFormattingRules';
1819
import { MathIntegrationRules } from '../panel/editorIntegrationRules';
19-
import { KeepGoingReminder } from './agentPrompt';
2020

2121
// Types and interfaces for reusable components
22-
interface ToolCapabilities extends Partial<Record<ToolName, boolean>> {
22+
export interface ToolCapabilities extends Partial<Record<ToolName, boolean>> {
2323
readonly hasSomeEditTool: boolean;
2424
}
2525

@@ -44,6 +44,65 @@ export interface DefaultAgentPromptProps extends BasePromptElementProps {
4444
readonly codesearchMode: boolean | undefined;
4545
}
4646

47+
export interface ToolReferencesHintProps extends BasePromptElementProps {
48+
readonly toolReferences: readonly { name: string }[];
49+
}
50+
51+
export class DefaultToolReferencesHint extends PromptElement<ToolReferencesHintProps> {
52+
async render() {
53+
if (!this.props.toolReferences.length) {
54+
return;
55+
}
56+
57+
return <>
58+
<Tag name='toolReferences'>
59+
The user attached the following tools to this message. The userRequest may refer to them using the tool name with "#". These tools are likely relevant to the user's query:<br />
60+
{this.props.toolReferences.map(tool => `- ${tool.name}`).join('\n')}
61+
</Tag>
62+
</>;
63+
}
64+
}
65+
66+
export interface ReminderInstructionsProps extends BasePromptElementProps {
67+
readonly endpoint: IChatEndpoint;
68+
readonly hasTodoTool: boolean;
69+
readonly hasEditFileTool: boolean;
70+
readonly hasReplaceStringTool: boolean;
71+
readonly hasMultiReplaceStringTool: boolean;
72+
}
73+
74+
export function getEditingReminder(hasEditFileTool: boolean, hasReplaceStringTool: boolean, useStrongReplaceStringHint: boolean, hasMultiStringReplace: boolean) {
75+
const lines = [];
76+
if (hasEditFileTool) {
77+
lines.push(<>When using the {ToolName.EditFile} tool, avoid repeating existing code, instead use a line comment with \`{EXISTING_CODE_MARKER}\` to represent regions of unchanged code.<br /></>);
78+
}
79+
if (hasReplaceStringTool) {
80+
lines.push(<>
81+
When using the {ToolName.ReplaceString} tool, include 3-5 lines of unchanged code before and after the string you want to replace, to make it unambiguous which part of the file should be edited.<br />
82+
{hasMultiStringReplace && <>For maximum efficiency, whenever you plan to perform multiple independent edit operations, invoke them simultaneously using {ToolName.MultiReplaceString} tool rather than sequentially. This will greatly improve user's cost and time efficiency leading to a better user experience. Do not announce which tool you're using (for example, avoid saying "I'll implement all the changes using multi_replace_string_in_file").<br /></>}
83+
</>);
84+
}
85+
if (hasEditFileTool && hasReplaceStringTool) {
86+
const eitherOr = hasMultiStringReplace ? `${ToolName.ReplaceString} or ${ToolName.MultiReplaceString} tools` : `${ToolName.ReplaceString} tool`;
87+
if (useStrongReplaceStringHint) {
88+
lines.push(<>You must always try making file edits using the {eitherOr}. NEVER use {ToolName.EditFile} unless told to by the user or by a tool.</>);
89+
} else {
90+
lines.push(<>It is much faster to edit using the {eitherOr}. Prefer the {eitherOr} for making edits and only fall back to {ToolName.EditFile} if it fails.</>);
91+
}
92+
}
93+
94+
return lines;
95+
}
96+
97+
export class DefaultReminderInstructions extends PromptElement<ReminderInstructionsProps> {
98+
async render(state: void, sizing: PromptSizing) {
99+
return <>
100+
{/* Tool-dependent editing reminders that apply to all models */}
101+
{getEditingReminder(this.props.hasEditFileTool, this.props.hasReplaceStringTool, false /* useStrongReplaceStringHint */, this.props.hasMultiReplaceStringTool)}
102+
</>;
103+
}
104+
}
105+
47106
/**
48107
* Base system prompt for agent mode
49108
*/
@@ -55,7 +114,6 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
55114
<Tag name='instructions'>
56115
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
57116
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.<br />
58-
<KeepGoingReminder modelFamily={this.props.modelFamily} />
59117
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.</>}<br />
60118
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.<br />
61119
{!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.<br /></>}
@@ -156,7 +214,6 @@ export class AlternateGPTPrompt extends PromptElement<DefaultAgentPromptProps> {
156214
return <InstructionMessage>
157215
<Tag name='gptAgentInstructions'>
158216
You are a highly sophisticated coding agent with expert-level knowledge across programming languages and frameworks.<br />
159-
<KeepGoingReminder modelFamily={this.props.modelFamily} />
160217
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. You can use the {ToolName.ReadFile} tool to read more context, but only do this if the attached file is incomplete.</>}<br />
161218
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.<br />
162219
Use multiple tools as needed, and do not give up until the task is complete or impossible.<br />

src/extension/prompts/node/agent/geminiPrompts.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import { ResponseTranslationRules } from '../base/responseTranslationRules';
1111
import { Tag } from '../base/tag';
1212
import { EXISTING_CODE_MARKER } from '../panel/codeBlockFormattingRules';
1313
import { MathIntegrationRules } from '../panel/editorIntegrationRules';
14-
import { KeepGoingReminder } from './agentPrompt';
15-
import { CodesearchModeInstructions, DefaultAgentPromptProps, detectToolCapabilities, GenericEditingTips, McpToolInstructions, NotebookInstructions } from './defaultAgentInstructions';
16-
import { IAgentPrompt, PromptConstructor, PromptRegistry } from './promptRegistry';
14+
import { CodesearchModeInstructions, DefaultAgentPromptProps, detectToolCapabilities, GenericEditingTips, getEditingReminder, McpToolInstructions, NotebookInstructions, ReminderInstructionsProps } from './defaultAgentInstructions';
15+
import { IAgentPrompt, PromptRegistry, ReminderInstructionsConstructor, SystemPrompt } from './promptRegistry';
1716

1817
/**
1918
* Base system prompt for agent mode
@@ -26,7 +25,6 @@ export class DefaultGeminiAgentPrompt extends PromptElement<DefaultAgentPromptPr
2625
<Tag name='instructions'>
2726
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
2827
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.<br />
29-
<KeepGoingReminder modelFamily={this.props.modelFamily} />
3028
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.</>}<br />
3129
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.<br />
3230
{!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.<br /></>}
@@ -119,9 +117,22 @@ class GeminiPromptResolver implements IAgentPrompt {
119117

120118
static readonly familyPrefixes = ['gemini'];
121119

122-
resolvePrompt(endpoint: IChatEndpoint): PromptConstructor | undefined {
120+
resolveSystemPrompt(endpoint: IChatEndpoint): SystemPrompt | undefined {
123121
return DefaultGeminiAgentPrompt;
124122
}
123+
124+
resolveReminderInstructions(endpoint: IChatEndpoint): ReminderInstructionsConstructor | undefined {
125+
return GeminiReminderInstructions;
126+
}
127+
}
128+
129+
class GeminiReminderInstructions extends PromptElement<ReminderInstructionsProps> {
130+
async render(state: void, sizing: PromptSizing) {
131+
// Gemini models need the strong replace string hint
132+
return <>
133+
{getEditingReminder(this.props.hasEditFileTool, this.props.hasReplaceStringTool, true /* useStrongReplaceStringHint */, this.props.hasMultiReplaceStringTool)}
134+
</>;
135+
}
125136
}
126137

127138

0 commit comments

Comments
 (0)