Path: blob/main/extensions/copilot/src/extension/prompts/node/agent/agentPrompt.tsx
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BasePromptElementProps, Chunk, Document, PromptElement, PromptPiece, PromptPieceChild, PromptSizing, Raw, SystemMessage, TokenLimit, UserMessage } from '@vscode/prompt-tsx';6import type { ChatRequestEditedFileEvent, LanguageModelToolInformation, NotebookEditor, TaskDefinition, TextEditor } from 'vscode';7import { sessionResourceToId } from '../../../../platform/chat/common/chatDebugFileLoggerService';8import { ChatLocation } from '../../../../platform/chat/common/commonTypes';9import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';10import { ICustomInstructionsService } from '../../../../platform/customInstructions/common/customInstructionsService';11import { USE_SKILL_ADHERENCE_PROMPT_SETTING } from '../../../../platform/customInstructions/common/promptTypes';12import { CacheType } from '../../../../platform/endpoint/common/endpointTypes';13import { IEnvService, OperatingSystem } from '../../../../platform/env/common/envService';14import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';15import { ILogService } from '../../../../platform/log/common/logService';16import { IChatEndpoint } from '../../../../platform/networking/common/networking';17import { IAlternativeNotebookContentService } from '../../../../platform/notebook/common/alternativeContent';18import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';19import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';20import { ITasksService } from '../../../../platform/tasks/common/tasksService';21import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';22import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';23import { isDefined, isString } from '../../../../util/vs/base/common/types';24import { URI } from '../../../../util/vs/base/common/uri';25import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';26import { ChatRequestEditedFileEventKind, Position, Range } from '../../../../vscodeTypes';27import { GenericBasePromptElementProps } from '../../../context/node/resolvers/genericPanelIntentInvocation';28import { ChatVariablesCollection, extractDebugTargetSessionIds, isCustomizationsIndex } from '../../../prompt/common/chatVariablesCollection';29import { getGlobalContextCacheKey, GlobalContextMessageMetadata, RenderedUserMessageMetadata, Turn } from '../../../prompt/common/conversation';30import { InternalToolReference } from '../../../prompt/common/intents';31import { IPromptVariablesService } from '../../../prompt/node/promptVariablesService';32import { ToolName } from '../../../tools/common/toolNames';33import { MemoryContextPrompt, MemoryInstructionsPrompt } from '../../../tools/node/memoryContextPrompt';34import { TodoListContextPrompt } from '../../../tools/node/todoListContextPrompt';35import { IPromptEndpoint, renderPromptElement } from '../base/promptRenderer';36import { Tag } from '../base/tag';37import { TerminalStatePromptElement } from '../base/terminalState';38import { ChatVariables, UserQuery } from '../panel/chatVariables';39import { CustomInstructions } from '../panel/customInstructions';40import { HistoricalImage } from '../panel/image';41import { NotebookFormat, NotebookReminderInstructions } from '../panel/notebookEditCodePrompt';42import { NotebookSummaryChange } from '../panel/notebookSummaryChangePrompt';43import { UserPreferences } from '../panel/preferences';44import { ChatToolCalls } from '../panel/toolCalling';45import { AgentMultirootWorkspaceStructure } from '../panel/workspace/workspaceStructure';46import { AgentConversationHistory } from './agentConversationHistory';47import './allAgentPrompts';48import { AlternateGPTPrompt, DefaultReminderInstructions, DefaultToolReferencesHint, ReminderInstructionsProps, ToolReferencesHintProps } from './defaultAgentInstructions';49import { AgentPromptCustomizations, ReminderInstructionsConstructor, ToolReferencesHintConstructor } from './promptRegistry';50import { SummarizedConversationHistory } from './summarizedConversationHistory';51import { DeferredToolListReminder } from './toolSearchInstructions';5253export interface AgentPromptProps extends GenericBasePromptElementProps {54readonly endpoint: IChatEndpoint;55readonly location: ChatLocation;5657readonly triggerSummarize?: boolean;5859/**60* Enables cache breakpoints and summarization61*/62readonly enableCacheBreakpoints?: boolean;6364/**65* Codesearch mode, aka agentic Ask mode66*/67readonly codesearchMode?: boolean;6869/**70* All resolved customizations from the prompt registry.71*/72readonly customizations?: AgentPromptCustomizations;7374/**75* Prefer Simple mode for summarization, typically for the budget-exceeded recovery path.76* An explicit summarization mode configuration can still force Full mode.77*/78readonly forceSimpleSummary?: boolean;79}8081/** Proportion of the prompt token budget any singular textual tool result is allowed to use. */82const MAX_TOOL_RESPONSE_PCT = 0.5;8384/**85* The agent mode prompt, rendered on each request86*/87export class AgentPrompt extends PromptElement<AgentPromptProps> {88constructor(89props: AgentPromptProps,90@IConfigurationService private readonly configurationService: IConfigurationService,91@IInstantiationService private readonly instantiationService: IInstantiationService,92@IExperimentationService private readonly experimentationService: IExperimentationService,93@IPromptVariablesService private readonly promptVariablesService: IPromptVariablesService,94@IPromptEndpoint private readonly promptEndpoint: IPromptEndpoint,95) {96super(props);97}9899async render(state: void, sizing: PromptSizing) {100const customizations = this.props.customizations;101if (!customizations) {102throw new Error('AgentPrompt requires customizations to be provided. Use PromptRegistry.resolveAllCustomizations() to resolve them.');103}104const instructions = await this.getSystemPrompt(customizations);105const CopilotIdentityRules = customizations.CopilotIdentityRulesClass;106const SafetyRules = customizations.SafetyRulesClass;107108const omitBaseAgentInstructions = this.configurationService.getConfig(ConfigKey.Advanced.OmitBaseAgentInstructions);109const baseAgentInstructions = <>110<SystemMessage>111You are an expert AI programming assistant, working with a user in the VS Code editor.<br />112<CopilotIdentityRules />113<SafetyRules />114</SystemMessage>115{instructions}116<SystemMessage>117<MemoryInstructionsPrompt />118</SystemMessage>119</>;120const isAutopilot = this.props.promptContext.request?.permissionLevel === 'autopilot';121const sessionResource = this.props.promptContext.request?.sessionResource;122const sessionId = sessionResource ? sessionResourceToId(sessionResource) : undefined;123const debugTargetSessionIds = extractDebugTargetSessionIds([...this.props.promptContext.chatVariables].map(v => v.reference));124const templateVariablesContext = this.promptVariablesService.buildTemplateVariablesContext(sessionId, debugTargetSessionIds);125const baseInstructions = <>126{!omitBaseAgentInstructions && baseAgentInstructions}127{await this.getAgentCustomInstructions()}128{isAutopilot && <SystemMessage priority={80}>129When you have fully completed the task, call the task_complete tool to signal that you are done.<br />130IMPORTANT: 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.131</SystemMessage>}132{templateVariablesContext.length > 0 && <SystemMessage>{templateVariablesContext}</SystemMessage>}133<UserMessage>134{await this.getOrCreateGlobalAgentContext(this.props.endpoint)}135</UserMessage>136</>;137138const maxToolResultLength = Math.floor(this.promptEndpoint.modelMaxPromptTokens * MAX_TOOL_RESPONSE_PCT);139const userQueryTagName = customizations.userQueryTagName;140const ReminderInstructionsClass = customizations.ReminderInstructionsClass;141const ToolReferencesHintClass = customizations.ToolReferencesHintClass;142143if (this.props.enableCacheBreakpoints) {144return <>145{baseInstructions}146<SummarizedConversationHistory147flexGrow={1}148triggerSummarize={this.props.triggerSummarize}149forceSimpleSummary={this.props.forceSimpleSummary}150priority={900}151promptContext={this.props.promptContext}152location={this.props.location}153maxToolResultLength={maxToolResultLength}154endpoint={this.props.endpoint}155tools={this.props.promptContext.tools?.availableTools}156enableCacheBreakpoints={this.props.enableCacheBreakpoints}157userQueryTagName={userQueryTagName}158ReminderInstructionsClass={ReminderInstructionsClass}159ToolReferencesHintClass={ToolReferencesHintClass}160/>161</>;162} else {163return <>164{baseInstructions}165<AgentConversationHistory flexGrow={1} priority={700} promptContext={this.props.promptContext} />166<AgentUserMessage flexGrow={2} priority={900} {...getUserMessagePropsFromAgentProps(this.props, { userQueryTagName, ReminderInstructionsClass, ToolReferencesHintClass })} />167<ChatToolCalls priority={899} flexGrow={2} promptContext={this.props.promptContext} toolCallRounds={this.props.promptContext.toolCallRounds} toolCallResults={this.props.promptContext.toolCallResults} truncateAt={maxToolResultLength} enableCacheBreakpoints={false} />168</>;169}170}171172private async getSystemPrompt(customizations: AgentPromptCustomizations) {173const modelFamily = this.props.endpoint.family ?? 'unknown';174175if (this.props.endpoint.family.startsWith('gpt-') && this.configurationService.getExperimentBasedConfig(ConfigKey.EnableAlternateGptPrompt, this.experimentationService)) {176return <AlternateGPTPrompt177availableTools={this.props.promptContext.tools?.availableTools}178modelFamily={this.props.endpoint.family}179codesearchMode={this.props.codesearchMode}180/>;181}182183const PromptClass = customizations.SystemPrompt!;184return <PromptClass185availableTools={this.props.promptContext.tools?.availableTools}186modelFamily={modelFamily}187codesearchMode={this.props.codesearchMode}188/>;189}190191private async getAgentCustomInstructions() {192const putCustomInstructionsInSystemMessage = this.configurationService.getConfig(ConfigKey.CustomInstructionsInSystemMessage);193const customInstructionsBodyParts: PromptPiece[] = [];194customInstructionsBodyParts.push(195<CustomInstructions196languageId={undefined}197chatVariables={this.props.promptContext.chatVariables}198includeSystemMessageConflictWarning={!putCustomInstructionsInSystemMessage}199customIntroduction={putCustomInstructionsInSystemMessage ? '' : undefined} // If in system message, skip the "follow these user-provided coding instructions" intro200/>201);202if (this.props.promptContext.modeInstructions) {203const { name, content, toolReferences } = this.props.promptContext.modeInstructions;204const resolvedContent = toolReferences && toolReferences.length > 0 ? await this.promptVariablesService.resolveToolReferencesInPrompt(content, toolReferences) : content;205206customInstructionsBodyParts.push(207<Tag name='modeInstructions'>208You are currently running in "{name}" mode. Below are your instructions for this mode, they must take precedence over any instructions above.<br />209<br />210{resolvedContent}211</Tag>212);213}214return putCustomInstructionsInSystemMessage ?215<SystemMessage>{customInstructionsBodyParts}</SystemMessage> :216<UserMessage>{customInstructionsBodyParts}</UserMessage>;217}218219private async getOrCreateGlobalAgentContext(endpoint: IChatEndpoint): Promise<PromptPieceChild[]> {220const globalContext = await this.getOrCreateGlobalAgentContextContent(endpoint);221const isNewChat = this.props.promptContext.history?.length === 0;222// TODO:@bhavyau find a better way to extract session resource223const sessionResource = (this.props.promptContext.tools?.toolInvocationToken as any)?.sessionResource as string | undefined;224const result = globalContext ?225renderedMessageToTsxChildren(globalContext, !!this.props.enableCacheBreakpoints) :226<GlobalAgentContext enableCacheBreakpoints={!!this.props.enableCacheBreakpoints} availableTools={this.props.promptContext.tools?.availableTools} isNewChat={isNewChat} sessionResource={sessionResource} />;227228return result;229}230231private async getOrCreateGlobalAgentContextContent(endpoint: IChatEndpoint): Promise<Raw.ChatCompletionContentPart[] | undefined> {232const firstTurn = this.props.promptContext.conversation?.turns.at(0);233if (firstTurn) {234const metadata = firstTurn.getMetadata(GlobalContextMessageMetadata);235if (metadata) {236const currentCacheKey = this.instantiationService.invokeFunction(getGlobalContextCacheKey);237if (metadata.cacheKey === currentCacheKey) {238return metadata.renderedGlobalContext;239}240}241}242243const isNewChat = this.props.promptContext.history?.length === 0;244// TODO:@bhavyau find a better way to extract session resource245const sessionResource = (this.props.promptContext.tools?.toolInvocationToken as any)?.sessionResource as string | undefined;246const rendered = await renderPromptElement(this.instantiationService, endpoint, GlobalAgentContext, { enableCacheBreakpoints: this.props.enableCacheBreakpoints, availableTools: this.props.promptContext.tools?.availableTools, isNewChat, sessionResource }, undefined, undefined);247const msg = rendered.messages.at(0)?.content;248if (msg) {249firstTurn?.setMetadata(new GlobalContextMessageMetadata(msg, this.instantiationService.invokeFunction(getGlobalContextCacheKey)));250return msg;251}252}253}254255interface GlobalAgentContextProps extends BasePromptElementProps {256readonly enableCacheBreakpoints?: boolean;257readonly availableTools?: readonly LanguageModelToolInformation[];258readonly isNewChat?: boolean;259readonly sessionResource?: string;260}261262/**263* 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 beginning264* hint for the agent but is not updated during the conversation.265*/266class GlobalAgentContext extends PromptElement<GlobalAgentContextProps> {267render() {268return <UserMessage>269<Tag name='environment_info'>270<UserOSPrompt />271</Tag>272<Tag name='workspace_info'>273<TokenLimit max={2000}>274<AgentTasksInstructions availableTools={this.props.availableTools} />275</TokenLimit>276<WorkspaceFoldersHint />277<AgentMultirootWorkspaceStructure maxSize={2000} excludeDotFiles={true} availableTools={this.props.availableTools} />278</Tag>279<UserPreferences flexGrow={7} priority={800} />280{this.props.isNewChat && <MemoryContextPrompt sessionResource={this.props.sessionResource} />}281<DeferredToolListReminder availableTools={this.props.availableTools} />282{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}283</UserMessage>;284}285}286287export interface AgentUserMessageCustomizations {288/** Tag name used to wrap the user query (e.g., 'userRequest' or 'user_query') */289readonly userQueryTagName?: string;290/** Custom reminder instructions component class */291readonly ReminderInstructionsClass?: ReminderInstructionsConstructor;292/** Custom tool references hint component class */293readonly ToolReferencesHintClass?: ToolReferencesHintConstructor;294}295296export interface AgentUserMessageProps extends BasePromptElementProps, AgentUserMessageCustomizations {297readonly turn?: Turn;298readonly isHistorical?: boolean;299readonly request: string;300readonly endpoint: IChatEndpoint;301readonly toolReferences: readonly InternalToolReference[];302readonly availableTools?: readonly LanguageModelToolInformation[];303readonly chatVariables: ChatVariablesCollection;304readonly enableCacheBreakpoints?: boolean;305readonly editedFileEvents?: readonly ChatRequestEditedFileEvent[];306readonly sessionId?: string;307readonly sessionResource?: string;308/** When true, indicates this is a stop hook continuation where the stop hook query is rendered as a separate message. */309readonly hasStopHookQuery?: boolean;310/** Additional context provided by SubagentStart hooks. */311readonly additionalHookContext?: string;312/** When true, this request was system-initiated (e.g. terminal completion notification) and should skip context/wrapping. */313readonly isSystemInitiated?: boolean;314}315316export function getUserMessagePropsFromTurn(turn: Turn, endpoint: IChatEndpoint, customizations?: AgentUserMessageCustomizations): AgentUserMessageProps {317return {318isHistorical: true,319request: turn.request.message,320turn,321endpoint,322toolReferences: turn.toolReferences,323chatVariables: turn.promptVariables ?? new ChatVariablesCollection(),324editedFileEvents: turn.editedFileEvents,325enableCacheBreakpoints: false, // Should only be added to the current turn - some user messages may get them in Agent post-processing326...customizations,327};328}329330export function getUserMessagePropsFromAgentProps(agentProps: AgentPromptProps, customizations?: AgentUserMessageCustomizations): AgentUserMessageProps {331return {332request: agentProps.promptContext.query,333// Will pull frozenContent off the Turn if available334turn: agentProps.promptContext.conversation?.getLatestTurn(),335endpoint: agentProps.endpoint,336toolReferences: agentProps.promptContext.tools?.toolReferences ?? [],337availableTools: agentProps.promptContext.tools?.availableTools,338chatVariables: agentProps.promptContext.chatVariables,339enableCacheBreakpoints: agentProps.enableCacheBreakpoints,340editedFileEvents: agentProps.promptContext.editedFileEvents,341hasStopHookQuery: agentProps.promptContext.hasStopHookQuery,342additionalHookContext: agentProps.promptContext.additionalHookContext,343isSystemInitiated: agentProps.promptContext.request?.isSystemInitiated,344// TODO:@roblourens345sessionId: (agentProps.promptContext.tools?.toolInvocationToken as any)?.sessionId,346sessionResource: (agentProps.promptContext.tools?.toolInvocationToken as any)?.sessionResource,347...customizations,348};349}350351/**352* Is sent with each user message. Includes the user message and also any ambient context that we want to update with each request.353* Uses frozen content if available, for prompt caching and to avoid being updated by any agent action below this point in the conversation.354*/355export class AgentUserMessage extends PromptElement<AgentUserMessageProps> {356constructor(357props: AgentUserMessageProps,358@IPromptVariablesService private readonly promptVariablesService: IPromptVariablesService,359@ILogService private readonly logService: ILogService,360@IConfigurationService private readonly configurationService: IConfigurationService361) {362super(props);363}364365async render(state: void, sizing: PromptSizing) {366const frozenContent = this.props.turn?.getMetadata(RenderedUserMessageMetadata)?.renderedUserMessage;367if (frozenContent) {368return <FrozenContentUserMessage frozenContent={frozenContent} enableCacheBreakpoints={this.props.enableCacheBreakpoints} />;369}370371if (this.props.isHistorical) {372this.logService.trace('Re-rendering historical user message');373}374375// System-initiated messages (e.g. terminal completion notifications) are376// self-contained and should not be wrapped in <userRequest> or have context re-added.377if (this.props.isSystemInitiated) {378return <UserMessage>{this.props.request}</UserMessage>;379}380381const query = await this.promptVariablesService.resolveToolReferencesInPrompt(this.props.request, this.props.toolReferences ?? []);382const hasReplaceStringTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ReplaceString);383const hasMultiReplaceStringTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.MultiReplaceString);384const hasApplyPatchTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.ApplyPatch);385const hasCreateFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CreateFile);386const hasEditFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditFile);387const hasEditNotebookTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditNotebook);388const hasTerminalTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreRunInTerminal);389const hasToolsToEditNotebook = hasCreateFileTool || hasEditNotebookTool || hasReplaceStringTool || hasApplyPatchTool || hasEditFileTool;390const hasTodoTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreManageTodoList);391392const userQueryTagName = this.props.userQueryTagName ?? 'userRequest';393const ReminderInstructionsClass = this.props.ReminderInstructionsClass ?? DefaultReminderInstructions;394const reminderProps: ReminderInstructionsProps = {395endpoint: this.props.endpoint,396hasTodoTool,397hasEditFileTool,398hasReplaceStringTool,399hasMultiReplaceStringTool,400};401const ToolReferencesHintClass = this.props.ToolReferencesHintClass ?? DefaultToolReferencesHint;402const toolReferencesHintProps: ToolReferencesHintProps = {403toolReferences: this.props.toolReferences,404};405406return (407<>408<UserMessage>409{hasToolsToEditNotebook && <NotebookFormat flexGrow={5} priority={810} chatVariables={this.props.chatVariables} query={query} />}410<TokenLimit max={sizing.tokenBudget / 6} flexGrow={3} priority={898}>411<ChatVariables chatVariables={this.props.chatVariables} isAgent={true} omitReferences />412</TokenLimit>413<ToolReferencesHintClass {...toolReferencesHintProps} />414<Tag name='context'>415<CurrentDatePrompt />416<EditedFileEvents editedFileEvents={this.props.editedFileEvents} />417<NotebookSummaryChange />418{hasTerminalTool && <TerminalStatePromptElement sessionId={this.props.sessionId} />}419{hasTodoTool && <TodoListContextPrompt sessionResource={this.props.sessionResource} />}420{this.props.additionalHookContext && <AdditionalHookContextPrompt context={this.props.additionalHookContext} />}421</Tag>422<CurrentEditorContext endpoint={this.props.endpoint} />423<Tag name='reminderInstructions'>424{/* Critical reminders that are effective when repeated right next to the user message */}425<ReminderInstructionsClass {...reminderProps} />426<NotebookReminderInstructions chatVariables={this.props.chatVariables} query={this.props.request} />427{this.configurationService.getNonExtensionConfig<boolean>(USE_SKILL_ADHERENCE_PROMPT_SETTING) && <SkillAdherenceReminder chatVariables={this.props.chatVariables} />}428</Tag>429{query && <Tag name={userQueryTagName} priority={900} flexGrow={7}>430<UserQuery chatVariables={this.props.chatVariables} query={query} />431</Tag>}432{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}433</UserMessage>434</>435);436}437}438439interface FrozenMessageContentProps extends BasePromptElementProps {440readonly frozenContent: readonly Raw.ChatCompletionContentPart[];441readonly enableCacheBreakpoints?: boolean;442}443444class FrozenContentUserMessage extends PromptElement<FrozenMessageContentProps> {445async render(state: void, sizing: PromptSizing) {446return <UserMessage priority={this.props.priority}>447<Chunk>448{/* Have to move <cacheBreakpoint> out of the Chunk */}449{renderedMessageToTsxChildren(this.props.frozenContent, false)}450</Chunk>451{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}452</UserMessage>;453}454}455456export function renderedMessageToTsxChildren(message: string | readonly Raw.ChatCompletionContentPart[], enableCacheBreakpoints: boolean): PromptPieceChild[] {457if (typeof message === 'string') {458return [message];459}460461return message.map(part => {462if (part.type === Raw.ChatCompletionContentPartKind.Text) {463return part.text;464} else if (part.type === Raw.ChatCompletionContentPartKind.Image) {465return <HistoricalImage src={part.imageUrl.url} detail={part.imageUrl.detail} mimeType={part.imageUrl.mediaType} />;466} else if (part.type === Raw.ChatCompletionContentPartKind.Document) {467return <Document data={part.documentData.data} mediaType={part.documentData.mediaType} />;468} else if (part.type === Raw.ChatCompletionContentPartKind.CacheBreakpoint) {469return enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />;470}471}).filter(isDefined);472}473474class UserOSPrompt extends PromptElement<BasePromptElementProps> {475constructor(props: BasePromptElementProps, @IEnvService private readonly envService: IEnvService) {476super(props);477}478479async render(state: void, sizing: PromptSizing) {480const userOS = this.envService.OS;481const osForDisplay = userOS === OperatingSystem.Macintosh ? 'macOS' :482userOS;483return <>The user's current OS is: {osForDisplay}</>;484}485}486487class CurrentDatePrompt extends PromptElement<BasePromptElementProps> {488constructor(489props: BasePromptElementProps,490@IEnvService private readonly envService: IEnvService) {491super(props);492}493494async render(state: void, sizing: PromptSizing) {495const dateStr = new Date().toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });496// Only include current date when not running simulations, since if we generate cache entries with the current date, the cache will be invalidated every day497return (498!this.envService.isSimulation() && <>The current date is {dateStr}.</>499);500}501}502503interface AdditionalHookContextPromptProps extends BasePromptElementProps {504readonly context: string;505}506507/**508* Renders additional context provided by hooks.509*/510class AdditionalHookContextPrompt extends PromptElement<AdditionalHookContextPromptProps> {511async render(state: void, sizing: PromptSizing) {512return <>Additional instructions from hooks: {this.props.context}</>;513}514}515516interface SkillAdherenceReminderProps extends BasePromptElementProps {517readonly chatVariables: ChatVariablesCollection;518}519520/**521* Skill adherence reminder that prompts the model to read SKILL.md files when skills are available522* in the instruction index.523* Shown whenever the instruction index variable contains at least one skill or skill folder entry.524*/525class SkillAdherenceReminder extends PromptElement<SkillAdherenceReminderProps> {526constructor(527props: SkillAdherenceReminderProps,528@ICustomInstructionsService private readonly customInstructionsService: ICustomInstructionsService,529@IConfigurationService private readonly configurationService: IConfigurationService,530@IExperimentationService private readonly experimentationService: IExperimentationService,531) {532super(props);533}534535async render() {536// Check if any skills are available from the instruction index537const indexVariable = this.props.chatVariables.find(isCustomizationsIndex);538if (!indexVariable || !isString(indexVariable.value)) {539return undefined;540}541542const indexFile = this.customInstructionsService.parseInstructionIndexFile(indexVariable.value);543if (indexFile.skills.size === 0) {544return undefined;545}546547const skillToolEnabled = this.configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SkillToolEnabled, this.experimentationService);548549if (skillToolEnabled) {550return <Tag name='additional_skills_reminder'>551Always 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 />552</Tag>;553}554555return <Tag name='additional_skills_reminder'>556Always 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 />557</Tag>;558}559}560561interface CurrentEditorContextProps extends BasePromptElementProps {562readonly endpoint: IChatEndpoint;563}564565/**566* Include the user's open editor and cursor position, but not content. This is independent of the "implicit context" attachment.567*/568class CurrentEditorContext extends PromptElement<CurrentEditorContextProps> {569constructor(570props: CurrentEditorContextProps,571@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,572@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,573@IConfigurationService private readonly configurationService: IConfigurationService,574@IAlternativeNotebookContentService private readonly alternativeNotebookContent: IAlternativeNotebookContentService,575) {576super(props);577}578579async render(state: void, sizing: PromptSizing) {580if (!this.configurationService.getConfig(ConfigKey.CurrentEditorAgentContext)) {581return;582}583584let context: PromptElement | undefined;585const activeEditor = this.tabsAndEditorsService.activeTextEditor;586if (activeEditor) {587context = this.renderActiveTextEditor(activeEditor);588}589590const activeNotebookEditor = this.tabsAndEditorsService.activeNotebookEditor;591if (activeNotebookEditor) {592context = this.renderActiveNotebookEditor(activeNotebookEditor);593}594595if (!context) {596return;597}598599return <Tag name='editorContext'>600{context}601</Tag>;602}603604private renderActiveTextEditor(activeEditor: TextEditor) {605// Should this include column numbers too? This confused gpt-4.1 and it read the wrong line numbers, need to find the right format.606const selection = activeEditor.selection;607// Found that selection is not always defined, so check for it.608const selectionText = (selection && !selection.isEmpty) ?609<>The current selection is from line {selection.start.line + 1} to line {selection.end.line + 1}.</> : undefined;610return <>The user's current file is {this.promptPathRepresentationService.getFilePath(activeEditor.document.uri)}. {selectionText}</>;611}612613private renderActiveNotebookEditor(activeNotebookEditor: NotebookEditor) {614const altDocument = this.alternativeNotebookContent.create(this.alternativeNotebookContent.getFormat(this.props.endpoint)).getAlternativeDocument(activeNotebookEditor.notebook);615let selectionText = '';616// Found that selection is not always defined, so check for it.617if (activeNotebookEditor.selection && !activeNotebookEditor.selection.isEmpty && activeNotebookEditor.notebook.cellCount > 0) {618// Compute a list of all cells that fall in the range of selection.start and selection.end619const { start, end } = activeNotebookEditor.selection;620const cellsInRange = [];621for (let i = start; i < end; i++) {622const cell = activeNotebookEditor.notebook.cellAt(i);623if (cell) {624cellsInRange.push(cell);625}626}627const startCell = cellsInRange[0];628const endCell = cellsInRange[cellsInRange.length - 1];629const lastLine = endCell.document.lineAt(endCell.document.lineCount - 1);630const startPosition = altDocument.fromCellPosition(startCell, new Position(0, 0));631const endPosition = altDocument.fromCellPosition(endCell, new Position(endCell.document.lineCount - 1, lastLine.text.length));632const selection = new Range(startPosition, endPosition);633selectionText = selection ? ` The current selection is from line ${selection.start.line + 1} to line ${selection.end.line + 1}.` : '';634}635return <>The user's current notebook is {this.promptPathRepresentationService.getFilePath(activeNotebookEditor.notebook.uri)}.{selectionText}</>;636}637}638639class WorkspaceFoldersHint extends PromptElement<BasePromptElementProps> {640constructor(641props: BasePromptElementProps,642@IWorkspaceService private readonly workspaceService: IWorkspaceService,643@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,644) {645super(props);646}647648async render(state: void, sizing: PromptSizing) {649const folders = this.workspaceService.getWorkspaceFolders();650if (folders.length > 0) {651return (652<>653I am working in a workspace with the following folders:<br />654{folders.map(folder => `- ${this.promptPathRepresentationService.getFilePath(folder)} `).join('\n')}655</>);656} else {657return <>There is no workspace currently open.</>;658}659}660}661662663interface AgentTasksInstructionsProps extends BasePromptElementProps {664readonly availableTools?: readonly LanguageModelToolInformation[];665}666667export class AgentTasksInstructions extends PromptElement<AgentTasksInstructionsProps> {668constructor(669props: AgentTasksInstructionsProps,670@ITasksService private readonly _tasksService: ITasksService,671@IPromptPathRepresentationService private readonly _promptPathRepresentationService: IPromptPathRepresentationService,672@IIgnoreService private readonly _ignoreService: IIgnoreService,673) {674super(props);675}676677async render() {678const foundEnabledTaskTool = this.props.availableTools?.find(t => t.name === ToolName.CoreRunTask || t.name === ToolName.CoreCreateAndRunTask || t.name === ToolName.CoreGetTaskOutput);679if (!foundEnabledTaskTool) {680return 0;681}682683const taskGroupsRaw = this._tasksService.getTasks();684const taskGroups = (await Promise.all(taskGroupsRaw.map(async ([folder, tasks]) => {685const tasksFile = URI.joinPath(folder, '.vscode', 'tasks.json');686if (await this._ignoreService.isCopilotIgnored(tasksFile)) {687return undefined;688}689const visibleTasks = tasks.filter(task => (!!task.type || task.dependsOn) && !task.hide);690return visibleTasks.length > 0 ? [folder, visibleTasks] as const : undefined;691}))).filter(isDefined);692if (taskGroups.length === 0) {693return 0;694}695696return <>697The following tasks can be executed using the {ToolName.CoreRunTask} tool if they are not already running:<br />698{taskGroups.map(([folder, tasks]) =>699<Tag name='workspaceFolder' attrs={{ path: this._promptPathRepresentationService.getFilePath(folder) }}>700{tasks.map((t, i) => {701const isActive = this._tasksService.isTaskActive(t);702return (703<Tag name='task' attrs={{ id: t.type ? `${t.type}: ${t.label || i}` : `${t.label || i}` }}>704{this.makeTaskPresentation(t)}705{isActive && <> (This task is currently running. You can use the {ToolName.CoreGetTaskOutput} tool to view its output.)</>}706</Tag>707);708})}709</Tag>710)}711</>;712}713714/** Makes a simplified JSON presentation of the task definition for the model to reference. */715private makeTaskPresentation(task: TaskDefinition) {716const enum PlatformAttr {717Windows = 'windows',718Mac = 'osx',719Linux = 'linux'720}721722const omitAttrs = ['presentation', 'problemMatcher', PlatformAttr.Windows, PlatformAttr.Mac, PlatformAttr.Linux];723724const output: Record<string, unknown> = {};725for (const [key, value] of Object.entries(task)) {726if (!omitAttrs.includes(key)) {727output[key] = value;728}729}730731732const myPlatformAttr = process.platform === 'win32' ? PlatformAttr.Windows :733process.platform === 'darwin' ? PlatformAttr.Mac :734PlatformAttr.Linux;735if (task[myPlatformAttr] && typeof task[myPlatformAttr] === 'object') {736Object.assign(output, task[myPlatformAttr]);737}738739return JSON.stringify(output, null, '\t');740}741}742743export interface EditedFileEventsProps extends BasePromptElementProps {744readonly editedFileEvents: readonly ChatRequestEditedFileEvent[] | undefined;745}746747/**748* Context about manual edits made to files that the agent previously edited.749*/750export class EditedFileEvents extends PromptElement<EditedFileEventsProps> {751constructor(752props: EditedFileEventsProps,753@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,754) {755super(props);756}757758async render(state: void, sizing: PromptSizing) {759const events = this.props.editedFileEvents;760761if (!events || events.length === 0) {762return undefined;763}764765// Group by event kind and collect file paths766const undoFiles: string[] = [];767const modFiles: string[] = [];768const seenUndo = new Set<string>();769const seenMod = new Set<string>();770771for (const event of events) {772if (event.eventKind === ChatRequestEditedFileEventKind.Undo) {773const fp = this.promptPathRepresentationService.getFilePath(event.uri);774if (!seenUndo.has(fp)) { seenUndo.add(fp); undoFiles.push(fp); }775} else if (event.eventKind === ChatRequestEditedFileEventKind.UserModification) {776const fp = this.promptPathRepresentationService.getFilePath(event.uri);777if (!seenMod.has(fp)) { seenMod.add(fp); modFiles.push(fp); }778}779}780781if (undoFiles.length === 0 && modFiles.length === 0) {782return undefined;783}784785const sections: string[] = [];786if (undoFiles.length > 0) {787sections.push([788'The user undid your edits to:',789...undoFiles.map(f => `- ${f}`)790].join('\n'));791}792if (modFiles.length > 0) {793sections.push([794'Some edits were made, by the user or possibly by a formatter or another automated tool, to:',795...modFiles.map(f => `- ${f}`)796].join('\n'));797}798799return (800<>801There have been some changes between the last request and now.<br />802{sections.join('\n')}<br />803So be sure to check the current file contents before making any new edits.804</>805);806}807}808809810