"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var LiveOpenAIFetcher_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiveOpenAIFetcher = exports.CMDQuotaExceeded = exports.OpenAIFetcher = exports.ICompletionsOpenAIFetcherService = exports.CopilotUiKind = void 0;
exports.sanitizeRequestOptionTelemetry = sanitizeRequestOptionTelemetry;
exports.postProcessChoices = postProcessChoices;
const authentication_1 = require("../../../../../../platform/authentication/common/authentication");
const copilotAnnotations_1 = require("../../../../../../platform/completions-core/common/openai/copilotAnnotations");
const envService_1 = require("../../../../../../platform/env/common/envService");
const completionsFetchService_1 = require("../../../../../../platform/nesFetch/common/completionsFetchService");
const fetch_1 = require("../../../../../../platform/networking/common/fetch");
const telemetry_1 = require("../../../../../../platform/telemetry/common/telemetry");
const services_1 = require("../../../../../../util/common/services");
const assert_1 = require("../../../../../../util/vs/base/common/assert");
const cancellation_1 = require("../../../../../../util/vs/base/common/cancellation");
const stopwatch_1 = require("../../../../../../util/vs/base/common/stopwatch");
const uuid_1 = require("../../../../../../util/vs/base/common/uuid");
const instantiation_1 = require("../../../../../../util/vs/platform/instantiation/common/instantiation");
const copilotTokenManager_1 = require("../auth/copilotTokenManager");
const copilotTokenNotifier_1 = require("../auth/copilotTokenNotifier");
const config_1 = require("../config");
const iterableHelpers_1 = require("../helpers/iterableHelpers");
const logger_1 = require("../logger");
const networkConfiguration_1 = require("../networkConfiguration");
const networking_1 = require("../networking");
const progress_1 = require("../progress");
const repository_1 = require("../prompt/repository");
const telemetry_2 = require("../telemetry");
const async_1 = require("../util/async");
const runtimeMode_1 = require("../util/runtimeMode");
const unknown_1 = require("../util/unknown");
const openai_1 = require("./openai");
const logger = new logger_1.Logger('fetchCompletions');
var CopilotUiKind;
(function (CopilotUiKind) {
    CopilotUiKind["GhostText"] = "ghostText";
    CopilotUiKind["Panel"] = "synthesize";
})(CopilotUiKind || (exports.CopilotUiKind = CopilotUiKind = {}));
// Request helpers
function getProcessingTime(responseHeaders) {
    const reqIdStr = responseHeaders.get('openai-processing-ms');
    if (reqIdStr) {
        return parseInt(reqIdStr, 10);
    }
    return 0;
}
function uiKindToIntent(uiKind) {
    switch (uiKind) {
        case CopilotUiKind.GhostText:
            return 'copilot-ghost';
        case CopilotUiKind.Panel:
            return 'copilot-panel';
    }
}
exports.ICompletionsOpenAIFetcherService = (0, services_1.createServiceIdentifier)('ICompletionsOpenAIFetcherService');
/** An interface to abstract away the network request to OpenAI, allowing for
 * fake or mock implementations. It's deliberately injected relatively high
 * in the call stack to avoid having to reconstruct some of the lower-level details
 * of the OpenAI API.
 */
