nes: feat: add eagerness option for diffpatch prompt (#327544)

* Add patchBased02AggressionHighLow

* Make eagerness part of prompt strategy for diffpatch prompt

* Explicitly gate isAggressionPromptingStrategy behind prompt strategy and eagerness option

* Remove accidental deletion

* Properly rename function

* Refactor patch-based postscript scoping

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix PatchBased02 eagerness prompt

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Ulugbek Abdullaev <ulugbekna@gmail.com>
This commit is contained in:
Benjamin Steenhoek
2026-08-19 19:25:22 +00:00
committed by GitHub
co-authored by Copilot App Ulugbek Abdullaev
parent 96ace44a39
commit 8041c5c6ce
5 changed files with 75 additions and 14 deletions
@@ -367,9 +367,10 @@ function appendWithNewLineIfNeeded(base: string, toAppend: string, minNewLines:
return (base + '\n'.repeat(newLinesToAdd) + toAppend).trim();
}
function getPostScript(opts: PromptOptions, currentFilePath: string, aggressivenessLevel: AggressivenessLevel) {
const { promptingStrategy } = opts;
function getPostScript(options: PromptOptions, currentFilePath: string, aggressivenessLevel: AggressivenessLevel) {
const { promptingStrategy, eagernessPrompt } = options;
const xtab275BasePostScript = `The developer was working on a section of code within the tags \`code_to_edit\` in the file located at \`${currentFilePath}\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor position marked as \`${PromptTags.CURSOR}\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes they would have made next. Provide the revised code that was between the \`${PromptTags.EDIT_WINDOW.start}\` and \`${PromptTags.EDIT_WINDOW.end}\` tags, but do not include the tags themselves. Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Don't include the line numbers or the form #| in your response. Do not skip any lines. Do not be lazy.`;
const patchBased02PostScript = `The developer was working on a section of code within the \`current_file_content\` - carefully note their \`cursor_location\` marked with \`<|cursor|>\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, and \`cursor_location\`, please continue the developer's work. Output a modified diff format with a sequence of intuitive next changes, where each patch must start with \`<filename>:<line number>\`. Order changes by priority and flow; for instance, edits adjacent to the user's cursor should always be prioritized, followed by lines near the cursor, followed by lines farther away. If there are no good edit candidates, output the empty string "". Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Adhere meticulously to the diff format.`;
let postScript: string | undefined;
switch (promptingStrategy) {
@@ -377,11 +378,13 @@ function getPostScript(opts: PromptOptions, currentFilePath: string, aggressiven
case PromptingStrategy.Codexv21NesUnified:
break;
case PromptingStrategy.PatchBased02:
postScript = `The developer was working on a section of code within the \`current_file_content\` - carefully note their \`cursor_location\` marked with \`<|cursor|>\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, and \`cursor_location\`, please continue the developer's work. Output a modified diff format with a sequence of intuitive next changes, where each patch must start with \`<filename>:<line number>\`. Order changes by priority and flow; for instance, edits adjacent to the user's cursor should always be prioritized, followed by lines near the cursor, followed by lines farther away. If there are no good edit candidates, output the empty string "". Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Adhere meticulously to the diff format.`;
break;
case PromptingStrategy.PatchBased02WithRecentLineNumbers:
case PromptingStrategy.PatchBased02WithoutRecentLineNumbers:
postScript = `The developer was working on a section of code within the \`current_file_content\` - carefully note their \`cursor_location\` marked with \`<|cursor|>\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, and \`cursor_location\`, please continue the developer's work. Output a modified diff format with a sequence of intuitive next changes, where each patch must start with \`<filename>:<line number>\`. Order changes by priority and flow; for instance, edits adjacent to the user's cursor should always be prioritized, followed by lines near the cursor, followed by lines farther away. If there are no good edit candidates, output the empty string "". Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Adhere meticulously to the diff format.`;
postScript = eagernessPrompt === 'aggressionHighLow'
? aggressivenessLevel === AggressivenessLevel.Medium
? patchBased02PostScript
: `<|aggression|>${aggressivenessLevel}<|/aggression|>\n\n${patchBased02PostScript}`
: patchBased02PostScript;
break;
case PromptingStrategy.UnifiedModel:
postScript = `The developer was working on a section of code within the tags \`code_to_edit\` in the file located at \`${currentFilePath}\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor position marked as \`${PromptTags.CURSOR}\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes they would have made next. Start your response with <EDIT>, <INSERT>, or <NO_CHANGE>. If you are making an edit, start with <EDIT> and then provide the rewritten code window followed by </EDIT>. If you are inserting new code, start with <INSERT> and then provide only the new code that will be inserted at the cursor position followed by </INSERT>. If no changes are necessary, reply only with <NO_CHANGE>. Avoid undoing or reverting the developer's last change unless there are obvious typos or errors.`;
@@ -19,7 +19,7 @@ import { LanguageContextEntry, LanguageContextResponse } from '../../../platform
import { LanguageId } from '../../../platform/inlineEdits/common/dataTypes/languageId';
import { NextCursorLinePrediction } from '../../../platform/inlineEdits/common/dataTypes/nextCursorLinePrediction';
import * as xtabPromptOptions from '../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions';
import { AggressivenessSetting, EarlyDivergenceCancellationMode, isAggressivenessStrategy, LanguageContextLanguages, LanguageContextOptions } from '../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions';
import { AggressivenessSetting, EarlyDivergenceCancellationMode, isEagernessPrompt, LanguageContextLanguages, LanguageContextOptions } from '../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions';
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
import { IInlineEditsModelService } from '../../../platform/inlineEdits/common/inlineEditsModelService';
import { ResponseProcessor } from '../../../platform/inlineEdits/common/responseProcessor';
@@ -617,7 +617,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
}
// Adjust debounce based on user aggressiveness setting for non-aggressiveness models
if (!isAggressivenessStrategy(promptOptions.promptingStrategy)) {
if (!isEagernessPrompt(promptOptions)) {
this._applyAggressivenessSettings(delaySession, tracer);
}
}
@@ -1574,6 +1574,7 @@ export class XtabProvider implements IStatelessNextEditProvider {
},
memory: undefined,
lintOptions: undefined,
eagernessPrompt: undefined,
includePostScript: true,
globalBudget: this.getGlobalBudget(),
};
@@ -1782,6 +1783,7 @@ export function overrideModelConfig(modelConfig: ModelConfig, overridingConfig:
...modelConfig,
modelName: overridingConfig.modelName,
promptingStrategy: overridingConfig.promptingStrategy,
eagernessPrompt: overridingConfig.eagernessPrompt ?? modelConfig.eagernessPrompt,
includePostScript: overridingConfig.includePostScript ?? modelConfig.includePostScript,
currentFile: {
...modelConfig.currentFile,
@@ -614,6 +614,7 @@ describe('getUserPrompt', () => {
strategy: PromptingStrategy | undefined;
includeLineNumbers?: IncludeLineNumbersOption;
includePostScript?: boolean;
eagernessPrompt?: 'aggressionHighLow';
aggressivenessLevel?: AggressivenessLevel;
rejectedEditsMemory?: RejectedEditsMemoryMode;
}): PromptPieces {
@@ -635,6 +636,7 @@ describe('getUserPrompt', () => {
const promptOptions: PromptOptions = {
...DEFAULT_OPTIONS,
promptingStrategy: opts.strategy,
eagernessPrompt: opts.eagernessPrompt,
...(opts.includePostScript !== undefined ? { includePostScript: opts.includePostScript } : {}),
...(opts.rejectedEditsMemory !== undefined ? { memory: { rejectedEdits: opts.rejectedEditsMemory } } : {}),
currentFile: {
@@ -816,6 +818,32 @@ describe('getUserPrompt', () => {
expect(prompt).toContain(PromptTags.CURSOR_LOCATION.start + '\n' + ' const ' + PromptTags.CURSOR + 'x = 1;' + '\n' + PromptTags.CURSOR_LOCATION.end);
});
test.each([
[AggressivenessLevel.Medium, ''],
[AggressivenessLevel.High, '<|aggression|>high<|/aggression|>'],
[AggressivenessLevel.Low, '<|aggression|>low<|/aggression|>'],
])('PatchBased02 aggression prompt places the %s tag before the postscript', (aggressivenessLevel, aggressionTag) => {
const pieces = createTestPromptPieces({
cursorLine: 2,
cursorColumn: 9,
strategy: PromptingStrategy.PatchBased02,
eagernessPrompt: 'aggressionHighLow',
aggressivenessLevel,
});
const { prompt } = getUserPrompt(pieces);
const cursorLocation = `${PromptTags.CURSOR_LOCATION.start}\n const ${PromptTags.CURSOR}x = 1;\n${PromptTags.CURSOR_LOCATION.end}`;
const postScript = 'The developer was working on a section of code within the `current_file_content`';
expect(prompt).toContain(cursorLocation);
expect(prompt.indexOf(cursorLocation)).toBeLessThan(prompt.indexOf(postScript));
if (aggressivenessLevel === AggressivenessLevel.Medium) {
expect(prompt).not.toContain('<|aggression|>');
expect(prompt).toContain(`${PromptTags.CURSOR_LOCATION.end}\n\n${postScript}`);
} else {
expect(prompt).toContain(`${PromptTags.CURSOR_LOCATION.end}\n\n${aggressionTag}\n\n${postScript}`);
}
});
describe('Xtab275AggressivenessHighLow', () => {
test('medium level does not include aggressive tag', () => {
const pieces = createTestPromptPieces({
@@ -492,6 +492,8 @@ export namespace EditIntent {
}
}
export type EagernessPrompt = 'aggressionHighLow';
export type PromptOptions = {
readonly promptingStrategy: PromptingStrategy | undefined /* default */;
readonly currentFile: CurrentFileOptions;
@@ -503,6 +505,7 @@ export type PromptOptions = {
readonly memory: PromptMemoryOptions | undefined;
readonly includePostScript: boolean;
readonly lintOptions: LintOptions | undefined;
readonly eagernessPrompt: EagernessPrompt | undefined;
/**
* When set, parts share a single pool of `totalTokens` and unused budget from
* earlier parts in `order` cascades to later parts. When `undefined`, each
@@ -559,12 +562,21 @@ export function isPromptingStrategy(value: string): value is PromptingStrategy {
return (Object.values(PromptingStrategy) as string[]).includes(value);
}
export function isAggressivenessStrategy(strategy: PromptingStrategy | undefined): boolean {
return strategy === PromptingStrategy.XtabAggressiveness
|| strategy === PromptingStrategy.Xtab275Aggressiveness
|| strategy === PromptingStrategy.Xtab275AggressivenessHighLow
|| strategy === PromptingStrategy.Xtab275EditIntent
|| strategy === PromptingStrategy.Xtab275EditIntentShort;
export function isEagernessPrompt(options: PromptOptions): boolean {
if (options.promptingStrategy === undefined) {
return false;
}
return (options.eagernessPrompt !== undefined && [
PromptingStrategy.PatchBased02,
PromptingStrategy.PatchBased02WithRecentLineNumbers,
PromptingStrategy.PatchBased02WithoutRecentLineNumbers,
].includes(options.promptingStrategy)) // eagerness prompt option is only supported for patch-based strategies
|| [PromptingStrategy.XtabAggressiveness,
PromptingStrategy.Xtab275Aggressiveness,
PromptingStrategy.Xtab275AggressivenessHighLow,
PromptingStrategy.Xtab275EditIntent,
PromptingStrategy.Xtab275EditIntentShort,
].includes(options.promptingStrategy); // first-class aggressiveness strategies
}
export function isRejectedEditMemoryEnabled(options: { readonly memory?: PromptMemoryOptions }): boolean {
@@ -614,6 +626,7 @@ export namespace ResponseFormat {
export const DEFAULT_OPTIONS: PromptOptions = {
promptingStrategy: undefined,
eagernessPrompt: undefined,
currentFile: {
maxTokens: 1500,
includeTags: true,
@@ -673,6 +686,7 @@ export const LANGUAGE_CONTEXT_ENABLED_LANGUAGES: LanguageContextLanguages = {
export interface ModelConfiguration {
modelName: string;
promptingStrategy: PromptingStrategy | undefined /* default */;
eagernessPrompt?: EagernessPrompt;
includeTagsInCurrentFile: boolean;
includePostScript?: boolean;
currentFile?: Partial<CurrentFileOptions>;
@@ -742,6 +756,7 @@ export const LINT_OPTIONS_VALIDATOR: IValidator<Partial<LintOptions>> = vObj({
export const MODEL_CONFIGURATION_VALIDATOR: IValidator<ModelConfiguration> = vObj({
'modelName': vRequired(vString()),
'promptingStrategy': vUnion(vEnum(...Object.values(PromptingStrategy)), vUndefined()),
'eagernessPrompt': vUnion(vEnum<EagernessPrompt[]>('aggressionHighLow'), vUndefined()),
'includeTagsInCurrentFile': vRequired(vBoolean()),
'includePostScript': vUnion(vBoolean(), vUndefined()),
'currentFile': vUnion(CurrentFileOptions.VALIDATOR, vUndefined()),
@@ -5,7 +5,7 @@
import { describe, expect, it } from 'vitest';
import { ImportChanges } from '../../common/dataTypes/importFilteringOptions';
import { applyStrategyConfig, DEFAULT_OPTIONS, GlobalBudgetOptions, IncludeLineNumbersOption, MODEL_CONFIGURATION_VALIDATOR, ModelConfiguration, PromptingStrategy, RejectedEditsMemoryMode } from '../../common/dataTypes/xtabPromptOptions';
import { applyStrategyConfig, DEFAULT_OPTIONS, GlobalBudgetOptions, IncludeLineNumbersOption, isEagernessPrompt, MODEL_CONFIGURATION_VALIDATOR, ModelConfiguration, PromptingStrategy, RejectedEditsMemoryMode } from '../../common/dataTypes/xtabPromptOptions';
function baseConfig(overrides: Partial<ModelConfiguration> = {}): ModelConfiguration {
return {
@@ -85,6 +85,12 @@ describe('applyStrategyConfig', () => {
describe('MODEL_CONFIGURATION_VALIDATOR', () => {
it('accepts a config with eagernessPrompt', () => {
const result = MODEL_CONFIGURATION_VALIDATOR.validate(baseConfig({ eagernessPrompt: 'aggressionHighLow' }));
expect(result.error).toBeUndefined();
expect(result.content?.eagernessPrompt).toBe('aggressionHighLow');
});
it('keeps rejected-edit memory off by default', () => {
expect(DEFAULT_OPTIONS.memory).toBeUndefined();
expect(MODEL_CONFIGURATION_VALIDATOR.validate(baseConfig()).content?.memory).toBeUndefined();
@@ -114,6 +120,13 @@ describe('MODEL_CONFIGURATION_VALIDATOR', () => {
});
});
describe('isEagernessPrompt', () => {
it('recognizes the PatchBased02 aggression prompt option', () => {
expect(isEagernessPrompt({ ...DEFAULT_OPTIONS, promptingStrategy: PromptingStrategy.PatchBased02, eagernessPrompt: 'aggressionHighLow' })).toBe(true);
expect(isEagernessPrompt({ ...DEFAULT_OPTIONS, promptingStrategy: PromptingStrategy.PatchBased02 })).toBe(false);
});
});
describe('GlobalBudgetOptions', () => {
function gb(overrides: Partial<GlobalBudgetOptions> = {}): GlobalBudgetOptions {