Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/agent/agentPrompt.tsx
13405 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 { BasePromptElementProps, Chunk, Document, PromptElement, PromptPiece, PromptPieceChild, PromptSizing, Raw, SystemMessage, TokenLimit, UserMessage } from '@vscode/prompt-tsx';
7
import type { ChatRequestEditedFileEvent, LanguageModelToolInformation, NotebookEditor, TaskDefinition, TextEditor } from 'vscode';
8
import { sessionResourceToId } from '../../../../platform/chat/common/chatDebugFileLoggerService';
9
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
10
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
11
import { ICustomInstructionsService } from '../../../../platform/customInstructions/common/customInstructionsService';
12
import { USE_SKILL_ADHERENCE_PROMPT_SETTING } from '../../../../platform/customInstructions/common/promptTypes';
13
import { CacheType } from '../../../../platform/endpoint/common/endpointTypes';
14
import { IEnvService, OperatingSystem } from '../../../../platform/env/common/envService';
15
import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';
16
import { ILogService } from '../../../../platform/log/common/logService';
17
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
18
import { IAlternativeNotebookContentService } from '../../../../platform/notebook/common/alternativeContent';
19
import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';
20
import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';
21
import { ITasksService } from '../../../../platform/tasks/common/tasksService';
22
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
23
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
24
import { isDefined, isString } from '../../../../util/vs/base/common/types';
25
import { URI } from '../../../../util/vs/base/common/uri';
26
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
27
import { ChatRequestEditedFileEventKind, Position, Range } from '../../../../vscodeTypes';
28
import { GenericBasePromptElementProps } from '../../../context/node/resolvers/genericPanelIntentInvocation';
29
import { ChatVariablesCollection, extractDebugTargetSessionIds, isCustomizationsIndex } from '../../../prompt/common/chatVariablesCollection';
30
import { getGlobalContextCacheKey, GlobalContextMessageMetadata, RenderedUserMessageMetadata, Turn } from '../../../prompt/common/conversation';
31
import { InternalToolReference } from '../../../prompt/common/intents';
32
import { IPromptVariablesService } from '../../../prompt/node/promptVariablesService';
33
import { ToolName } from '../../../tools/common/toolNames';
34
import { MemoryContextPrompt, MemoryInstructionsPrompt } from '../../../tools/node/memoryContextPrompt';
35
import { TodoListContextPrompt } from '../../../tools/node/todoListContextPrompt';
36
import { IPromptEndpoint, renderPromptElement } from '../base/promptRenderer';
37
import { Tag } from '../base/tag';
38
import { TerminalStatePromptElement } from '../base/terminalState';
39
import { ChatVariables, UserQuery } from '../panel/chatVariables';
40
import { CustomInstructions } from '../panel/customInstructions';
41
import { HistoricalImage } from '../panel/image';
42
import { NotebookFormat, NotebookReminderInstructions } from '../panel/notebookEditCodePrompt';
43
import { NotebookSummaryChange } from '../panel/notebookSummaryChangePrompt';
44
import { UserPreferences } from '../panel/preferences';
45
import { ChatToolCalls } from '../panel/toolCalling';
46
import { AgentMultirootWorkspaceStructure } from '../panel/workspace/workspaceStructure';
47
import { AgentConversationHistory } from './agentConversationHistory';
48
import './allAgentPrompts';
49
import { AlternateGPTPrompt, DefaultReminderInstructions, DefaultToolReferencesHint, ReminderInstructionsProps, ToolReferencesHintProps } from './defaultAgentInstructions';
50
import { AgentPromptCustomizations, ReminderInstructionsConstructor, ToolReferencesHintConstructor } from './promptRegistry';
51
import { SummarizedConversationHistory } from './summarizedConversationHistory';
52
import { DeferredToolListReminder } from './toolSearchInstructions';
53
54
export interface AgentPromptProps extends GenericBasePromptElementProps {
55
readonly endpoint: IChatEndpoint;
56
readonly location: ChatLocation;
57
58
readonly triggerSummarize?: boolean;
59
60
/**
61
* Enables cache breakpoints and summarization
62
*/
63
readonly enableCacheBreakpoints?: boolean;
64
65
/**
66
* Codesearch mode, aka agentic Ask mode
67
*/
68
readonly codesearchMode?: boolean;
69
70
/**
71
* All resolved customizations from the prompt registry.
72
*/
73
readonly customizations?: AgentPromptCustomizations;
74
75
/**
76
* Prefer Simple mode for summarization, typically for the budget-exceeded recovery path.
77
* An explicit summarization mode configuration can still force Full mode.
78
*/
79
readonly forceSimpleSummary?: boolean;
80
}
81
82
/** Proportion of the prompt token budget any singular textual tool result is allowed to use. */
83
const MAX_TOOL_RESPONSE_PCT = 0.5;
84
85
/**
86
* The agent mode prompt, rendered on each request
87
*/
88
export class AgentPrompt extends PromptElement<AgentPromptProps> {
89
constructor(
90
props: AgentPromptProps,
91
@IConfigurationService private readonly configurationService: IConfigurationService,
92
@IInstantiationService private readonly instantiationService: IInstantiationService,
93
@IExperimentationService private readonly experimentationService: IExperimentationService,
94
@IPromptVariablesService private readonly promptVariablesService: IPromptVariablesService,
95
@IPromptEndpoint private readonly promptEndpoint: IPromptEndpoint,
96
) {
97
super(props);
98
}
99
100
async render(state: void, sizing: PromptSizing) {
101
const customizations = this.props.customizations;
102
if (!customizations) {
103
throw new Error('AgentPrompt requires customizations to be provided. Use PromptRegistry.resolveAllCustomizations() to resolve them.');
104
}
105
const instructions = await this.getSystemPrompt(customizations);
106
const CopilotIdentityRules = customizations.CopilotIdentityRulesClass;
107
const SafetyRules = customizations.SafetyRulesClass;
108
109
const omitBaseAgentInstructions = this.configurationService.getConfig(ConfigKey.Advanced.OmitBaseAgentInstructions);
110
const baseAgentInstructions = <>
111
<SystemMessage>
112
You are an expert AI programming assistant, working with a user in the VS Code editor.<br />
113
<CopilotIdentityRules />
114
<SafetyRules />
115
</SystemMessage>
116
{instructions}
117
<SystemMessage>
118
<MemoryInstructionsPrompt />
119
</SystemMessage>
120
</>;
121
const isAutopilot = this.props.promptContext.request?.permissionLevel === 'autopilot';
122
const sessionResource = this.props.promptContext.request?.sessionResource;
123
const sessionId = sessionResource ? sessionResourceToId(sessionResource) : undefined;
124
const debugTargetSessionIds = extractDebugTargetSessionIds([...this.props.promptContext.chatVariables].map(v => v.reference));
125
const templateVariablesContext = this.promptVariablesService.buildTemplateVariablesContext(sessionId, debugTargetSessionIds);
126
const baseInstructions = <>
127
{!omitBaseAgentInstructions && baseAgentInstructions}
128
{await this.getAgentCustomInstructions()}
129
{isAutopilot && <SystemMessage priority={80}>
130
When you have fully completed the task, call the task_complete tool to signal that you are done.<br />
131
IMPORTANT: Before calling task_complete, you MUST provide a brief text summary of what was accomplished in your message. The task is not complete until both the summary and the task_complete call are present.
132
</SystemMessage>}
133
{templateVariablesContext.length > 0 && <SystemMessage>{templateVariablesContext}</SystemMessage>}
134
<UserMessage>
135
{await this.getOrCreateGlobalAgentContext(this.props.endpoint)}
136
</UserMessage>
137
</>;
138
139
const maxToolResultLength = Math.floor(this.promptEndpoint.modelMaxPromptTokens * MAX_TOOL_RESPONSE_PCT);
140
const userQueryTagName = customizations.userQueryTagName;
141
const ReminderInstructionsClass = customizations.ReminderInstructionsClass;
142
const ToolReferencesHintClass = customizations.ToolReferencesHintClass;
143
144
if (this.props.enableCacheBreakpoints) {
145
return <>
146
{baseInstructions}
147
<SummarizedConversationHistory
148
flexGrow={1}
149
triggerSummarize={this.props.triggerSummarize}
150
forceSimpleSummary={this.props.forceSimpleSummary}
151
priority={900}
152
promptContext={this.props.promptContext}
153
location={this.props.location}
154
maxToolResultLength={maxToolResultLength}
155
endpoint={this.props.endpoint}
156
tools={this.props.promptContext.tools?.availableTools}
157
enableCacheBreakpoints={this.props.enableCacheBreakpoints}
158
userQueryTagName={userQueryTagName}
159
ReminderInstructionsClass={ReminderInstructionsClass}
160
ToolReferencesHintClass={ToolReferencesHintClass}
161
/>
162
</>;
163
} else {
164
return <>
165
{baseInstructions}
166
<AgentConversationHistory flexGrow={1} priority={700} promptContext={this.props.promptContext} />
167
<AgentUserMessage flexGrow={2} priority={900} {...getUserMessagePropsFromAgentProps(this.props, { userQueryTagName, ReminderInstructionsClass, ToolReferencesHintClass })} />
168
<ChatToolCalls priority={899} flexGrow={2} promptContext={this.props.promptContext} toolCallRounds={this.props.promptContext.toolCallRounds} toolCallResults={this.props.promptContext.toolCallResults} truncateAt={maxToolResultLength} enableCacheBreakpoints={false} />
169
</>;
170
}
171
}
172
173
private async getSystemPrompt(customizations: AgentPromptCustomizations) {
174
const modelFamily = this.props.endpoint.family ?? 'unknown';
175
176
if (this.props.endpoint.family.startsWith('gpt-') && this.configurationService.getExperimentBasedConfig(ConfigKey.EnableAlternateGptPrompt, this.experimentationService)) {
177
return <AlternateGPTPrompt
178
availableTools={this.props.promptContext.tools?.availableTools}
179
modelFamily={this.props.endpoint.family}
180
codesearchMode={this.props.codesearchMode}
181
/>;
182
}
183
184
const PromptClass = customizations.SystemPrompt!;
185
return <PromptClass
186
availableTools={this.props.promptContext.tools?.availableTools}
187
modelFamily={modelFamily}
188
codesearchMode={this.props.codesearchMode}
189
/>;
190
}
191
192
private async getAgentCustomInstructions() {
193
const putCustomInstructionsInSystemMessage = this.configurationService.getConfig(ConfigKey.CustomInstructionsInSystemMessage);
194
const customInstructionsBodyParts: PromptPiece[] = [];
195
customInstructionsBodyParts.push(
196
<CustomInstructions
197
languageId={undefined}
198
chatVariables={this.props.promptContext.chatVariables}
199
includeSystemMessageConflictWarning={!putCustomInstructionsInSystemMessage}
200
customIntroduction={putCustomInstructionsInSystemMessage ? '' : undefined} // If in system message, skip the "follow these user-provided coding instructions" intro
201
/>
202
);
203
if (this.props.promptContext.modeInstructions) {
204
const { name, content, toolReferences } = this.props.promptContext.modeInstructions;
205
const resolvedContent = toolReferences && toolReferences.length > 0 ? await this.promptVariablesService.resolveToolReferencesInPrompt(content, toolReferences) : content;
206
207
customInstructionsBodyParts.push(
208
<Tag name='modeInstructions'>
209
You are currently running in "{name}" mode. Below are your instructions for this mode, they must take precedence over any instructions above.<br />
210
<br />
211
{resolvedContent}
212
</Tag>
213
);
214
}
215
return putCustomInstructionsInSystemMessage ?
216
<SystemMessage>{customInstructionsBodyParts}</SystemMessage> :
217
<UserMessage>{customInstructionsBodyParts}</UserMessage>;
218
}
219
220
private async getOrCreateGlobalAgentContext(endpoint: IChatEndpoint): Promise<PromptPieceChild[]> {
221
const globalContext = await this.getOrCreateGlobalAgentContextContent(endpoint);
222
const isNewChat = this.props.promptContext.history?.length === 0;
223
// TODO:@bhavyau find a better way to extract session resource
224
const sessionResource = (this.props.promptContext.tools?.toolInvocationToken as any)?.sessionResource as string | undefined;
225
const result = globalContext ?
226
renderedMessageToTsxChildren(globalContext, !!this.props.enableCacheBreakpoints) :
227
<GlobalAgentContext enableCacheBreakpoints={!!this.props.enableCacheBreakpoints} availableTools={this.props.promptContext.tools?.availableTools} isNewChat={isNewChat} sessionResource={sessionResource} />;
228
229
return result;
230
}
231
232
private async getOrCreateGlobalAgentContextContent(endpoint: IChatEndpoint): Promise<Raw.ChatCompletionContentPart[] | undefined> {
233
const firstTurn = this.props.promptContext.conversation?.turns.at(0);
234
if (firstTurn) {
235
const metadata = firstTurn.getMetadata(GlobalContextMessageMetadata);
236
if (metadata) {
237
const currentCacheKey = this.instantiationService.invokeFunction(getGlobalContextCacheKey);
238
if (metadata.cacheKey === currentCacheKey) {
239
return metadata.renderedGlobalContext;
240
}
241
}
242
}
243
244
const isNewChat = this.props.promptContext.history?.length === 0;
245
// TODO:@bhavyau find a better way to extract session resource
246
const sessionResource = (this.props.promptContext.tools?.toolInvocationToken as any)?.sessionResource as string | undefined;
247
const rendered = await renderPromptElement(this.instantiationService, endpoint, GlobalAgentContext, { enableCacheBreakpoints: this.props.enableCacheBreakpoints, availableTools: this.props.promptContext.tools?.availableTools, isNewChat, sessionResource }, undefined, undefined);
248
const msg = rendered.messages.at(0)?.content;
249
if (msg) {
250
firstTurn?.setMetadata(new GlobalContextMessageMetadata(msg, this.instantiationService.invokeFunction(getGlobalContextCacheKey)));
251
return msg;
252
}
253
}
254
}
255
256
interface GlobalAgentContextProps extends BasePromptElementProps {
257
readonly enableCacheBreakpoints?: boolean;
258
readonly availableTools?: readonly LanguageModelToolInformation[];
259
readonly isNewChat?: boolean;
260
readonly sessionResource?: string;
261
}
262
263
/**
264
* The "global agent context" is a static prompt at the start of a conversation containing user environment info, initial workspace structure, anything else that is a useful beginning
265
* hint for the agent but is not updated during the conversation.
266
*/
267
class GlobalAgentContext extends PromptElement<GlobalAgentContextProps> {
268
render() {
269
return <UserMessage>
270
<Tag name='environment_info'>
271
<UserOSPrompt />
272
</Tag>
273
<Tag name='workspace_info'>
274
<TokenLimit max={2000}>
275
<AgentTasksInstructions availableTools={this.props.availableTools} />
276
</TokenLimit>
277
<WorkspaceFoldersHint />
278
<AgentMultirootWorkspaceStructure maxSize={2000} excludeDotFiles={true} availableTools={this.props.availableTools} />
279
</Tag>
280
<UserPreferences flexGrow={7} priority={800} />
281
{this.props.isNewChat && <MemoryContextPrompt sessionResource={this.props.sessionResource} />}
282
<DeferredToolListReminder availableTools={this.props.availableTools} />
283
{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}
284
</UserMessage>;
285
}
286
}
287
288
export interface AgentUserMessageCustomizations {
289
/** Tag name used to wrap the user query (e.g., 'userRequest' or 'user_query') */
290
readonly userQueryTagName?: string;
291
/** Custom reminder instructions component class */
292
readonly ReminderInstructionsClass?: ReminderInstructionsConstructor;
293
/** Custom tool references hint component class */
294
readonly ToolReferencesHintClass?: ToolReferencesHintConstructor;
295
}
296
297
export interface AgentUserMessageProps extends BasePromptElementProps, AgentUserMessageCustomizations {
298
readonly turn?: Turn;
299
readonly isHistorical?: boolean;
300
readonly request: string;
301
readonly endpoint: IChatEndpoint;
302
readonly toolReferences: readonly InternalToolReference[];
303
readonly availableTools?: readonly LanguageModelToolInformation[];
304
readonly chatVariables: ChatVariablesCollection;
305
readonly enableCacheBreakpoints?: boolean;
306
readonly editedFileEvents?: readonly ChatRequestEditedFileEvent[];
307
readonly sessionId?: string;
308
readonly sessionResource?: string;
309
/** When true, indicates this is a stop hook continuation where the stop hook query is rendered as a separate message. */
310
readonly hasStopHookQuery?: boolean;
311
/** Additional context provided by SubagentStart hooks. */
312
readonly additionalHookContext?: string;
313
/** When true, this request was system-initiated (e.g. terminal completion notification) and should skip context/wrapping. */
314
readonly isSystemInitiated?: boolean;
315
}
316
317
export function getUserMessagePropsFromTurn(turn: Turn, endpoint: IChatEndpoint, customizations?: AgentUserMessageCustomizations): AgentUserMessageProps {
318
return {
319
isHistorical: true,
320
request: turn.request.message,
321
turn,
322
endpoint,
323
toolReferences: turn.toolReferences,
324
chatVariables: turn.promptVariables ?? new ChatVariablesCollection(),
325
editedFileEvents: turn.editedFileEvents,
326
enableCacheBreakpoints: false, // Should only be added to the current turn - some user messages may get them in Agent post-processing
327
...customizations,
328
};
329
}
330
331
export function getUserMessagePropsFromAgentProps(agentProps: AgentPromptProps, customizations?: AgentUserMessageCustomizations): AgentUserMessageProps {
332
return {
333
request: agentProps.promptContext.query,
334
// Will pull frozenContent off the Turn if available
335
turn: agentProps.promptContext.conversation?.getLatestTurn(),
336
endpoint: agentProps.endpoint,
337
toolReferences: agentProps.promptContext.tools?.toolReferences ?? [],
338
availableTools: agentProps.promptContext.tools?.availableTools,
339
chatVariables: agentProps.promptContext.chatVariables,
340
enableCacheBreakpoints: agentProps.enableCacheBreakpoints,
341
editedFileEvents: agentProps.promptContext.editedFileEvents,
342
hasStopHookQuery: agentProps.promptContext.hasStopHookQuery,
343
additionalHookContext: agentProps.promptContext.additionalHookContext,
344
isSystemInitiated: agentProps.promptContext.request?.isSystemInitiated,
345
// TODO:@roblourens
346
sessionId: (agentProps.promptContext.tools?.toolInvocationToken as any)?.sessionId,
347
sessionResource: (agentProps.promptContext.tools?.toolInvocationToken as any)?.sessionResource,
348
...customizations,
349
};
350
}
351
352
/**
353
* Is sent with each user message. Includes the user message and also any ambient context that we want to update with each request.
354
* Uses frozen content if available, for prompt caching and to avoid being updated by any agent action below this point in the conversation.
355
*/
356
export class AgentUserMessage extends PromptElement<AgentUserMessageProps> {
357
constructor(
358
props: AgentUserMessageProps,
359
@IPromptVariablesService private readonly promptVariablesService: IPromptVariablesService,
360
@ILogService private readonly logService: ILogService,
361
@IConfigurationService private readonly configurationService: IConfigurationService
362
) {
363
super(props);
364
}
365
366
async render(state: void, sizing: PromptSizing) {
367
const frozenContent = this.props.turn?.getMetadata(RenderedUserMessageMetadata)?.renderedUserMessage;
368
if (frozenContent) {
369
return <FrozenContentUserMessage frozenContent={frozenContent} enableCacheBreakpoints={this.props.enableCacheBreakpoints} />;
370
}
371
372
if (this.props.isHistorical) {
373
this.logService.trace('Re-rendering historical user message');
374
}
375
376
// System-initiated messages (e.g. terminal completion notifications) are
377
// self-contained and should not be wrapped in <userRequest> or have context re-added.
378
if (this.props.isSystemInitiated) {
379
return <UserMessage>{this.props.request}</UserMessage>;
380
}
381
382
const query = await this.promptVariablesService.resolveToolReferencesInPrompt(this.props.request, this.props.toolReferences ?? []);
383
const hasReplaceStringTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ReplaceString);
384
const hasMultiReplaceStringTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.MultiReplaceString);
385
const hasApplyPatchTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ApplyPatch);
386
const hasCreateFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CreateFile);
387
const hasEditFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditFile);
388
const hasEditNotebookTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditNotebook);
389
const hasTerminalTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreRunInTerminal);
390
const hasToolsToEditNotebook = hasCreateFileTool || hasEditNotebookTool || hasReplaceStringTool || hasApplyPatchTool || hasEditFileTool;
391
const hasTodoTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreManageTodoList);
392
393
const userQueryTagName = this.props.userQueryTagName ?? 'userRequest';
394
const ReminderInstructionsClass = this.props.ReminderInstructionsClass ?? DefaultReminderInstructions;
395
const reminderProps: ReminderInstructionsProps = {
396
endpoint: this.props.endpoint,
397
hasTodoTool,
398
hasEditFileTool,
399
hasReplaceStringTool,
400
hasMultiReplaceStringTool,
401
};
402
const ToolReferencesHintClass = this.props.ToolReferencesHintClass ?? DefaultToolReferencesHint;
403
const toolReferencesHintProps: ToolReferencesHintProps = {
404
toolReferences: this.props.toolReferences,
405
};
406
407
return (
408
<>
409
<UserMessage>
410
{hasToolsToEditNotebook && <NotebookFormat flexGrow={5} priority={810} chatVariables={this.props.chatVariables} query={query} />}
411
<TokenLimit max={sizing.tokenBudget / 6} flexGrow={3} priority={898}>
412
<ChatVariables chatVariables={this.props.chatVariables} isAgent={true} omitReferences />
413
</TokenLimit>
414
<ToolReferencesHintClass {...toolReferencesHintProps} />
415
<Tag name='context'>
416
<CurrentDatePrompt />
417
<EditedFileEvents editedFileEvents={this.props.editedFileEvents} />
418
<NotebookSummaryChange />
419
{hasTerminalTool && <TerminalStatePromptElement sessionId={this.props.sessionId} />}
420
{hasTodoTool && <TodoListContextPrompt sessionResource={this.props.sessionResource} />}
421
{this.props.additionalHookContext && <AdditionalHookContextPrompt context={this.props.additionalHookContext} />}
422
</Tag>
423
<CurrentEditorContext endpoint={this.props.endpoint} />
424
<Tag name='reminderInstructions'>
425
{/* Critical reminders that are effective when repeated right next to the user message */}
426
<ReminderInstructionsClass {...reminderProps} />
427
<NotebookReminderInstructions chatVariables={this.props.chatVariables} query={this.props.request} />
428
{this.configurationService.getNonExtensionConfig<boolean>(USE_SKILL_ADHERENCE_PROMPT_SETTING) && <SkillAdherenceReminder chatVariables={this.props.chatVariables} />}
429
</Tag>
430
{query && <Tag name={userQueryTagName} priority={900} flexGrow={7}>
431
<UserQuery chatVariables={this.props.chatVariables} query={query} />
432
</Tag>}
433
{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}
434
</UserMessage>
435
</>
436
);
437
}
438
}
439
440
interface FrozenMessageContentProps extends BasePromptElementProps {
441
readonly frozenContent: readonly Raw.ChatCompletionContentPart[];
442
readonly enableCacheBreakpoints?: boolean;
443
}
444
445
class FrozenContentUserMessage extends PromptElement<FrozenMessageContentProps> {
446
async render(state: void, sizing: PromptSizing) {
447
return <UserMessage priority={this.props.priority}>
448
<Chunk>
449
{/* Have to move <cacheBreakpoint> out of the Chunk */}
450
{renderedMessageToTsxChildren(this.props.frozenContent, false)}
451
</Chunk>
452
{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}
453
</UserMessage>;
454
}
455
}
456
457
export function renderedMessageToTsxChildren(message: string | readonly Raw.ChatCompletionContentPart[], enableCacheBreakpoints: boolean): PromptPieceChild[] {
458
if (typeof message === 'string') {
459
return [message];
460
}
461
462
return message.map(part => {
463
if (part.type === Raw.ChatCompletionContentPartKind.Text) {
464
return part.text;
465
} else if (part.type === Raw.ChatCompletionContentPartKind.Image) {
466
return <HistoricalImage src={part.imageUrl.url} detail={part.imageUrl.detail} mimeType={part.imageUrl.mediaType} />;
467
} else if (part.type === Raw.ChatCompletionContentPartKind.Document) {
468
return <Document data={part.documentData.data} mediaType={part.documentData.mediaType} />;
469
} else if (part.type === Raw.ChatCompletionContentPartKind.CacheBreakpoint) {
470
return enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />;
471
}
472
}).filter(isDefined);
473
}
474
475
class UserOSPrompt extends PromptElement<BasePromptElementProps> {
476
constructor(props: BasePromptElementProps, @IEnvService private readonly envService: IEnvService) {
477
super(props);
478
}
479
480
async render(state: void, sizing: PromptSizing) {
481
const userOS = this.envService.OS;
482
const osForDisplay = userOS === OperatingSystem.Macintosh ? 'macOS' :
483
userOS;
484
return <>The user's current OS is: {osForDisplay}</>;
485
}
486
}
487
488
class CurrentDatePrompt extends PromptElement<BasePromptElementProps> {
489
constructor(
490
props: BasePromptElementProps,
491
@IEnvService private readonly envService: IEnvService) {
492
super(props);
493
}
494
495
async render(state: void, sizing: PromptSizing) {
496
const dateStr = new Date().toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
497
// Only include current date when not running simulations, since if we generate cache entries with the current date, the cache will be invalidated every day
498
return (
499
!this.envService.isSimulation() && <>The current date is {dateStr}.</>
500
);
501
}
502
}
503
504
interface AdditionalHookContextPromptProps extends BasePromptElementProps {
505
readonly context: string;
506
}
507
508
/**
509
* Renders additional context provided by hooks.
510
*/
511
class AdditionalHookContextPrompt extends PromptElement<AdditionalHookContextPromptProps> {
512
async render(state: void, sizing: PromptSizing) {
513
return <>Additional instructions from hooks: {this.props.context}</>;
514
}
515
}
516
517
interface SkillAdherenceReminderProps extends BasePromptElementProps {
518
readonly chatVariables: ChatVariablesCollection;
519
}
520
521
/**
522
* Skill adherence reminder that prompts the model to read SKILL.md files when skills are available
523
* in the instruction index.
524
* Shown whenever the instruction index variable contains at least one skill or skill folder entry.
525
*/
526
class SkillAdherenceReminder extends PromptElement<SkillAdherenceReminderProps> {
527
constructor(
528
props: SkillAdherenceReminderProps,
529
@ICustomInstructionsService private readonly customInstructionsService: ICustomInstructionsService,
530
@IConfigurationService private readonly configurationService: IConfigurationService,
531
@IExperimentationService private readonly experimentationService: IExperimentationService,
532
) {
533
super(props);
534
}
535
536
async render() {
537
// Check if any skills are available from the instruction index
538
const indexVariable = this.props.chatVariables.find(isCustomizationsIndex);
539
if (!indexVariable || !isString(indexVariable.value)) {
540
return undefined;
541
}
542
543
const indexFile = this.customInstructionsService.parseInstructionIndexFile(indexVariable.value);
544
if (indexFile.skills.size === 0) {
545
return undefined;
546
}
547
548
const skillToolEnabled = this.configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SkillToolEnabled, this.experimentationService);
549
550
if (skillToolEnabled) {
551
return <Tag name='additional_skills_reminder'>
552
Always check if any skills apply to the user's request. If so, use the {ToolName.Skill} tool to invoke the skill by name. Multiple skill files may be needed for a single request. These files contain best practices built from testing that are needed for high-quality outputs.<br />
553
</Tag>;
554
}
555
556
return <Tag name='additional_skills_reminder'>
557
Always check if any skills apply to the user's request. If so, use the {ToolName.ReadFile} tool to read the corresponding SKILL.md files. Multiple skill files may be needed for a single request. These files contain best practices built from testing that are needed for high-quality outputs.<br />
558
</Tag>;
559
}
560
}
561
562
interface CurrentEditorContextProps extends BasePromptElementProps {
563
readonly endpoint: IChatEndpoint;
564
}
565
566
/**
567
* Include the user's open editor and cursor position, but not content. This is independent of the "implicit context" attachment.
568
*/
569
class CurrentEditorContext extends PromptElement<CurrentEditorContextProps> {
570
constructor(
571
props: CurrentEditorContextProps,
572
@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,
573
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
574
@IConfigurationService private readonly configurationService: IConfigurationService,
575
@IAlternativeNotebookContentService private readonly alternativeNotebookContent: IAlternativeNotebookContentService,
576
) {
577
super(props);
578
}
579
580
async render(state: void, sizing: PromptSizing) {
581
if (!this.configurationService.getConfig(ConfigKey.CurrentEditorAgentContext)) {
582
return;
583
}
584
585
let context: PromptElement | undefined;
586
const activeEditor = this.tabsAndEditorsService.activeTextEditor;
587
if (activeEditor) {
588
context = this.renderActiveTextEditor(activeEditor);
589
}
590
591
const activeNotebookEditor = this.tabsAndEditorsService.activeNotebookEditor;
592
if (activeNotebookEditor) {
593
context = this.renderActiveNotebookEditor(activeNotebookEditor);
594
}
595
596
if (!context) {
597
return;
598
}
599
600
return <Tag name='editorContext'>
601
{context}
602
</Tag>;
603
}
604
605
private renderActiveTextEditor(activeEditor: TextEditor) {
606
// Should this include column numbers too? This confused gpt-4.1 and it read the wrong line numbers, need to find the right format.
607
const selection = activeEditor.selection;
608
// Found that selection is not always defined, so check for it.
609
const selectionText = (selection && !selection.isEmpty) ?
610
<>The current selection is from line {selection.start.line + 1} to line {selection.end.line + 1}.</> : undefined;
611
return <>The user's current file is {this.promptPathRepresentationService.getFilePath(activeEditor.document.uri)}. {selectionText}</>;
612
}
613
614
private renderActiveNotebookEditor(activeNotebookEditor: NotebookEditor) {
615
const altDocument = this.alternativeNotebookContent.create(this.alternativeNotebookContent.getFormat(this.props.endpoint)).getAlternativeDocument(activeNotebookEditor.notebook);
616
let selectionText = '';
617
// Found that selection is not always defined, so check for it.
618
if (activeNotebookEditor.selection && !activeNotebookEditor.selection.isEmpty && activeNotebookEditor.notebook.cellCount > 0) {
619
// Compute a list of all cells that fall in the range of selection.start and selection.end
620
const { start, end } = activeNotebookEditor.selection;
621
const cellsInRange = [];
622
for (let i = start; i < end; i++) {
623
const cell = activeNotebookEditor.notebook.cellAt(i);
624
if (cell) {
625
cellsInRange.push(cell);
626
}
627
}
628
const startCell = cellsInRange[0];
629
const endCell = cellsInRange[cellsInRange.length - 1];
630
const lastLine = endCell.document.lineAt(endCell.document.lineCount - 1);
631
const startPosition = altDocument.fromCellPosition(startCell, new Position(0, 0));
632
const endPosition = altDocument.fromCellPosition(endCell, new Position(endCell.document.lineCount - 1, lastLine.text.length));
633
const selection = new Range(startPosition, endPosition);
634
selectionText = selection ? ` The current selection is from line ${selection.start.line + 1} to line ${selection.end.line + 1}.` : '';
635
}
636
return <>The user's current notebook is {this.promptPathRepresentationService.getFilePath(activeNotebookEditor.notebook.uri)}.{selectionText}</>;
637
}
638
}
639
640
class WorkspaceFoldersHint extends PromptElement<BasePromptElementProps> {
641
constructor(
642
props: BasePromptElementProps,
643
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
644
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
645
) {
646
super(props);
647
}
648
649
async render(state: void, sizing: PromptSizing) {
650
const folders = this.workspaceService.getWorkspaceFolders();
651
if (folders.length > 0) {
652
return (
653
<>
654
I am working in a workspace with the following folders:<br />
655
{folders.map(folder => `- ${this.promptPathRepresentationService.getFilePath(folder)} `).join('\n')}
656
</>);
657
} else {
658
return <>There is no workspace currently open.</>;
659
}
660
}
661
}
662
663
664
interface AgentTasksInstructionsProps extends BasePromptElementProps {
665
readonly availableTools?: readonly LanguageModelToolInformation[];
666
}
667
668
export class AgentTasksInstructions extends PromptElement<AgentTasksInstructionsProps> {
669
constructor(
670
props: AgentTasksInstructionsProps,
671
@ITasksService private readonly _tasksService: ITasksService,
672
@IPromptPathRepresentationService private readonly _promptPathRepresentationService: IPromptPathRepresentationService,
673
@IIgnoreService private readonly _ignoreService: IIgnoreService,
674
) {
675
super(props);
676
}
677
678
async render() {
679
const foundEnabledTaskTool = this.props.availableTools?.find(t => t.name === ToolName.CoreRunTask || t.name === ToolName.CoreCreateAndRunTask || t.name === ToolName.CoreGetTaskOutput);
680
if (!foundEnabledTaskTool) {
681
return 0;
682
}
683
684
const taskGroupsRaw = this._tasksService.getTasks();
685
const taskGroups = (await Promise.all(taskGroupsRaw.map(async ([folder, tasks]) => {
686
const tasksFile = URI.joinPath(folder, '.vscode', 'tasks.json');
687
if (await this._ignoreService.isCopilotIgnored(tasksFile)) {
688
return undefined;
689
}
690
const visibleTasks = tasks.filter(task => (!!task.type || task.dependsOn) && !task.hide);
691
return visibleTasks.length > 0 ? [folder, visibleTasks] as const : undefined;
692
}))).filter(isDefined);
693
if (taskGroups.length === 0) {
694
return 0;
695
}
696
697
return <>
698
The following tasks can be executed using the {ToolName.CoreRunTask} tool if they are not already running:<br />
699
{taskGroups.map(([folder, tasks]) =>
700
<Tag name='workspaceFolder' attrs={{ path: this._promptPathRepresentationService.getFilePath(folder) }}>
701
{tasks.map((t, i) => {
702
const isActive = this._tasksService.isTaskActive(t);
703
return (
704
<Tag name='task' attrs={{ id: t.type ? `${t.type}: ${t.label || i}` : `${t.label || i}` }}>
705
{this.makeTaskPresentation(t)}
706
{isActive && <> (This task is currently running. You can use the {ToolName.CoreGetTaskOutput} tool to view its output.)</>}
707
</Tag>
708
);
709
})}
710
</Tag>
711
)}
712
</>;
713
}
714
715
/** Makes a simplified JSON presentation of the task definition for the model to reference. */
716
private makeTaskPresentation(task: TaskDefinition) {
717
const enum PlatformAttr {
718
Windows = 'windows',
719
Mac = 'osx',
720
Linux = 'linux'
721
}
722
723
const omitAttrs = ['presentation', 'problemMatcher', PlatformAttr.Windows, PlatformAttr.Mac, PlatformAttr.Linux];
724
725
const output: Record<string, unknown> = {};
726
for (const [key, value] of Object.entries(task)) {
727
if (!omitAttrs.includes(key)) {
728
output[key] = value;
729
}
730
}
731
732
733
const myPlatformAttr = process.platform === 'win32' ? PlatformAttr.Windows :
734
process.platform === 'darwin' ? PlatformAttr.Mac :
735
PlatformAttr.Linux;
736
if (task[myPlatformAttr] && typeof task[myPlatformAttr] === 'object') {
737
Object.assign(output, task[myPlatformAttr]);
738
}
739
740
return JSON.stringify(output, null, '\t');
741
}
742
}
743
744
export interface EditedFileEventsProps extends BasePromptElementProps {
745
readonly editedFileEvents: readonly ChatRequestEditedFileEvent[] | undefined;
746
}
747
748
/**
749
* Context about manual edits made to files that the agent previously edited.
750
*/
751
export class EditedFileEvents extends PromptElement<EditedFileEventsProps> {
752
constructor(
753
props: EditedFileEventsProps,
754
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
755
) {
756
super(props);
757
}
758
759
async render(state: void, sizing: PromptSizing) {
760
const events = this.props.editedFileEvents;
761
762
if (!events || events.length === 0) {
763
return undefined;
764
}
765
766
// Group by event kind and collect file paths
767
const undoFiles: string[] = [];
768
const modFiles: string[] = [];
769
const seenUndo = new Set<string>();
770
const seenMod = new Set<string>();
771
772
for (const event of events) {
773
if (event.eventKind === ChatRequestEditedFileEventKind.Undo) {
774
const fp = this.promptPathRepresentationService.getFilePath(event.uri);
775
if (!seenUndo.has(fp)) { seenUndo.add(fp); undoFiles.push(fp); }
776
} else if (event.eventKind === ChatRequestEditedFileEventKind.UserModification) {
777
const fp = this.promptPathRepresentationService.getFilePath(event.uri);
778
if (!seenMod.has(fp)) { seenMod.add(fp); modFiles.push(fp); }
779
}
780
}
781
782
if (undoFiles.length === 0 && modFiles.length === 0) {
783
return undefined;
784
}
785
786
const sections: string[] = [];
787
if (undoFiles.length > 0) {
788
sections.push([
789
'The user undid your edits to:',
790
...undoFiles.map(f => `- ${f}`)
791
].join('\n'));
792
}
793
if (modFiles.length > 0) {
794
sections.push([
795
'Some edits were made, by the user or possibly by a formatter or another automated tool, to:',
796
...modFiles.map(f => `- ${f}`)
797
].join('\n'));
798
}
799
800
return (
801
<>
802
There have been some changes between the last request and now.<br />
803
{sections.join('\n')}<br />
804
So be sure to check the current file contents before making any new edits.
805
</>
806
);
807
}
808
}
809
810