Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ const toolsCalledInParallel = new Set<ToolName>([
ToolName.GetErrors,
ToolName.GetScmChanges,
ToolName.GetNotebookSummary,
ToolName.ReadCellOutput,
ToolName.InstallExtension,
ToolName.FetchWebPage,
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export class RunNotebookCellOutput extends PromptElement<IRunNotebookCellOutputP
|| item.mime === 'application/vnd.code.notebook.stderr'
|| item.mime === 'application/json');
if (textItem) {
if (getCharLimit(textItem.data.byteLength) > sizing.tokenBudget / this.props.sizeLimitRatio) {
if (textItem.data.byteLength > getCharLimit(sizing.tokenBudget / this.props.sizeLimitRatio)) {
return <Tag name={`cell-output`} attrs={{ mimeType: textItem.mime }}>
Output {index + 1}: Output is too large to be used as context in the language model, but the user should be able to see it in the notebook.<br />
<br />
Expand All @@ -394,7 +394,7 @@ export class RunNotebookCellOutput extends PromptElement<IRunNotebookCellOutputP
</Tag>;
}

const largeOutput = output.items.find((item) => getCharLimit(item.data.byteLength) > sizing.tokenBudget / this.props.sizeLimitRatio);
const largeOutput = output.items.find((item) => item.data.byteLength > getCharLimit(sizing.tokenBudget / this.props.sizeLimitRatio));
if (largeOutput) {
return <Tag name={`cell-output`} attrs={{ mimeType: largeOutput.mime }}>
Output {index + 1}: Output is too large to be used as context in the language model, but the user should be able to see it in the notebook.<br />
Expand Down
11 changes: 9 additions & 2 deletions extensions/copilot/src/extension/xtab/common/promptCrafting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ function appendWithNewLineIfNeeded(base: string, toAppend: string, minNewLines:
}

function getPostScript(strategy: PromptingStrategy | undefined, currentFilePath: string, aggressivenessLevel: AggressivenessLevel) {
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.`;

let postScript: string | undefined;
switch (strategy) {
case PromptingStrategy.PatchBased01:
Expand All @@ -340,13 +342,18 @@ function getPostScript(strategy: PromptingStrategy | undefined, currentFilePath:
case PromptingStrategy.Xtab275EditIntentShort:
case PromptingStrategy.Xtab275EditIntent:
case PromptingStrategy.Xtab275:
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. 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.`;
postScript = xtab275BasePostScript;
break;
case PromptingStrategy.XtabAggressiveness:
postScript = `<|aggressive|>${aggressivenessLevel}<|/aggressive|>`;
break;
case PromptingStrategy.Xtab275Aggressiveness:
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. 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.\n<|aggressive|>${aggressivenessLevel}<|/aggressive|>`;
postScript = `${xtab275BasePostScript}\n<|aggressive|>${aggressivenessLevel}<|/aggressive|>`;
break;
case PromptingStrategy.Xtab275AggressivenessHighLow:
postScript = aggressivenessLevel === AggressivenessLevel.Medium
? xtab275BasePostScript
: `${xtab275BasePostScript}\n<|aggressive|>${aggressivenessLevel}<|/aggressive|>`;
break;
case PromptingStrategy.PatchBased:
postScript = `Output a modified diff style format with the changes you want. Each change patch must start with \`<filename>:<line number>\` and then include some non empty "anchor lines" preceded by \`-\` and the new lines meant to replace them preceded by \`+\`. Put your changes in the order that makes the most sense, for example edits inside the code_to_edit region and near the user's <|cursor|> should always be prioritized. Output "<NO_EDIT>" if you don't have a good edit candidate.`;
Expand Down
1 change: 1 addition & 0 deletions extensions/copilot/src/extension/xtab/node/xtabProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,7 @@ export function pickSystemPrompt(promptingStrategy: xtabPromptOptions.PromptingS
case xtabPromptOptions.PromptingStrategy.Xtab275:
case xtabPromptOptions.PromptingStrategy.XtabAggressiveness:
case xtabPromptOptions.PromptingStrategy.Xtab275Aggressiveness:
case xtabPromptOptions.PromptingStrategy.Xtab275AggressivenessHighLow:
case xtabPromptOptions.PromptingStrategy.Xtab275EditIntent:
case xtabPromptOptions.PromptingStrategy.Xtab275EditIntentShort:
return xtab275SystemPrompt;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ describe('getUserPrompt', () => {
strategy: PromptingStrategy | undefined;
includeLineNumbers?: IncludeLineNumbersOption;
includePostScript?: boolean;
aggressivenessLevel?: AggressivenessLevel;
}): PromptPieces {
const currentDocLines = ['function foo() {', ' const x = 1;', ' return x;', '}', ''];
const docText = new StringText(currentDocLines.join('\n'));
Expand Down Expand Up @@ -592,7 +593,7 @@ describe('getUserPrompt', () => {
currentDocLines,
'<area>some code</area>',
undefined,
AggressivenessLevel.Medium,
opts.aggressivenessLevel ?? AggressivenessLevel.Medium,
new LintErrors(documentId, currentDocument, new TestLanguageDiagnosticsService()),
s => Math.ceil(s.length / 4),
promptOptions,
Expand Down Expand Up @@ -703,6 +704,67 @@ describe('getUserPrompt', () => {
// No line number prefix — cursor line starts directly with content
expect(prompt).toContain(PromptTags.CURSOR_LOCATION.start + '\n' + ' const ' + PromptTags.CURSOR + 'x = 1;' + '\n' + PromptTags.CURSOR_LOCATION.end);
});

describe('Xtab275AggressivenessHighLow', () => {
test('medium level does not include aggressive tag', () => {
const pieces = createTestPromptPieces({
cursorLine: 2, cursorColumn: 1,
strategy: PromptingStrategy.Xtab275AggressivenessHighLow,
aggressivenessLevel: AggressivenessLevel.Medium,
});
const { prompt } = getUserPrompt(pieces);

expect(prompt).toContain('Do not skip any lines. Do not be lazy.');
expect(prompt).not.toContain('<|aggressive|>');
expect(prompt).not.toContain('<|/aggressive|>');
});

test('high level includes aggressive tag with high', () => {
const pieces = createTestPromptPieces({
cursorLine: 2, cursorColumn: 1,
strategy: PromptingStrategy.Xtab275AggressivenessHighLow,
aggressivenessLevel: AggressivenessLevel.High,
});
const { prompt } = getUserPrompt(pieces);

expect(prompt).toContain('<|aggressive|>high<|/aggressive|>');
});

test('low level includes aggressive tag with low', () => {
const pieces = createTestPromptPieces({
cursorLine: 2, cursorColumn: 1,
strategy: PromptingStrategy.Xtab275AggressivenessHighLow,
aggressivenessLevel: AggressivenessLevel.Low,
});
const { prompt } = getUserPrompt(pieces);

expect(prompt).toContain('<|aggressive|>low<|/aggressive|>');
});

test('high level includes xtab275 base postscript text', () => {
const pieces = createTestPromptPieces({
cursorLine: 2, cursorColumn: 1,
strategy: PromptingStrategy.Xtab275AggressivenessHighLow,
aggressivenessLevel: AggressivenessLevel.High,
});
const { prompt } = getUserPrompt(pieces);

expect(prompt).toContain('Do not skip any lines. Do not be lazy.');
});
});

describe('Xtab275Aggressiveness', () => {
test('medium level includes aggressive tag (unlike HighLow variant)', () => {
const pieces = createTestPromptPieces({
cursorLine: 2, cursorColumn: 1,
strategy: PromptingStrategy.Xtab275Aggressiveness,
aggressivenessLevel: AggressivenessLevel.Medium,
});
const { prompt } = getUserPrompt(pieces);

expect(prompt).toContain('<|aggressive|>medium<|/aggressive|>');
});
});
});

describe('getUserPrompt — globalBudget cascade', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ describe('pickSystemPrompt', () => {
PromptingStrategy.PatchBased02,
PromptingStrategy.Xtab275,
PromptingStrategy.XtabAggressiveness,
PromptingStrategy.Xtab275Aggressiveness,
PromptingStrategy.Xtab275AggressivenessHighLow,
PromptingStrategy.Xtab275EditIntent,
PromptingStrategy.Xtab275EditIntentShort,
])('returns xtab275SystemPrompt for %s', (strategy) => {
Expand Down
11 changes: 9 additions & 2 deletions extensions/copilot/src/lib/node/chatLibMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ export interface INESProviderOptions {
readonly waitForTreatmentVariables?: boolean;
readonly undesiredModelsManager?: IUndesiredModelsManager;
readonly configOverrides?: Map<ConfigKeyType, unknown>;
/**
* Diagnostics provider used to enrich the NES prompt with the active file's
* lint/error context. When omitted, falls back to an in-memory
* {@link TestLanguageDiagnosticsService} that returns empty diagnostics
*/
readonly languageDiagnosticsService?: ILanguageDiagnosticsService;
}

export interface INESResult {
Expand Down Expand Up @@ -371,7 +377,7 @@ function setupServices(options: INESProviderOptions) {
builder.define(ILogService, new SyncDescriptor(LogServiceImpl, [[logTarget || new ConsoleLog(undefined, InternalLogLevel.Trace)]]));
builder.define(IGitExtensionService, new SyncDescriptor(NullGitExtensionService));
builder.define(ILanguageContextProviderService, new SyncDescriptor(NullLanguageContextProviderService));
builder.define(ILanguageDiagnosticsService, new SyncDescriptor(TestLanguageDiagnosticsService));
builder.define(ILanguageDiagnosticsService, options.languageDiagnosticsService || new SyncDescriptor(TestLanguageDiagnosticsService));
builder.define(IIgnoreService, new SyncDescriptor(NullIgnoreService));
builder.define(ISnippyService, new SyncDescriptor(NullSnippyService));
builder.define(IDomainService, new SyncDescriptor(DomainService));
Expand Down Expand Up @@ -743,6 +749,7 @@ export interface IInlineCompletionsProviderOptions {
readonly capiClientService?: ICAPIClientService;
readonly citationHandler?: IInlineCompletionsCitationHandler;
readonly configOverrides?: Map<ConfigKeyType, unknown>;
readonly languageDiagnosticsService?: ILanguageDiagnosticsService;
}

export type IGetInlineCompletionsOptions = Exclude<Partial<GetGhostTextOptions>, 'promptOnly'> & {
Expand Down Expand Up @@ -997,7 +1004,7 @@ function setupCompletionServices(options: IInlineCompletionsProviderOptions): II
}
});
builder.define(ILanguageContextProviderService, options.languageContextProvider ?? new NullLanguageContextProviderService());
builder.define(ILanguageDiagnosticsService, new SyncDescriptor(TestLanguageDiagnosticsService));
builder.define(ILanguageDiagnosticsService, options.languageDiagnosticsService || new SyncDescriptor(TestLanguageDiagnosticsService));
builder.define(IRequestLogger, new SyncDescriptor(NullRequestLogger));

return builder.seal();
Expand Down
95 changes: 95 additions & 0 deletions extensions/copilot/src/lib/vscode-node/test/nesProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { CopilotToken, createTestExtendedTokenInfo } from '../../../platform/aut
import { ICopilotTokenManager } from '../../../platform/authentication/common/copilotTokenManager';
import { DocumentId } from '../../../platform/inlineEdits/common/dataTypes/documentId';
import { MutableObservableWorkspace } from '../../../platform/inlineEdits/common/observableWorkspace';
import { ILanguageDiagnosticsService } from '../../../platform/languages/common/languageDiagnosticsService';
import { TestLanguageDiagnosticsService } from '../../../platform/languages/common/testLanguageDiagnosticsService';
import { FetchOptions, IAbortController, IHeaders, PaginationOptions, Response } from '../../../platform/networking/common/fetcherService';
import { IFetcher } from '../../../platform/networking/common/networking';
import { NullTerminalService } from '../../../platform/terminal/common/terminalService';
Expand All @@ -23,6 +25,8 @@ import { Emitter } from '../../../util/vs/base/common/event';
import { URI } from '../../../util/vs/base/common/uri';
import { StringEdit, StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit';
import { OffsetRange } from '../../../util/vs/editor/common/core/ranges/offsetRange';
import { ensureDependenciesAreSet } from '../../../util/vs/editor/common/core/text/positionToOffset';
import { DiagnosticSeverity, Range } from '../../../vscodeTypes';
import { createNESProvider, ILogTarget, ITelemetrySender, LogLevel } from '../../node/chatLibMain';


Expand Down Expand Up @@ -208,4 +212,95 @@ describe('NESProvider Facade', () => {
const errorLogs = logTarget.logs.filter(l => l.level === LogLevel.Error);
assert.strictEqual(errorLogs.length, 0, `Unexpected error logs: ${JSON.stringify(errorLogs, null, 2)}`);
});

describe('languageDiagnosticsService injection', () => {

const sentinelMessage = 'INJECTED_DIAG_SENTINEL_DO_NOT_MATCH_ANYWHERE_ELSE';

async function runNESRequest(diagnosticsService: ILanguageDiagnosticsService | undefined): Promise<{ payload: string }> {
const docUri = URI.file('/test/test.ts');
const workspace = new MutableObservableWorkspace();
const doc = workspace.addDocument({
id: DocumentId.create(docUri.toString()),
initialValue: outdent`
class Point {
constructor(
private readonly x: number,
private readonly y: number,
) { }
getDistance() {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
}

const myPoint = new Point(0, 1);`.trimStart()
});
doc.setSelection([new OffsetRange(1, 1)], undefined);

const fetcher = new TestFetcher({
'/models': JSON.stringify({ models: [] }),
'/chat/completions': await fs.readFile(path.join(__dirname, 'nesProvider.reply.txt'), 'utf8'),
});

const nextEditProvider = createNESProvider({
workspace,
fetcher,
copilotTokenManager: new TestCopilotTokenManager(),
telemetrySender: new TestTelemetrySender(),
terminalService: new NullTerminalService(),
logTarget: new TestLogTarget(),
languageDiagnosticsService: diagnosticsService,
});

nextEditProvider.updateTreatmentVariables({
'config.github.copilot.chat.advanced.inlineEdits.xtabProvider.defaultModelConfigurationString': JSON.stringify({
modelName: 'xtab-test',
promptingStrategy: 'copilotNesXtab',
includeTagsInCurrentFile: false,
lintOptions: {
tagName: 'linter diagnostics',
warnings: 'yes',
showCode: 'no',
maxLints: 5,
maxLineDistance: 1000,
nRecentFiles: 0,
},
}),
});

doc.applyEdit(StringEdit.insert(11, '3D'));

await nextEditProvider.getNextEdit(doc.id.toUri(), CancellationToken.None);

const chatRequest = fetcher.requests.find(r => r.url.endsWith('/chat/completions'));
assert.ok(chatRequest, `Expected a /chat/completions request, got: ${fetcher.requests.map(r => r.url).join(', ')}`);

nextEditProvider.dispose();

return { payload: JSON.stringify(chatRequest.options.json) };
}

it('forwards the provided ILanguageDiagnosticsService into NES request construction', async () => {
ensureDependenciesAreSet();

const diagnosticsService = new TestLanguageDiagnosticsService();
diagnosticsService.setDiagnostics(URI.file('/test/test.ts'), [{
message: sentinelMessage,
range: new Range(0, 0, 0, 5),
severity: DiagnosticSeverity.Error,
}]);

const { payload } = await runNESRequest(diagnosticsService);

expect(payload).toContain(sentinelMessage);
});

it('falls back to an empty diagnostics source when none is provided', async () => {
ensureDependenciesAreSet();

const { payload } = await runNESRequest(undefined);

expect(payload).not.toContain(sentinelMessage);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,11 @@ export enum PromptingStrategy {
* Xtab275 prompt + aggressiveness level tag.
*/
Xtab275Aggressiveness = 'xtab275Aggressiveness',
/**
* Xtab275 prompt + aggressiveness level tag only for high/low.
* Medium uses the plain xtab275 prompt (no tag).
*/
Xtab275AggressivenessHighLow = 'xtab275AggressivenessHighLow',
PatchBased = 'patchBased',
PatchBased01 = 'patchBased01',
PatchBased02 = 'patchBased02',
Expand All @@ -359,6 +364,7 @@ export function isPromptingStrategy(value: string): value is PromptingStrategy {
export function isAggressivenessStrategy(strategy: PromptingStrategy | undefined): boolean {
return strategy === PromptingStrategy.XtabAggressiveness
|| strategy === PromptingStrategy.Xtab275Aggressiveness
|| strategy === PromptingStrategy.Xtab275AggressivenessHighLow
|| strategy === PromptingStrategy.Xtab275EditIntent
|| strategy === PromptingStrategy.Xtab275EditIntentShort;
}
Expand All @@ -382,6 +388,7 @@ export namespace ResponseFormat {
case PromptingStrategy.Xtab275:
case PromptingStrategy.XtabAggressiveness:
case PromptingStrategy.Xtab275Aggressiveness:
case PromptingStrategy.Xtab275AggressivenessHighLow:
return ResponseFormat.EditWindowOnly;
case PromptingStrategy.PatchBased:
case PromptingStrategy.PatchBased01:
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@microsoft/mxc-sdk": "0.2.0",
"@parcel/watcher": "^2.5.6",
"@types/semver": "^7.5.8",
"@vscode/codicons": "^0.0.46-12",
"@vscode/codicons": "^0.0.46-13",
"@vscode/copilot-api": "^0.4.0",
"@vscode/deviceid": "^0.1.1",
"@vscode/diff": "^0.0.2-0",
Expand Down
Loading
Loading