Path: blob/main/extensions/copilot/src/extension/test/node/intent.spec.ts
13399 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 assert from 'assert';6import { beforeEach, suite, test } from 'vitest';7import { IResponsePart } from '../../../platform/chat/common/chatMLFetcher';8import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';9import { MockEndpoint } from '../../../platform/endpoint/test/node/mockEndpoint';10import { ITestingServicesAccessor } from '../../../platform/test/node/services';11import { ChatResponseStreamImpl } from '../../../util/common/chatResponseStreamImpl';12import { createTextDocumentData } from '../../../util/common/test/shims/textDocument';13import { AsyncIterableObject } from '../../../util/vs/base/common/async';14import { CancellationToken } from '../../../util/vs/base/common/cancellation';15import { URI } from '../../../util/vs/base/common/uri';16import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';17import { ChatResponseTextEditPart, Range, Selection, TextEdit, Uri } from '../../../vscodeTypes';18import { ISessionTurnStorage, OutcomeAnnotation } from '../../inlineChat/node/promptCraftingTypes';19import { ChatVariablesCollection } from '../../prompt/common/chatVariablesCollection';20import { PromptReference, getUniqueReferences } from '../../prompt/common/conversation';21import { IDocumentContext } from '../../prompt/node/documentContext';22import { IResponseProcessorContext, ReplyInterpreterMetaData } from '../../prompt/node/intents';23import { PromptRenderer } from '../../prompts/node/base/promptRenderer';24import { InlineChatEditCodePrompt } from '../../prompts/node/inline/inlineChatEditCodePrompt';25import { createExtensionUnitTestingServices } from './services';262728suite('Intent Streaming', function () {2930let accessor: ITestingServicesAccessor;3132beforeEach(function () {33accessor = createExtensionUnitTestingServices().createTestingAccessor();34});3536test.skip('[Bug] Stream processing may never terminate if model responds with something other than an edit #2080', async function () {373839const data = createTextDocumentData(URI.from({ scheme: 'test', path: '/path/file.txt' }), 'Hello', 'fooLang');40const doc = TextDocumentSnapshot.create(data.document);4142const context: IDocumentContext = {43document: doc,44language: { languageId: doc.languageId, lineComment: { start: '//' } },45fileIndentInfo: undefined,46wholeRange: new Range(0, 0, 1, 0),47selection: new Selection(0, 0, 0, 0),48};4950const endpoint = accessor.get(IInstantiationService).createInstance(MockEndpoint, undefined);51const progressReporter = { report() { } };52const renderer = PromptRenderer.create(accessor.get(IInstantiationService), endpoint, InlineChatEditCodePrompt, {53documentContext: context,54promptContext: {55query: 'hello',56chatVariables: new ChatVariablesCollection([]),57history: [],58}59});60const result = await renderer.render(progressReporter, CancellationToken.None);61const replyInterpreter = result.metadata.get(ReplyInterpreterMetaData)!.replyInterpreter;62const stream = new ChatResponseStreamImpl((value) => {63if (value instanceof ChatResponseTextEditPart) {64values.push(...value.edits);65}66}, () => { }, undefined, undefined, undefined, () => Promise.resolve(undefined));67const values: TextEdit[] = [];6869const part: IResponsePart = {70delta: {71text: 'What can be done'72}73};74const context2: IResponseProcessorContext = {75addAnnotations: function (annotations: OutcomeAnnotation[]): void {76// nothing77},78storeInInlineSession: function (store: ISessionTurnStorage): void {79// nothing80},81chatSessionId: '',82turn: null!,83messages: []84};85await replyInterpreter.processResponse(context2, AsyncIterableObject.fromArray([part]), stream, CancellationToken.None);8687assert.strictEqual(values.length, 0);88});89});9091suite('Reference Processing', function () {92test('combines adjacent lines and full file overlap', async () => {93const uri1 = Uri.file('1.txt');94const uri2 = Uri.file('2.txt');95const uri3 = Uri.file('3.txt');9697const references = [98new PromptReference({99uri: uri1,100range: new Range(0, 0, 2, 0),101}), new PromptReference({102uri: uri1,103range: new Range(3, 0, 4, 0),104}), new PromptReference({105uri: uri1,106range: new Range(5, 0, 7, 0),107}), new PromptReference({108uri: uri2,109range: new Range(0, 0, 4, 0),110}), new PromptReference(uri3),111new PromptReference({112uri: uri3,113range: new Range(0, 0, 4, 0),114})115];116117const result = getUniqueReferences(references);118119assert.deepEqual(result,120[121new PromptReference({122uri: uri1,123range: new Range(0, 0, 7, 0),124}), new PromptReference({125uri: uri2,126range: new Range(0, 0, 4, 0),127}), new PromptReference(uri3)128]);129});130131test('combines overlaping ranges', async () => {132const uri1 = Uri.file('1.txt');133const uri2 = Uri.file('2.txt');134135const references = [136new PromptReference({137uri: uri1,138range: new Range(0, 0, 2, 0),139}), new PromptReference({140uri: uri1,141range: new Range(5, 0, 10, 0),142}), new PromptReference({143uri: uri1,144range: new Range(3, 0, 6, 0),145}), new PromptReference({146uri: uri2,147range: new Range(1, 0, 4, 0),148}), new PromptReference({149uri: uri2,150range: new Range(0, 0, 5, 0),151})152];153154const result = getUniqueReferences(references);155156assert.deepEqual(result,157[158new PromptReference({159uri: uri1,160range: new Range(0, 0, 10, 0),161}), new PromptReference({162uri: uri2,163range: new Range(0, 0, 5, 0),164})165166]);167});168169test('removes duplicates', async () => {170const uri1 = Uri.file('1.txt');171const uri2 = Uri.file('2.txt');172173const references = [174new PromptReference({175uri: uri1,176range: new Range(3, 0, 4, 0),177}), new PromptReference({178uri: uri1,179range: new Range(3, 0, 4, 0),180}), new PromptReference({181uri: uri2,182range: new Range(3, 0, 4, 0),183}), new PromptReference({184uri: uri2,185range: new Range(3, 0, 4, 0),186})187];188189const result = getUniqueReferences(references);190191192assert.deepEqual(result, [193new PromptReference({194uri: uri1,195range: new Range(3, 0, 4, 0),196}), new PromptReference({197uri: uri2,198range: new Range(3, 0, 4, 0),199}),200201]);202});203204205206207208test('leaves distinct ranges alone, but sorts them', async () => {209const uri1 = Uri.file('1.txt');210const uri2 = Uri.file('2.txt');211212const references = [213new PromptReference({214uri: uri1,215range: new Range(7, 0, 10, 0),216}),217new PromptReference({218uri: uri2,219range: new Range(4, 0, 5, 0),220}),221new PromptReference({222uri: uri1,223range: new Range(0, 0, 2, 0),224}), new PromptReference({225uri: uri1,226range: new Range(4, 0, 5, 0),227}),228];229230const result = getUniqueReferences(references);231232233assert.deepEqual(result, [234new PromptReference({235uri: uri1,236range: new Range(0, 0, 2, 0),237}), new PromptReference({238uri: uri1,239range: new Range(4, 0, 5, 0),240}),241new PromptReference({242uri: uri1,243range: new Range(7, 0, 10, 0),244}),245new PromptReference({246uri: uri2,247range: new Range(4, 0, 5, 0),248}),249]);250});251});252253254