Path: blob/main/extensions/copilot/src/platform/chat/test/common/streamingMockChatMLFetcher.ts
13405 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 { CancellationToken } from '../../../../util/vs/base/common/cancellation';6import { Event } from '../../../../util/vs/base/common/event';7import { IChatMLFetcher, IFetchMLOptions } from '../../common/chatMLFetcher';8import { ChatFetchResponseType, ChatResponse, ChatResponses } from '../../common/commonTypes';910/**11* A mock IChatMLFetcher that simulates streaming LLM responses by calling `finishedCb`12* incrementally for each configured line. This enables testing the full streaming pipeline13* in XtabProvider's `streamEdits` without hitting a real endpoint.14*/15export class StreamingMockChatMLFetcher implements IChatMLFetcher {16declare readonly _serviceBrand: undefined;17readonly onDidMakeChatMLRequest = Event.None;1819private _responseLines: string[] | undefined;20private _errorResponse: ChatResponse | undefined;21private _responseQueue: ChatResponse[] = [];22private _callCount = 0;23private _capturedOptions: IFetchMLOptions[] = [];2425/**26* Configure the next fetchOne call to stream lines of text via `finishedCb`.27*/28setStreamingLines(lines: string[]): void {29this._responseLines = lines;30this._errorResponse = undefined;31}3233/**34* Configure the next fetchOne call to return an error response.35*/36setErrorResponse(response: ChatResponse): void {37this._errorResponse = response;38this._responseLines = undefined;39}4041/**42* Enqueue ordered responses. Each `fetchOne` call dequeues from the front.43* If the queue is empty, falls back to `_responseLines` / `_errorResponse`.44*/45enqueueResponse(response: ChatResponse): void {46this._responseQueue.push(response);47}4849/**50* Get all captured request options from previous calls.51*/52get capturedOptions(): readonly IFetchMLOptions[] {53return this._capturedOptions;54}5556/**57* Get how many times fetchOne was called.58*/59get callCount(): number {60return this._callCount;61}6263/**64* Reset call tracking state.65*/66resetTracking(): void {67this._callCount = 0;68this._capturedOptions = [];69}7071async fetchOne(options: IFetchMLOptions, _token: CancellationToken): Promise<ChatResponse> {72this._callCount++;73this._capturedOptions.push(options);7475// Check queued responses first76if (this._responseQueue.length > 0) {77const queuedResponse = this._responseQueue.shift()!;78// For success responses, still call finishedCb if available79if (queuedResponse.type === ChatFetchResponseType.Success && options.finishedCb) {80await options.finishedCb(queuedResponse.value, 0, { text: queuedResponse.value });81}82return queuedResponse;83}8485if (this._errorResponse) {86return this._errorResponse;87}8889const lines = this._responseLines ?? [];90const fullText = lines.join('\n');9192// call finishedCb for each line incrementally to simulate streaming93if (options.finishedCb) {94let soFar = '';95for (let i = 0; i < lines.length; i++) {96if (i > 0) {97soFar += '\n';98}99soFar += lines[i];100await options.finishedCb(soFar, 0, { text: (i > 0 ? '\n' : '') + lines[i] });101}102}103104return {105type: ChatFetchResponseType.Success,106requestId: 'test-request-id',107serverRequestId: 'test-server-request-id',108usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, prompt_tokens_details: { cached_tokens: 0 } },109value: fullText,110resolvedModel: 'test-model',111};112}113114async fetchMany(options: IFetchMLOptions, token: CancellationToken): Promise<ChatResponses> {115const response = await this.fetchOne(options, token);116if (response.type === ChatFetchResponseType.Success) {117return { ...response, value: [response.value] };118}119return response;120}121}122123124