class OpenAIFetcher {
}
exports.OpenAIFetcher = OpenAIFetcher;
function getProxyEngineUrl(accessor, token, modelId, endpoint) {
    return (0, networkConfiguration_1.getEndpointUrl)(accessor, token, 'proxy', 'v1/engines', modelId, endpoint);
}
function sanitizeRequestOptionTelemetry(request, telemetryData, topLevelKeys, // top-level properties to exclude from standard telemetry
extraKeys // keys under the `extra` property to exclude from standard telemetry
) {
    for (const [key, value] of Object.entries(request)) {
        if (topLevelKeys.includes(key)) {
            continue;
        }
        let valueToLog = value;
        if (key === 'extra' && extraKeys) {
            const extra = { ...valueToLog };
            for (const extraKey of extraKeys) {
                delete extra[extraKey];
            }
            valueToLog = extra;
        }
        telemetryData.properties[`request.option.${key}`] = JSON.stringify(valueToLog) ?? 'undefined';
    }
}
function postProcessChoices(choices) {
    return (0, iterableHelpers_1.asyncIterableFilter)(choices, choice => choice.completionText.trim().length > 0);
}
exports.CMDQuotaExceeded = 'github.copilot.completions.quotaExceeded';
let LiveOpenAIFetcher = LiveOpenAIFetcher_1 = class LiveOpenAIFetcher extends OpenAIFetcher {
    #disabledReason;
    constructor(instantiationService, runtimeModeService, logTargetService, copilotTokenManager, statusReporter, authenticationService, fetchService, envService) {
        super();
        this.instantiationService = instantiationService;
        this.runtimeModeService = runtimeModeService;
        this.logTargetService = logTargetService;
        this.copilotTokenManager = copilotTokenManager;
        this.statusReporter = statusReporter;
        this.authenticationService = authenticationService;
        this.fetchService = fetchService;
        this.envService = envService;
    }
    async fetchAndStreamCompletions(params, baseTelemetryData, finishedCb, cancel) {
        if (this.#disabledReason) {
            return { type: 'canceled', reason: this.#disabledReason };
        }
        const endpoint = 'completions';
        const copilotToken = this.copilotTokenManager.token ?? await this.copilotTokenManager.getToken();
        const request = {
            prompt: params.prompt.prefix,
            suffix: params.prompt.suffix,
            max_tokens: (0, openai_1.getMaxSolutionTokens)(),
            temperature: (0, openai_1.getTemperatureForSamples)(this.runtimeModeService, params.count),
            top_p: (0, openai_1.getTopP)(),
            n: params.count,
            stop: (0, openai_1.getStops)(params.languageId),
            stream: true, // Always true: non streaming requests are not supported by this proxy
            extra: params.extra,
        };
        {
            if (params.requestLogProbs) {
                request.logprobs = 2; // Request that logprobs of 2 tokens (i.e. including the best alternative) be returned
            }
            const githubNWO = (0, repository_1.tryGetGitHubNWO)(params.repoInfo);
            if (githubNWO !== undefined) {
                request.nwo = githubNWO;
            }
            if (params.postOptions) {
                Object.assign(request, params.postOptions);
            }
            if (params.prompt.context && params.prompt.context.length > 0) {
                request.extra.context = params.prompt.context;
            }
            // Give a final opportunity to cancel the request before we send the request
            // This await line is necessary to allow the tests in extension/src/openai.test.ts to pass
            await (0, async_1.delay)(0);
            if (cancel?.isCancellationRequested) {
                return { type: 'canceled', reason: 'before fetch request' };
            }
        }
        {
            const prompt = params.prompt;
            const engineModelId = params.engineModelId;
            const ourRequestId = params.ourRequestId;
            const telemetryExp = baseTelemetryData;
            const uiKind = params.uiKind;
            const headers = params.headers;
            const uri = this.instantiationService.invokeFunction(getProxyEngineUrl, copilotToken, engineModelId, endpoint);
            const telemetryData = telemetryExp.extendedBy({
                endpoint: endpoint,
                engineName: engineModelId,
                uiKind: uiKind,
            }, (0, telemetry_2.telemetrizePromptLength)(prompt));
            // Skip prompt info (PII)
            sanitizeRequestOptionTelemetry(request, telemetryData, ['prompt', 'suffix'], ['context']);
            // The request ID we are passed in is sent in the request to the proxy, and included in our pre-request telemetry.
            // We hope (but do not rely on) that the model will use the same ID in the response, allowing us to correlate
            // the request and response.
            telemetryData.properties['headerRequestId'] = ourRequestId;
            this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.sent', telemetryData);
            const intent = uiKindToIntent(uiKind);
            let fullHeaders;
            {
                fullHeaders = {
                    ...headers,
                    ...this.instantiationService.invokeFunction(config_1.editorVersionHeaders),
                };
                fullHeaders['Openai-Organization'] = 'github-copilot';
                fullHeaders['X-Request-Id'] = ourRequestId;
                fullHeaders['VScode-SessionId'] = this.envService.sessionId;
                fullHeaders['VScode-MachineId'] = this.envService.machineId;
                fullHeaders['X-GitHub-Api-Version'] = config_1.apiVersion;
                if (intent) {
                    fullHeaders['OpenAI-Intent'] = intent;
                }
            }
            const requestSw = new stopwatch_1.StopWatch();
            const cancelToken = cancel ?? cancellation_1.CancellationToken.None;
            const res = await this.fetchService.fetch(uri, copilotToken.token, request, ourRequestId, cancelToken, fullHeaders).then(response => {
                if (response.isError() && response.err instanceof completionsFetchService_1.Completions.Unexpected && (0, networking_1.isInterruptedNetworkError)(response.err.error)) {
                    // disconnect and retry the request once if the connection was reset
                    this.instantiationService.invokeFunction(telemetry_2.telemetry, 'networking.disconnectAll');
                    return this.fetchService.disconnectAll().then(() => {
                        return this.fetchService.fetch(uri, copilotToken.token, request, ourRequestId, cancelToken, fullHeaders);
                    });
                }
                else {
                    return response;
                }
            });
            try {
                if (res.isError()) {
                    const err = res.err;
                    if (err instanceof completionsFetchService_1.Completions.RequestCancelled) {
                        // abort the request when the token is canceled
                        this.instantiationService.invokeFunction(telemetry_2.telemetry, 'networking.cancelRequest', telemetry_2.TelemetryData.createAndMarkAsIssued({ headerRequestId: ourRequestId }));
                        this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.cancel', telemetryData);
                        return { type: 'canceled', reason: 'during fetch request' };
                    }
                    else if (err instanceof completionsFetchService_1.Completions.UnsuccessfulResponse) {
                        const modelRequestId = (0, fetch_1.getRequestId)(err.headers);
                        telemetryData.extendWithRequestId(modelRequestId);
                        if (modelRequestId.serverExperiments) {
                            this.instantiationService.invokeFunction(accessor => accessor.get(telemetry_1.ITelemetryService).setSharedProperty('capi.assignmentcontext', modelRequestId.serverExperiments));
                        }
                        const totalTimeMs = requestSw.elapsed();
                        telemetryData.measurements.totalTimeMs = totalTimeMs;
                        telemetryData.properties.status = String(err.status);
                        logger.info(this.logTargetService, `Request ${ourRequestId} at <${uri}> finished with ${err.status} status after ${totalTimeMs}ms`);
                        logger.debug(this.logTargetService, 'request.response properties', telemetryData.properties);
                        logger.debug(this.logTargetService, 'request.response measurements', telemetryData.measurements);
                        logger.debug(this.logTargetService, 'prompt:', prompt);
                        this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.response', telemetryData);
                        return this.handleError(this.statusReporter, telemetryData, {
                            status: err.status,
                            text: err.text,
                            headers: err.headers,
                        }, copilotToken);
                    }
                    else if (err instanceof completionsFetchService_1.Completions.Unexpected) {
                        const error = err.error;
                        if ((0, networking_1.isAbortError)(error)) {
                            // If we cancelled a network request, we want to log a `request.cancel` instead of `request.error`
                            this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.cancel', telemetryData);
                            throw error;
                        }
                        this.statusReporter.setWarning((0, unknown_1.getKey)(error, 'message') ?? '');
                        const warningTelemetry = telemetryData.extendedBy({ error: 'Network exception' });
                        this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.shownWarning', warningTelemetry);
                        telemetryData.properties.message = String((0, unknown_1.getKey)(error, 'name') ?? '');
                        telemetryData.properties.code = String((0, unknown_1.getKey)(error, 'code') ?? '');
                        telemetryData.properties.errno = String((0, unknown_1.getKey)(error, 'errno') ?? '');
                        telemetryData.properties.type = String((0, unknown_1.getKey)(error, 'type') ?? '');
                        const totalTimeMs = requestSw.elapsed();
                        telemetryData.measurements.totalTimeMs = totalTimeMs;
                        logger.info(this.logTargetService, `Request ${ourRequestId} at <${uri}> rejected with ${String(error)} after ${totalTimeMs}ms`);
                        logger.debug(this.logTargetService, 'request.error properties', telemetryData.properties);
                        logger.debug(this.logTargetService, 'request.error measurements', telemetryData.measurements);
                        this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.error', telemetryData);
                        throw error;
                    }
                    else {
                        (0, assert_1.assertNever)(err);
                    }
                }
                const responseStream = res.val;
                {
                    // This ID is hopefully the one the same as ourRequestId, but it is not guaranteed.
                    // If they are different then we will override the original one we set in telemetryData above.
                    const modelRequestId = responseStream.requestId;
                    telemetryData.extendWithRequestId(modelRequestId);
                    if (modelRequestId.serverExperiments) {
                        this.instantiationService.invokeFunction(accessor => accessor.get(telemetry_1.ITelemetryService).setSharedProperty('capi.assignmentcontext', modelRequestId.serverExperiments));
                    }
                    // TODO: Add response length (requires parsing)
                    const totalTimeMs = requestSw.elapsed();
                    telemetryData.measurements.totalTimeMs = totalTimeMs;
                    const responseStatus = 200; // because otherwise it wouldn't be here
                    logger.info(this.logTargetService, `Request ${ourRequestId} at <${uri}> finished with ${responseStatus} status after ${totalTimeMs}ms`);
                    telemetryData.properties.status = String(responseStatus);
                    logger.debug(this.logTargetService, 'request.response properties', telemetryData.properties);
                    logger.debug(this.logTargetService, 'request.response measurements', telemetryData.measurements);
                    logger.debug(this.logTargetService, 'prompt:', prompt);
                    this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.response', telemetryData);
                }
                if (cancel?.isCancellationRequested) {
                    try {
                        // Destroy the stream so that the server is hopefully notified we don't want any more data
                        // and can cancel/forget about the request itself.
                        await responseStream.destroy();
                    }
                    catch (e) {
                        this.instantiationService.invokeFunction(acc => logger.exception(acc, e, `Error destroying stream`));
                    }
                    return { type: 'canceled', reason: 'after fetch request' };
                }
                const choices = LiveOpenAIFetcher_1.convertStreamToApiChoices(responseStream, finishedCb, baseTelemetryData, cancel);
                return {
                    type: 'success',
                    choices: postProcessChoices(choices),
                    getProcessingTime: () => getProcessingTime(responseStream.headers),
                };
            }
            finally {
                this.instantiationService.invokeFunction(telemetry_2.logEnginePrompt, prompt, telemetryData);
            }
        }
    }
    /**
     * @remarks exposed only for testing.
     */
    static async *convertStreamToApiChoices(resp, finishedCb, baseTelemetryData, cancel) {
        const createAPIChoice = (choiceIndex, completionText, finishReason, accumulator, blockFinished) => ({
            choiceIndex,
            completionText,
            requestId: resp.requestId,
            finishReason,
            tokens: accumulator.chunks,
            numTokens: accumulator.chunks.length,
            blockFinished,
            telemetryData: baseTelemetryData,
            clientCompletionId: (0, uuid_1.generateUuid)(),
            meanLogProb: undefined,
            meanAlternativeLogProb: undefined,
            copilotAnnotations: accumulator.annotations.current,
        });
        const completions = [];
        try {
            for await (const chunk of resp.stream) {
                if (cancel?.isCancellationRequested) {
                    return;
                }
                for (let i = 0; i < chunk.choices.length; i++) {
                    const chunkIdx = chunk.choices[i].index;
                    let completion = completions[chunkIdx];
                    if (completion === undefined) {
                        completion = { accumulator: new CompletionAccumulator(), isFinished: false, yielded: false };
                        completions[chunkIdx] = completion;
                    }
                    else if (completion.isFinished) {
                        // already finished, skip
                        continue;
                    }
                    const choice = chunk.choices[i];
                    completion.accumulator.append(choice);
                    // finish_reason determines whether the completion is finished by the LLM
                    const hasFinishReason = !!(chunk.choices[i].finish_reason);
                    // Only call finishedCb when there's a newline or finish_reason, matching SSEProcessor behavior.
                    // This optimization avoids calling finishedCb on every chunk which can be expensive.
                    const chunkText = choice.text ?? '';
                    const hasNewLine = chunkText.indexOf('\n') > -1;
                    let solutionDecision;
                    if (hasFinishReason || hasNewLine) {
                        // call finishedCb to determine whether the completion is finished by the client
                        solutionDecision = await finishedCb(completion.accumulator.responseSoFar, {
                            index: chunkIdx,
                            text: completion.accumulator.responseSoFar,
                            finished: hasFinishReason,
                            requestId: resp.requestId,
                            telemetryData: baseTelemetryData,
                            annotations: completion.accumulator.annotations,
                            getAPIJsonData: () => ({
                                text: completion.accumulator.responseSoFar,
                                tokens: completion.accumulator.chunks,
                                finish_reason: completion.accumulator.finishReason ?? 'stop', // @ulugbekna: logic to determine if last completion was accepted uses finish reason, so changing this `?? 'stop'` will change behavior of multiline completions
                                copilot_annotations: completion.accumulator.annotations.current,
                            }),
                        });
                        if (cancel?.isCancellationRequested) {
                            return;
                        }
                    }
                    // Determine whether to yield based on finish_reason or callback decision.
                    // When finish_reason is present, force yield & stop streaming (matching SSEProcessor).
                    if (hasFinishReason) {
                        if (solutionDecision === undefined || typeof solutionDecision !== 'object') {
                            solutionDecision = { yieldSolution: true, continueStreaming: false };
                        }
                        else {
                            solutionDecision.yieldSolution = true;
                            solutionDecision.continueStreaming = false;
                        }
                    }
                    if (solutionDecision !== undefined &&
                        (typeof solutionDecision === 'number' || solutionDecision.yieldSolution)) {
                        // mark as finished
                        const isFinished = hasFinishReason || typeof solutionDecision === 'number' || (solutionDecision !== undefined && !solutionDecision.continueStreaming);
                        completion.isFinished = isFinished;
                        const finishReason = chunk.choices[i].finish_reason;
                        if (finishReason) {
                            completion.accumulator.finishReason = finishReason;
                        }
                        const finishOffset = typeof solutionDecision === 'number'
                            ? solutionDecision
                            : (solutionDecision && solutionDecision.finishOffset !== undefined
                                ? solutionDecision.finishOffset
                                : undefined);
                        const completionText = finishOffset === undefined
                            ? completion.accumulator.responseSoFar
                            : completion.accumulator.responseSoFar.slice(0, finishOffset);
                        // Guard against double-yielding the same choice, matching
                        // SSEProcessor's `solution.yielded` flag. Without this,
                        // StreamedCompletionSplitter can yield the first segment
                        // (via yieldSolution+continueStreaming), and then
                        // finish_reason forces a second yield of the full text,
                        // which ends up cached as a spurious ghost text suggestion.
                        if (!completion.yielded) {
                            completion.yielded = true;
                            const apiChoice = createAPIChoice(chunkIdx, completionText, completion.accumulator.finishReason ?? 'stop', completion.accumulator, finishOffset !== undefined);
                            yield apiChoice;
                        }
                        if (cancel?.isCancellationRequested) {
                            return;
                        }
                    }
                }
            }
            // When the stream ends, call finishedCb with finished=true for any
            // completions that weren't individually finished. This matches
            // SSEProcessor.finishSolutions which gives the callback a final
            // chance to process / trim the complete text (e.g. StreamedCompletionSplitter.trimAll).
            for (const [chunkIdx, completion] of completions.entries()) {
                if (!completion.isFinished) {
                    await finishedCb(completion.accumulator.responseSoFar, {
                        index: chunkIdx,
                        text: completion.accumulator.responseSoFar,
                        finished: true,
                        requestId: resp.requestId,
                        telemetryData: baseTelemetryData,
                        annotations: completion.accumulator.annotations,
                        getAPIJsonData: () => ({
                            text: completion.accumulator.responseSoFar,
                            tokens: completion.accumulator.chunks,
                            finish_reason: completion.accumulator.finishReason ?? 'stop',
                            copilot_annotations: completion.accumulator.annotations.current,
                        }),
                    });
                    if (cancel?.isCancellationRequested) {
                        return;
                    }
                    // Skip if already yielded (matches SSEProcessor.finishSolutions)
                    if (completion.yielded) {
                        continue;
                    }
                    completion.yielded = true;
                    const apiChoice = createAPIChoice(chunkIdx, completion.accumulator.responseSoFar, completion.accumulator.finishReason ?? 'stop', // Matches original SSEProcessor behavior: convertToAPIJsonData defaults to 'stop'
                    completion.accumulator, false);
                    yield apiChoice;
                    if (cancel?.isCancellationRequested) {
                        return;
                    }
                }
            }
        }
        finally {
            // Destroy the network stream so the server is notified to stop sending data,
            // matching SSEProcessor's finally block behavior in processSSE.
            try {
                await resp.destroy();
            }
            catch {
                // ignore destroy errors
            }
        }
    }
    async handleError(statusReporter, telemetryData, response, copilotToken) {
        const text = await response.text();
        if (response.status === 402) {
            this.#disabledReason = 'monthly free code completions exhausted';
            const message = 'Completions limit reached';
            statusReporter.setError(message, {
                command: exports.CMDQuotaExceeded,
                title: 'Learn More',
            });
            const event = (0, copilotTokenNotifier_1.onCopilotToken)(this.authenticationService, t => {
                this.#disabledReason = undefined;
                if (!t.isCompletionsQuotaExceeded) {
                    statusReporter.forceNormal();
                    event.dispose();
                }
            });
            return { type: 'failed', reason: this.#disabledReason };
        }
        if (response.status === 466) {
            statusReporter.setError(text);
            logger.info(this.logTargetService, text);
            return { type: 'failed', reason: `client not supported: ${text}` };
        }
        if (isClientError(response) && !response.headers.get('x-github-request-id')) {
            const message = `Last response was a ${response.status} error and does not appear to originate from GitHub. Is a proxy or firewall intercepting this request? https://gh.io/copilot-firewall`;
            logger.error(this.logTargetService, message);
            statusReporter.setWarning(message);
            telemetryData.properties.error = `Response status was ${response.status} with no x-github-request-id header`;
        }
        else if (isClientError(response)) {
            logger.warn(this.logTargetService, `Response status was ${response.status}:`, text);
            statusReporter.setWarning(`Last response was a ${response.status} error: ${text}`);
            telemetryData.properties.error = `Response status was ${response.status}: ${text}`;
        }
        else {
            statusReporter.setWarning(`Last response was a ${response.status} error`);
            telemetryData.properties.error = `Response status was ${response.status}`;
        }
        telemetryData.properties.status = String(response.status);
        this.instantiationService.invokeFunction(telemetry_2.telemetry, 'request.shownWarning', telemetryData);
        // check for 4xx responses which will point to a forbidden
        if (response.status === 401 || response.status === 403) {
            // Token has expired or invalid, fetch a new one on next request
            // TODO(drifkin): these actions should probably happen in vsc specific code
            this.copilotTokenManager.resetToken(response.status);
            return { type: 'failed', reason: `token expired or invalid: ${response.status}` };
        }
        if (response.status === 429) {
            const rateLimitSeconds = 10;
            setTimeout(() => {
                this.#disabledReason = undefined;
            }, rateLimitSeconds * 1000);
            this.#disabledReason = 'rate limited';
            logger.warn(this.logTargetService, `Rate limited by server. Denying completions for the next ${rateLimitSeconds} seconds.`);
            return { type: 'failed', reason: this.#disabledReason };
        }
        if (response.status === 499) {
            logger.info(this.logTargetService, 'Cancelled by server');
            return { type: 'failed', reason: 'canceled by server' };
        }
        logger.error(this.logTargetService, 'Unhandled status from server:', response.status, text);
        return { type: 'failed', reason: `unhandled status from server: ${response.status} ${text}` };
    }
};
exports.LiveOpenAIFetcher = LiveOpenAIFetcher;
exports.LiveOpenAIFetcher = LiveOpenAIFetcher = LiveOpenAIFetcher_1 = __decorate([
    __param(0, instantiation_1.IInstantiationService),
    __param(1, runtimeMode_1.ICompletionsRuntimeModeService),
    __param(2, logger_1.ICompletionsLogTargetService),
    __param(3, copilotTokenManager_1.ICompletionsCopilotTokenManager),
    __param(4, progress_1.ICompletionsStatusReporter),
    __param(5, authentication_1.IAuthenticationService),
    __param(6, completionsFetchService_1.ICompletionsFetchService),
    __param(7, envService_1.IEnvService)
], LiveOpenAIFetcher);
function isClientError(response) {
    return response.status >= 400 && response.status < 500;
}
class CompletionAccumulator {
    constructor() {
        this._chunks = [];
        /** concatenated version of {_chunks} */
        this._responseSoFar = '';
        this._finishReason = null;
        this.annotations = new copilotAnnotations_1.StreamCopilotAnnotations();
    }
    get responseSoFar() {
        return this._responseSoFar;
    }
    get chunks() {
        return this._chunks;
    }
    set finishReason(value) {
        this._finishReason = value;
    }
    get finishReason() {
        return this._finishReason;
    }
    append(choice) {
        const chunk = choice.text;
        if (chunk) {
            this._chunks.push(chunk);
            this._responseSoFar = this._responseSoFar + chunk;
        }
        if (choice.copilot_annotations) {
            this.annotations.update(choice.copilot_annotations);
        }
    }
}
//# sourceMappingURL=fetch.js.map