Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/extension/intents/node/agentIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { IDefaultIntentRequestHandlerOptions } from '../../prompt/node/defaultIn
import { IDocumentContext } from '../../prompt/node/documentContext';
import { IBuildPromptResult, IIntent, IIntentInvocation } from '../../prompt/node/intents';
import { AgentPrompt, AgentPromptProps } from '../../prompts/node/agent/agentPrompt';
import { PromptRegistry } from '../../prompts/node/agent/promptRegistry';
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
import { ICodeMapperService } from '../../prompts/node/codeMapper/codeMapperService';
import { TemporalContextStats } from '../../prompts/node/inline/temporalContext';
Expand Down Expand Up @@ -259,6 +260,11 @@ export class AgentIntentInvocation extends EditCodeIntentInvocation implements I
const endpoint = toolTokens > 0 ? this.endpoint.cloneWithTokenOverride(safeBudget) : this.endpoint;
const summarizationEnabled = this.configurationService.getConfig(ConfigKey.SummarizeAgentConversationHistory) && this.prompt === AgentPrompt;
this.logService.debug(`AgentIntent: rendering with budget=${safeBudget} (baseBudget: ${baseBudget}, toolTokens: ${toolTokens}), summarizationEnabled=${summarizationEnabled}`);

// Get model options from prompt registry
const agentPromptResolver = await PromptRegistry.getPrompt(endpoint);
const modelOptions = agentPromptResolver && this.instantiationService.createInstance(agentPromptResolver).resolveModelOptions?.(endpoint);

