Path: blob/main/extensions/copilot/test/pipeline/promptStep.ts
13389 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 { Raw } from '@vscode/prompt-tsx';6import { IRecordingInformation, ObservableWorkspaceRecordingReplayer } from '../../src/extension/inlineEdits/common/observableWorkspaceRecordingReplayer';7import { createNextEditProvider } from '../../src/extension/inlineEdits/node/createNextEditProvider';8import { DebugRecorder } from '../../src/extension/inlineEdits/node/debugRecorder';9import { NESInlineCompletionContext, NextEditProvider } from '../../src/extension/inlineEdits/node/nextEditProvider';10import { NextEditProviderTelemetryBuilder } from '../../src/extension/inlineEdits/node/nextEditProviderTelemetry';11import { ConfigKey, IConfigurationService } from '../../src/platform/configuration/common/configurationService';12import { IGitExtensionService } from '../../src/platform/git/common/gitExtensionService';13import { InlineEditRequestLogContext } from '../../src/platform/inlineEdits/common/inlineEditLogContext';14import { ObservableGit } from '../../src/platform/inlineEdits/common/observableGit';15import { NesHistoryContextProvider } from '../../src/platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';16import { NesXtabHistoryTracker } from '../../src/platform/inlineEdits/common/workspaceEditTracker/nesXtabHistoryTracker';17import { INotebookService } from '../../src/platform/notebook/common/notebookService';18import { IExperimentationService } from '../../src/platform/telemetry/common/nullExperimentationService';19import { IWorkspaceService } from '../../src/platform/workspace/common/workspaceService';20import { CancellationToken } from '../../src/util/vs/base/common/cancellation';21import { generateUuid } from '../../src/util/vs/base/common/uuid';22import { IInstantiationService, ServicesAccessor } from '../../src/util/vs/platform/instantiation/common/instantiation';2324export interface IGeneratedPrompt {25readonly system: string;26readonly user: string;27}2829function extractTextContent(message: Raw.ChatMessage): string {30const textPart = message.content.find(p => p.type === Raw.ChatCompletionContentPartKind.Text);31return textPart && 'text' in textPart ? textPart.text : '';32}3334function extractPromptParts(messages: Raw.ChatMessage[]): { system: string; user: string } {35const systemMsg = messages.find(m => m.role === Raw.ChatRole.System);36const userMsg = messages.find(m => m.role === Raw.ChatRole.User);37return {38system: systemMsg ? extractTextContent(systemMsg) : '',39user: userMsg ? extractTextContent(userMsg) : '',40};41}4243/**44* Generate a prompt from a recording using the NES pipeline.45* Uses MockChatMLFetcher (via DI services) to capture the prompt without calling a real model.46*/47export async function generatePromptFromRecording(48accessor: ServicesAccessor,49recordingInfo: IRecordingInformation,50): Promise<IGeneratedPrompt | { error: string }> {51const instaService = accessor.get(IInstantiationService);52const configService = accessor.get(IConfigurationService);53const expService = accessor.get(IExperimentationService);54const gitExtensionService = accessor.get(IGitExtensionService);55const notebookService = accessor.get(INotebookService);56const workspaceService = accessor.get(IWorkspaceService);5758const replayer = new ObservableWorkspaceRecordingReplayer(recordingInfo);59const obsGit = instaService.createInstance(ObservableGit);60const historyContextProvider = new NesHistoryContextProvider(replayer.workspace, obsGit);61const nesXtabHistoryTracker = new NesXtabHistoryTracker(replayer.workspace, undefined, configService, expService);62const debugRecorder = new DebugRecorder(replayer.workspace);6364try {65const { lastDocId } = replayer.replay();6667const nextEditProviderId = configService.getExperimentBasedConfig(ConfigKey.TeamInternal.InlineEditsProviderId, expService);68const statelessNextEditProvider = createNextEditProvider(nextEditProviderId, instaService);69const nextEditProvider = instaService.createInstance(70NextEditProvider, replayer.workspace, statelessNextEditProvider,71historyContextProvider, nesXtabHistoryTracker, debugRecorder,72);7374const historyContext = historyContextProvider.getHistoryContext(lastDocId);75if (!historyContext) {76nextEditProvider.dispose();77return { error: `No history context for document ${lastDocId}` };78}7980const activeDocument = historyContext.getMostRecentDocument();81const context: NESInlineCompletionContext = {82triggerKind: 1,83selectedCompletionInfo: undefined,84requestUuid: generateUuid(),85requestIssuedDateTime: Date.now(),86earliestShownDateTime: Date.now() + 200,87enforceCacheDelay: false,88};89const logContext = new InlineEditRequestLogContext(activeDocument.docId.toString(), 1, context);90const telemetryBuilder = new NextEditProviderTelemetryBuilder(91gitExtensionService, notebookService, workspaceService,92nextEditProvider.ID, replayer.workspace.getDocument(activeDocument.docId),93);9495// Prompt is captured in logContext; model call is mocked via DI.96// The provider may throw during response streaming (after prompt capture)97// since we use a mock fetcher. We only tolerate errors once the prompt98// has been captured in logContext; otherwise we rethrow so the outer99// handler can surface a useful error message.100try {101await nextEditProvider.getNextEdit(102activeDocument.docId, context, logContext,103CancellationToken.None, telemetryBuilder.nesBuilder,104);105} catch (err) {106if (!logContext.rawMessages) {107// Error occurred before the prompt was captured; let the outer108// handler report this as a failure.109throw err;110}111// Expected: mock fetcher response causes downstream errors after112// the prompt has already been captured in logContext.113} finally {114nextEditProvider.dispose();115telemetryBuilder.dispose();116}117118const rawMessages = logContext.rawMessages;119if (!rawMessages) {120return { error: 'Prompt was not captured in logContext (pipeline returned early before prompt construction)' };121}122123const { system, user } = extractPromptParts(rawMessages);124return { system, user };125126} catch (e) {127const detail = e instanceof Error && e.stack128? e.stack.split('\n').slice(0, 3).join(' | ')129: (e instanceof Error ? e.message : String(e));130return { error: `Prompt generation failed: ${detail}` };131} finally {132historyContextProvider.dispose();133obsGit.dispose();134replayer.dispose();135}136}137138139