let result: RenderPromptResult;
const props: AgentPromptProps = {
endpoint,
Expand All @@ -271,6 +277,7 @@ export class AgentIntentInvocation extends EditCodeIntentInvocation implements I
},
location: this.location,
enableCacheBreakpoints: summarizationEnabled,
...(modelOptions && { modelOptions }),
...this.extraPromptProps
};
try {
Expand Down
53 changes: 27 additions & 26 deletions src/extension/prompts/node/agent/agentPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BasePromptElementProps, Chunk, Image, PromptElement, PromptPiece, Promp
import type { ChatRequestEditedFileEvent, LanguageModelToolInformation, NotebookEditor, TaskDefinition, TextEditor } from 'vscode';
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
import { isHiddenModelB, isVSCModel, modelNeedsStrongReplaceStringHint } from '../../../../platform/endpoint/common/chatModelCapabilities';
import { isVSCModel, modelNeedsStrongReplaceStringHint } from '../../../../platform/endpoint/common/chatModelCapabilities';
import { CacheType } from '../../../../platform/endpoint/common/endpointTypes';
import { IEnvService, OperatingSystem } from '../../../../platform/env/common/envService';
import { getGitHubRepoInfoFromContext, IGitService } from '../../../../platform/git/common/gitService';
Expand All @@ -31,9 +31,10 @@ import { InternalToolReference } from '../../../prompt/common/intents';
import { IPromptVariablesService } from '../../../prompt/node/promptVariablesService';
import { ToolName } from '../../../tools/common/toolNames';
import { TodoListContextPrompt } from '../../../tools/node/todoListContextPrompt';
import { CopilotIdentityRules, GPT5CopilotIdentityRule } from '../base/copilotIdentity';
import { CopilotIdentityRules } from '../base/copilotIdentity';
import { CustomRender } from '../base/customRender';
import { IPromptEndpoint, renderPromptElement } from '../base/promptRenderer';
import { Gpt5SafetyRule, SafetyRules } from '../base/safetyRules';
import { SafetyRules } from '../base/safetyRules';
import { Tag } from '../base/tag';
import { TerminalStatePromptElement } from '../base/terminalState';
import { ChatVariables } from '../panel/chatVariables';
Expand All @@ -47,7 +48,7 @@ import { MultirootWorkspaceStructure } from '../panel/workspace/workspaceStructu
import { AgentConversationHistory } from './agentConversationHistory';
import './allAgentPrompts';
import { AlternateGPTPrompt, DefaultAgentPrompt } from './defaultAgentInstructions';
import { PromptRegistry } from './promptRegistry';
import { ModelOptions, PromptRegistry } from './promptRegistry';
import { SummarizedConversationHistory } from './summarizedConversationHistory';

export interface AgentPromptProps extends GenericBasePromptElementProps {
Expand All @@ -65,6 +66,11 @@ export interface AgentPromptProps extends GenericBasePromptElementProps {
* Codesearch mode, aka agentic Ask mode
*/
readonly codesearchMode?: boolean;

/**
* Model-specific options
*/
readonly modelOptions?: ModelOptions;
}

/** Proportion of the prompt token budget any singular textual tool result is allowed to use. */
Expand Down Expand Up @@ -92,17 +98,10 @@ export class AgentPrompt extends PromptElement<AgentPromptProps> {
const baseAgentInstructions = <>
<SystemMessage>
You are an expert AI programming assistant, working with a user in the VS Code editor.<br />
{this.props.endpoint.family.startsWith('gpt-5') ? (
<>
<GPT5CopilotIdentityRule />
<Gpt5SafetyRule />
</>
) : (
<>
<CopilotIdentityRules />
<SafetyRules />
</>
)}
<CustomRender id='SystemMessageContent' overrides={this.props.modelOptions?.overrides}>
<CopilotIdentityRules />
<SafetyRules />
</CustomRender>
</SystemMessage>
{instructions}
</>;
Expand All @@ -129,6 +128,7 @@ export class AgentPrompt extends PromptElement<AgentPromptProps> {
endpoint={this.props.endpoint}
tools={this.props.promptContext.tools?.availableTools}
enableCacheBreakpoints={this.props.enableCacheBreakpoints}
modelOptions={this.props.modelOptions}
/>
</>;
} else {
Expand All @@ -141,7 +141,7 @@ export class AgentPrompt extends PromptElement<AgentPromptProps> {
}
}

private async getInstructions() {
private async getInstructions(): Promise<PromptPieceChild> {
const modelFamily = this.props.endpoint.family ?? 'unknown';

if (this.props.endpoint.family.startsWith('gpt-') && this.configurationService.getExperimentBasedConfig(ConfigKey.EnableAlternateGptPrompt, this.experimentationService)) {
Expand Down Expand Up @@ -268,9 +268,10 @@ export interface AgentUserMessageProps extends BasePromptElementProps {
readonly enableCacheBreakpoints?: boolean;
readonly editedFileEvents?: readonly ChatRequestEditedFileEvent[];
readonly sessionId?: string;
readonly modelOptions?: ModelOptions;
}

export function getUserMessagePropsFromTurn(turn: Turn, endpoint: IChatEndpoint): AgentUserMessageProps {
export function getUserMessagePropsFromTurn(turn: Turn, endpoint: IChatEndpoint, modelOptions: ModelOptions | undefined): AgentUserMessageProps {
return {
isHistorical: true,
request: turn.request.message,
Expand All @@ -279,7 +280,8 @@ export function getUserMessagePropsFromTurn(turn: Turn, endpoint: IChatEndpoint)
toolReferences: turn.toolReferences,
chatVariables: turn.promptVariables ?? new ChatVariablesCollection(),
editedFileEvents: turn.editedFileEvents,
enableCacheBreakpoints: false // Should only be added to the current turn - some user messages may get them in Agent post-processing
enableCacheBreakpoints: false, // Should only be added to the current turn - some user messages may get them in Agent post-processing
modelOptions,
};
}

Expand All @@ -296,7 +298,7 @@ export function getUserMessagePropsFromAgentProps(agentProps: AgentPromptProps):
editedFileEvents: agentProps.promptContext.editedFileEvents,
// TODO:@roblourens
sessionId: (agentProps.promptContext.tools?.toolInvocationToken as any)?.sessionId,

modelOptions: agentProps.modelOptions,
};
}

Expand Down Expand Up @@ -333,14 +335,9 @@ export class AgentUserMessage extends PromptElement<AgentUserMessageProps> {
const hasEditFileTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditFile);
const hasEditNotebookTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.EditNotebook);
const hasTerminalTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreRunInTerminal);
const isGpt5 = this.props.endpoint.family.startsWith('gpt-5') && this.props.endpoint.family !== 'gpt-5-codex';
const attachmentHint = (this.props.endpoint.family === 'gpt-4.1' || isGpt5) && this.props.chatVariables.hasVariables() ?
' (See <attachments> above for file contents. You may not need to search or read the file again.)'
: '';
const hasVariables = this.props.chatVariables.hasVariables();
const hasToolsToEditNotebook = hasCreateFileTool || hasEditNotebookTool || hasReplaceStringTool || hasApplyPatchTool || hasEditFileTool;
const hasTodoTool = !!this.props.availableTools?.find(tool => tool.name === ToolName.CoreManageTodoList);
const isHiddenModelBFlag = await isHiddenModelB(this.props.endpoint);
const shouldUseUserQuery = this.props.endpoint.family.startsWith('grok-code') || isHiddenModelBFlag;

return (
<>
Expand Down Expand Up @@ -368,7 +365,11 @@ export class AgentUserMessage extends PromptElement<AgentUserMessageProps> {
{getExplanationReminder(this.props.endpoint.family, hasTodoTool)}
{getVSCModelReminder(shouldIncludePreamble)}
</Tag>
{query && <Tag name={shouldUseUserQuery ? 'user_query' : 'userRequest'} priority={900} flexGrow={7}>{query + attachmentHint}</Tag>}
{query && <Tag name={this.props.modelOptions?.shouldUseUserQuery ? 'user_query' : 'userRequest'} priority={900} flexGrow={7}>
<CustomRender id='UserMessageContent' overrides={this.props.modelOptions?.overrides} args={{ query, hasVariables }}>
{query}
</CustomRender>
</Tag>}
{this.props.enableCacheBreakpoints && <cacheBreakpoint type={CacheType} />}
</UserMessage>
</>
Expand Down
21 changes: 20 additions & 1 deletion src/extension/prompts/node/agent/openAIPrompts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import { ConfigKey, IConfigurationService } from '../../../../platform/configura
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
import { ToolName } from '../../../tools/common/toolNames';
import { GPT5CopilotIdentityRule } from '../base/copilotIdentity';
import { InstructionMessage } from '../base/instructionMessage';
import { ResponseTranslationRules } from '../base/responseTranslationRules';
import { Gpt5SafetyRule } from '../base/safetyRules';
import { Tag } from '../base/tag';
import { EXISTING_CODE_MARKER } from '../panel/codeBlockFormattingRules';
import { MathIntegrationRules } from '../panel/editorIntegrationRules';
import { KeepGoingReminder } from './agentPrompt';
import { ApplyPatchInstructions, CodesearchModeInstructions, DefaultAgentPromptProps, detectToolCapabilities, GenericEditingTips, McpToolInstructions, NotebookInstructions } from './defaultAgentInstructions';
import { IAgentPrompt, PromptConstructor, PromptRegistry } from './promptRegistry';
import { IAgentPrompt, ModelOptions, PromptConstructor, PromptRegistry } from './promptRegistry';

export class DefaultOpenAIAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
async render(state: void, sizing: PromptSizing) {
Expand Down Expand Up @@ -720,6 +722,23 @@ class OpenAIPromptResolver implements IAgentPrompt {

return DefaultOpenAIAgentPrompt;
}

resolveModelOptions(endpoint: IChatEndpoint): ModelOptions | undefined {
return {
overrides: {
SystemMessageContent: endpoint.model?.startsWith('gpt-5') ? () => <>
<GPT5CopilotIdentityRule />
<Gpt5SafetyRule />
</> : undefined,
UserMessageContent: (endpoint.family.startsWith('gpt-5') && endpoint.family !== 'gpt-5-codex') || (endpoint.family === 'gpt-4.1') ? ({ query, hasVariables }) => {
if (hasVariables) {
return <>{query + ' (See <attachments> above for file contents. You may not need to search or read the file again.)'}</>;
}
return <>{query}</>;
} : undefined
}
};
}
}

PromptRegistry.registerPrompt(OpenAIPromptResolver);
11 changes: 10 additions & 1 deletion src/extension/prompts/node/agent/promptRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { PromptElement } from '@vscode/prompt-tsx';
import { PromptElement, PromptPiece } from '@vscode/prompt-tsx';
import type { IChatEndpoint } from '../../../../platform/networking/common/networking';
import { DefaultAgentPromptProps } from './defaultAgentInstructions';

export interface ModelOptions {
readonly shouldUseUserQuery?: boolean;
readonly overrides?: {
readonly SystemMessageContent?: () => PromptElement | PromptPiece;
readonly UserMessageContent?: (props: { query: string; hasVariables: boolean }) => PromptElement | PromptPiece;
};
}

export type PromptConstructor = new (props: DefaultAgentPromptProps, ...args: any[]) => PromptElement<DefaultAgentPromptProps>;

export interface IAgentPrompt {
resolvePrompt(endpoint: IChatEndpoint): PromptConstructor | undefined;
resolveModelOptions?(endpoint: IChatEndpoint): ModelOptions | undefined;
}

export interface IAgentPromptCtor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { renderPromptElement } from '../base/promptRenderer';
import { Tag } from '../base/tag';
import { ChatToolCalls } from '../panel/toolCalling';
import { AgentPrompt, AgentPromptProps, AgentUserMessage, getUserMessagePropsFromAgentProps, getUserMessagePropsFromTurn, KeepGoingReminder } from './agentPrompt';
import { ModelOptions } from './promptRegistry';
import { SimpleSummarizedHistory } from './simpleSummarizedHistoryPrompt';

export interface ConversationHistorySummarizationPromptProps extends SummarizedAgentHistoryProps {
Expand Down Expand Up @@ -274,7 +275,7 @@ class ConversationHistory extends PromptElement<SummarizedAgentHistoryProps> {
// We have a summary for a tool call round that was part of this turn
turnComponents.push(<SummaryMessageElement endpoint={this.props.endpoint} summaryText={summaryForTurn.text} />);
} else {
turnComponents.push(<AgentUserMessage flexGrow={1} {...getUserMessagePropsFromTurn(turn, this.props.endpoint)} />);
turnComponents.push(<AgentUserMessage flexGrow={1} {...getUserMessagePropsFromTurn(turn, this.props.endpoint, this.props.modelOptions)} />);
}

// Reverse the tool call rounds so they are in chronological order
Expand Down Expand Up @@ -322,6 +323,7 @@ export interface SummarizedAgentHistoryProps extends BasePromptElementProps {
readonly maxToolResultLength: number;
/** Optional hard cap on summary tokens; effective budget = min(prompt sizing tokenBudget, this value) */
readonly maxSummaryTokens?: number;
readonly modelOptions?: ModelOptions;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/extension/prompts/node/agent/test/agentPrompt.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ToolName } from '../../../../tools/common/toolNames';
import { IToolsService } from '../../../../tools/common/toolsService';
import { PromptRenderer } from '../../base/promptRenderer';
import { AgentPrompt, AgentPromptProps } from '../agentPrompt';
import { PromptRegistry } from '../promptRegistry';

["default", "gpt-4.1", "gpt-5", "claude-sonnet-4.5", "gemini-2.0-flash", "grok-code-fast-1"].forEach(family => {
suite(`AgentPrompt - ${family}`, () => {
Expand Down Expand Up @@ -75,11 +76,15 @@ import { AgentPrompt, AgentPromptProps } from '../agentPrompt';
promptContext = { ...promptContext, conversation };
}

const agentPromptResolver = await PromptRegistry.getPrompt(endpoint);
const modelOptions = agentPromptResolver && instaService.createInstance(agentPromptResolver).resolveModelOptions?.(endpoint);

const baseProps = {
priority: 1,
endpoint,
location: ChatLocation.Panel,
promptContext,
modelOptions,
...otherProps
};

Expand Down
8 changes: 7 additions & 1 deletion src/extension/prompts/node/agent/xAIPrompts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { EXISTING_CODE_MARKER } from '../panel/codeBlockFormattingRules';
import { MathIntegrationRules } from '../panel/editorIntegrationRules';
import { KeepGoingReminder } from './agentPrompt';
import { CodesearchModeInstructions, DefaultAgentPromptProps, detectToolCapabilities, GenericEditingTips, McpToolInstructions, NotebookInstructions } from './defaultAgentInstructions';
import { IAgentPrompt, PromptConstructor, PromptRegistry } from './promptRegistry';
import { IAgentPrompt, ModelOptions, PromptConstructor, PromptRegistry } from './promptRegistry';

class DefaultGrokCodeFastAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
async render(state: void, sizing: PromptSizing) {
Expand Down Expand Up @@ -129,6 +129,12 @@ class XAIPromptResolver implements IAgentPrompt {
resolvePrompt(endpoint: IChatEndpoint): PromptConstructor | undefined {
return DefaultGrokCodeFastAgentPrompt;
}

resolveModelOptions(endpoint: IChatEndpoint): ModelOptions | undefined {
return {
shouldUseUserQuery: true,
};
}
}


Expand Down
30 changes: 30 additions & 0 deletions src/extension/prompts/node/base/customRender.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BasePromptElementProps, PromptElement, PromptPiece } from '@vscode/prompt-tsx';

interface CustomRenderProps<T extends { [key: string]: (args: any) => PromptElement | PromptPiece }, K extends keyof T> extends BasePromptElementProps {
id: K;
overrides?: T;
args?: Parameters<T[K]>[0];
}

export class CustomRender<
T extends { [key: string]: (args: any) => PromptElement | PromptPiece },
K extends keyof T = keyof T
> extends PromptElement<CustomRenderProps<T, K>> {
constructor(props: CustomRenderProps<T, K>) {
super(props);
}

render() {
if (this.props.overrides && Object.hasOwn(this.props.overrides, this.props.id)) {
const override = this.props.overrides[this.props.id];
if (typeof override === 'function') {
return override(this.props.args);
}
}
return <>{this.props.children}</>;
}
}
Loading