"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); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChatWebSocketManager = exports.NullChatWebSocketManager = exports.IChatWebSocketManager = void 0;
exports.isCAPIWebSocketError = isCAPIWebSocketError;
const services_1 = require("../../../util/common/services");
const errors_1 = require("../../../util/vs/base/common/errors");
const event_1 = require("../../../util/vs/base/common/event");
const lifecycle_1 = require("../../../util/vs/base/common/lifecycle");
const configurationService_1 = require("../../configuration/common/configurationService");
const capiClient_1 = require("../../endpoint/common/capiClient");
const logService_1 = require("../../log/common/logService");
const telemetry_1 = require("../../telemetry/common/telemetry");
const fetcherService_1 = require("../common/fetcherService");
const responsesApi_1 = require("../../endpoint/node/responsesApi");
const chatWebSocketTelemetry_1 = require("./chatWebSocketTelemetry");
exports.IChatWebSocketManager = (0, services_1.createServiceIdentifier)('IChatWebSocketManager');
/**
 * No-op implementation for contexts where WebSocket is not available (web, tests, chat-lib).
 */
class NullChatWebSocketManager {
    getOrCreateConnection(_conversationId, _headers, _initiatingRequestId) {
        throw new Error('WebSocket not available');
    }
    hasActiveConnection(_conversationId) { return false; }
    getStatefulMarker(_conversationId) { return undefined; }
    getSummarizedAtRoundId(_conversationId) { return undefined; }
    closeConnection(_conversationId) { }
    closeAll() { }
}
exports.NullChatWebSocketManager = NullChatWebSocketManager;
function isCAPIWebSocketError(event) {
    return event.type === 'error' && 'error' in event && typeof event.error?.code === 'string';
}
const streamTerminatingOutcomes = {
    'response.completed': 'completed',
    'response.failed': 'response_failed',
    'response.incomplete': 'response_incomplete',
    'response.cancelled': 'response_cancelled',
    'error': 'upstream_error',
};
function getStreamTerminatingOutcome(event) {
    return streamTerminatingOutcomes[event.type];
}
let ChatWebSocketManager = class ChatWebSocketManager extends lifecycle_1.Disposable {
    constructor(_logService, _capiClientService, _telemetryService, _configurationService) {
        super();
        this._logService = _logService;
        this._capiClientService = _capiClientService;
        this._telemetryService = _telemetryService;
        this._configurationService = _configurationService;
        this._connections = new Map();
    }
    getOrCreateConnection(conversationId, headers, initiatingRequestId) {
        const existing = this._connections.get(conversationId);
        // Reuse the connection if it's still open, even across turns.
        if (existing?.isOpen) {
            return existing;
        }
        if (existing) {
            this._logService.debug(`[ChatWebSocketManager] Replacing closed connection for conversation ${conversationId}`);
            existing.dispose();
            this._connections.delete(conversationId);
        }
        const connection = new ChatWebSocketConnection(this._capiClientService, this._logService, this._telemetryService, this._configurationService, conversationId, headers, initiatingRequestId);
        this._logService.debug(`[ChatWebSocketManager] Creating new connection for conversation ${conversationId}`);
        this._connections.set(conversationId, connection);
        // Remove from map when disposed externally
        connection.onDidDispose(() => {
            const entry = this._connections.get(conversationId);
            if (entry === connection) {
                this._connections.delete(conversationId);
            }
        });
        return connection;
    }
    hasActiveConnection(conversationId) {
        const connection = this._connections.get(conversationId);
        return !!connection?.isOpen;
    }
    getStatefulMarker(conversationId) {
        const connection = this._connections.get(conversationId);
        return connection?.isOpen ? connection.statefulMarker : undefined;
    }
    getSummarizedAtRoundId(conversationId) {
        const connection = this._connections.get(conversationId);
        return connection?.isOpen ? connection.summarizedAtRoundId : undefined;
    }
    closeConnection(conversationId) {
        const connection = this._connections.get(conversationId);
        if (connection) {
            if (connection.hasActiveRequest) {
                this._logService.warn(`[ChatWebSocketManager] Closing connection for conversation ${conversationId} while turn ${connection.turnId} still has an active request`);
            }
            else {
                this._logService.debug(`[ChatWebSocketManager] Closing connection for conversation ${conversationId}`);
            }
            connection.dispose();
            this._connections.delete(conversationId);
        }
    }
    closeAll() {
        for (const connection of this._connections.values()) {
            connection.dispose();
        }
        this._connections.clear();
    }
    dispose() {
        this.closeAll();
        super.dispose();
    }
};
exports.ChatWebSocketManager = ChatWebSocketManager;
exports.ChatWebSocketManager = ChatWebSocketManager = __decorate([
    __param(0, logService_1.ILogService),
    __param(1, capiClient_1.ICAPIClientService),
    __param(2, telemetry_1.ITelemetryService),
    __param(3, configurationService_1.IConfigurationService)
], ChatWebSocketManager);
function wsCloseCodeToString(code) {
    switch (code) {
        case 1000: return 'Normal Closure';
        case 1001: return 'Going Away';
        case 1002: return 'Protocol Error';
        case 1003: return 'Unsupported Data';
        case 1005: return 'No Status Received';
        case 1006: return 'Abnormal Closure';
        case 1007: return 'Invalid Payload';
        case 1008: return 'Policy Violation';
        case 1009: return 'Message Too Big';
        case 1010: return 'Missing Extension';
        case 1011: return 'Internal Error';
        case 1012: return 'Service Restart';
        case 1013: return 'Try Again Later';
        case 1014: return 'Bad Gateway';
        case 1015: return 'TLS Handshake Failed';
        default: return 'Unknown';
    }
}
class ChatWebSocketConnection extends lifecycle_1.Disposable {
    constructor(_capiClientService, _logService, _telemetryService, _configurationService, _conversationId, _headers, _initiatingRequestId) {
        super();
        this._capiClientService = _capiClientService;
        this._logService = _logService;
        this._telemetryService = _telemetryService;
        this._configurationService = _configurationService;
        this._conversationId = _conversationId;
        this._headers = _headers;
        this._initiatingRequestId = _initiatingRequestId;
        this._state = 2 /* ConnectionState.Closed */;
        this._onDidDispose = this._register(new event_1.Emitter());
        this.onDidDispose = this._onDidDispose.event;
        this._totalSentMessageCount = 0;
        this._totalReceivedMessageCount = 0;
        this._totalSentCharacters = 0;
        this._totalReceivedCharacters = 0;
        this._responseHeaders = new fetcherService_1.HeadersImpl({});
        this._hadActiveRequest = false;
    }
    get isOpen() {
        return this._state === 1 /* ConnectionState.Open */ && !!this._ws;
    }
    get hasActiveRequest() {
        return !!this._activeRequest;
    }
    get turnId() {
        return this._turnId;
    }
    get statefulMarker() {
        return this._statefulMarker;
    }
    get summarizedAtRoundId() {
        return this._summarizedAtRoundId;
    }
    get responseHeaders() {
        return this._responseHeaders;
    }
    get responseStatusCode() {
        return this._responseStatusCode;
    }
    get responseStatusText() {
        return this._responseStatusText;
    }
    get gitHubRequestId() {
        return this._responseHeaders.get('x-github-request-id') || '';
    }
    async connect() {
        if (this._state === 1 /* ConnectionState.Open */) {
            return;
        }
        this._state = 0 /* ConnectionState.Connecting */;
        this._connectStartTime = Date.now();
        this._logService.debug(`[ChatWebSocketManager] Connecting WebSocket for conversation ${this._conversationId}`);
        const connection = await this._capiClientService.createResponsesWebSocket({
            headers: this._headers,
        });
        return new Promise((resolve, reject) => {
            const ws = connection.webSocket;
            const onOpen = () => {
                cleanup();
                this._state = 1 /* ConnectionState.Open */;
                this._connectedTime = Date.now();
                this._ws = ws;
                this._responseHeaders = connection.responseHeaders;
                this._responseStatusCode = connection.responseStatusCode;
                this._responseStatusText = connection.responseStatusText;
                this._setupMessageHandlers(ws);
                const connectDurationMs = this._connectedTime - (this._connectStartTime ?? this._connectedTime);
                this._logService.debug(`[ChatWebSocketManager] Connected for conversation ${this._conversationId}`);
                chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendConnectedTelemetry(this._telemetryService, {
                    conversationId: this._conversationId,
                    initiatingRequestId: this._initiatingRequestId,
                    gitHubRequestId: this.gitHubRequestId,
                    connectDurationMs,
                });
                resolve();
            };
            const onError = (event) => {
                cleanup();
                this._state = 2 /* ConnectionState.Closed */;
                this._responseHeaders = connection.responseHeaders;
                this._responseStatusCode = connection.responseStatusCode;
                this._responseStatusText = connection.responseStatusText;
                const errorMessage = event.error ? `${event.message}: ${(0, logService_1.collectSingleLineErrorMessage)(event.error)}` : event.message || 'WebSocket error';
                const networkError = event.error?.cause ?? connection.networkError;
                const networkErrorMessage = networkError ? (0, logService_1.collectSingleLineErrorMessage)(networkError) : undefined;
                const connectDurationMs = Date.now() - (this._connectStartTime ?? Date.now());
                this._logService.error(`[ChatWebSocketManager] Connection error for conversation ${this._conversationId}: ${errorMessage}${networkErrorMessage ? ` (cause: ${networkErrorMessage})` : ''}`);
                chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendConnectErrorTelemetry(this._telemetryService, {
                    conversationId: this._conversationId,
                    initiatingRequestId: this._initiatingRequestId,
                    gitHubRequestId: this.gitHubRequestId,
                    error: errorMessage,
                    connectDurationMs,
                    responseStatusCode: this._responseStatusCode,
                    responseStatusText: this._responseStatusText,
                    networkError: networkErrorMessage,
                });
                reject(new Error(errorMessage));
            };
            const onClose = (event) => {
                cleanup();
                this._state = 2 /* ConnectionState.Closed */;
                this._responseHeaders = connection.responseHeaders;
                this._responseStatusCode = connection.responseStatusCode;
                this._responseStatusText = connection.responseStatusText;
                const connectDurationMs = Date.now() - (this._connectStartTime ?? Date.now());
                const closeCodeDescription = wsCloseCodeToString(event.code);
                this._logService.debug(`[ChatWebSocketManager] Connection closed during setup for conversation ${this._conversationId} (code: ${event.code} ${closeCodeDescription}, reason: ${event.reason || '<empty>'}, wasClean: ${event.wasClean})`);
                chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendCloseDuringSetupTelemetry(this._telemetryService, {
                    conversationId: this._conversationId,
                    initiatingRequestId: this._initiatingRequestId,
                    gitHubRequestId: this.gitHubRequestId,
                    closeCode: event.code,
                    closeReason: closeCodeDescription,
                    closeEventReason: event.reason,
                    closeEventWasClean: String(event.wasClean),
                    connectDurationMs,
                });
                reject(new Error('WebSocket closed during connection setup'));
            };
            const cleanup = () => {
                ws.removeEventListener('open', onOpen);
                ws.removeEventListener('error', onError);
                ws.removeEventListener('close', onClose);
            };
            ws.addEventListener('open', onOpen);
            ws.addEventListener('error', onError);
            ws.addEventListener('close', onClose);
        });
    }
    _setupMessageHandlers(ws) {
        ws.addEventListener('message', (event) => {
            if (typeof event.data !== 'string') {
                return; // Only process text messages
            }
            const receivedMessageCharacters = event.data.length;
            this._totalReceivedMessageCount += 1;
            this._totalReceivedCharacters += receivedMessageCharacters;
            const connectionDurationMs = Date.now() - (this._connectedTime ?? Date.now());
            let parsed;
            try {
                parsed = JSON.parse(event.data);
            }
            catch (error) {
                const parseErrorMessage = (0, logService_1.collectSingleLineErrorMessage)(error) || 'Failed to parse websocket message';
                this._logService.error(`[ChatWebSocketManager] Failed to parse message for conversation ${this._conversationId} turn ${this._turnId}: ${parseErrorMessage}`);
                chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendMessageParseErrorTelemetry(this._telemetryService, {
                    conversationId: this._conversationId,
                    initiatingRequestId: this._initiatingRequestId,
                    turnId: this._turnId,
                    previousTurnId: this._previousTurnId,
                    hadActiveRequest: this._hadActiveRequest,
                    requestId: this._activeRequest?.requestId,
                    gitHubRequestId: this.gitHubRequestId,
                    modelId: this._activeRequest?.modelId,
                    error: parseErrorMessage,
                    connectionDurationMs,
                    totalSentMessageCount: this._totalSentMessageCount,
                    totalReceivedMessageCount: this._totalReceivedMessageCount,
                    receivedMessageCharacters,
                    totalSentCharacters: this._totalSentCharacters,
                    totalReceivedCharacters: this._totalReceivedCharacters,
                });
                return;
            }
            if (!isCAPIWebSocketError(parsed) && parsed.type === 'response.completed') {
                this._statefulMarker = parsed.response.id;
                this._summarizedAtRoundId = this._activeRequest?.summarizedAtRoundId;
            }
            this._activeRequest?.handleEvent(parsed);
        });
        ws.addEventListener('close', (event) => {
            this._state = 2 /* ConnectionState.Closed */;
            const connectionDurationMs = Date.now() - (this._connectedTime ?? Date.now());
            const closeCodeDescription = wsCloseCodeToString(event.code);
            this._logService.debug(`[ChatWebSocketManager] Connection closed for conversation ${this._conversationId} turn ${this._turnId} (code: ${event.code} ${closeCodeDescription}, reason: ${event.reason || '<empty>'}, wasClean: ${event.wasClean})`);
            chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendCloseTelemetry(this._telemetryService, {
                conversationId: this._conversationId,
                initiatingRequestId: this._initiatingRequestId,
                turnId: this._turnId,
                previousTurnId: this._previousTurnId,
                hadActiveRequest: this._hadActiveRequest,
                requestId: this._activeRequest?.requestId,
                gitHubRequestId: this.gitHubRequestId,
                modelId: this._activeRequest?.modelId,
                closeCode: event.code,
                closeReason: closeCodeDescription,
                closeEventReason: event.reason,
                closeEventWasClean: String(event.wasClean),
                connectionDurationMs,
                totalSentMessageCount: this._totalSentMessageCount,
                totalReceivedMessageCount: this._totalReceivedMessageCount,
                totalSentCharacters: this._totalSentCharacters,
                totalReceivedCharacters: this._totalReceivedCharacters,
            });
            const errorMessage = this._pendingErrorMessage;
            this._pendingErrorMessage = undefined;
            this._activeRequest?.handleConnectionClose(event.code, event.reason, errorMessage);
            this._activeRequest = undefined;
        });
        ws.addEventListener('error', (event) => {
            const errorMessage = event.error ? `${event.message}: ${(0, logService_1.collectSingleLineErrorMessage)(event.error)}` : event.message || 'WebSocket error';
            const connectionDurationMs = Date.now() - (this._connectedTime ?? Date.now());
            this._logService.error(`[ChatWebSocketManager] Error for conversation ${this._conversationId} turn ${this._turnId}: ${errorMessage}`);
            chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendErrorTelemetry(this._telemetryService, {
                conversationId: this._conversationId,
                initiatingRequestId: this._initiatingRequestId,
                turnId: this._turnId,
                previousTurnId: this._previousTurnId,
                hadActiveRequest: this._hadActiveRequest,
                requestId: this._activeRequest?.requestId,
                gitHubRequestId: this.gitHubRequestId,
                modelId: this._activeRequest?.modelId,
                error: errorMessage,
                connectionDurationMs,
                totalSentMessageCount: this._totalSentMessageCount,
                totalReceivedMessageCount: this._totalReceivedMessageCount,
                totalSentCharacters: this._totalSentCharacters,
                totalReceivedCharacters: this._totalReceivedCharacters,
            });
            this._pendingErrorMessage ??= errorMessage;
        });
    }
    sendRequest(body, options, token) {
        if (!this._ws || this._state !== 1 /* ConnectionState.Open */) {
            throw new Error('WebSocket is not connected');
        }
        const statefulMarkerMatched = this._statefulMarker === body.previous_response_id;
        const previousResponseIdUnset = body.previous_response_id === undefined;
        const hasCompactionData = body.input?.some(item => item?.type === 'compaction') ?? false;
        const summarizedAtRoundIdSet = options.summarizedAtRoundId !== undefined;
        const summarizedAtRoundIdMatched = options.summarizedAtRoundId === this._summarizedAtRoundId;
        const compactionThreshold = (0, responsesApi_1.getResponsesApiCompactionThresholdFromBody)(body);
        const statefulMarkerPrefix = this._statefulMarker?.slice(0, 5).concat('...') ?? '<none>';
        const previousResponsePrefix = body.previous_response_id?.slice(0, 5).concat('...') ?? '<none>';
        if (statefulMarkerMatched) {
            this._logService.trace(`[ChatWebSocketManager] WebSocket stateful marker matches previous_response_id (${previousResponsePrefix}), summarizedAtRoundIdMatched: ${summarizedAtRoundIdMatched}`);
        }
        else {
            this._logService.debug(`[ChatWebSocketManager] WebSocket stateful marker (${statefulMarkerPrefix}) does not match previous_response_id (${previousResponsePrefix}), summarizedAtRoundIdMatched: ${summarizedAtRoundIdMatched}`);
        }
        // Supersede any in-flight request before updating turn state
        const hadActiveRequest = !!this._activeRequest;
        if (hadActiveRequest) {
            this._logService.warn(`[ChatWebSocketManager] New request for conversation ${this._conversationId} turn ${options.turnId} while turn ${this._turnId} still has an active request`);
            this._activeRequest.handleSuperseded();
        }
        else {
            this._logService.debug(`[ChatWebSocketManager] New request for conversation ${this._conversationId} turn ${options.turnId} (previous turn: ${this._turnId})`);
        }
        // Update turn state after superseding so the old request's settle
        // callback (which fires synchronously from handleSuperseded) still
        // sees its own turnId on `this`.
        const previousTurnId = this._turnId;
        const turnId = options.turnId;
        this._previousTurnId = previousTurnId;
        this._turnId = turnId;
        this._hadActiveRequest = hadActiveRequest;
        const requestId = options.requestId;
        const requestStartTime = Date.now();
        const requestStartSentMessageCount = this._totalSentMessageCount;
        const requestStartReceivedMessageCount = this._totalReceivedMessageCount;
        const requestStartSentCharacters = this._totalSentCharacters;
        const requestStartReceivedCharacters = this._totalReceivedCharacters;
        const promptTokenCountPromise = options.countTokens();
        let promptTokenCount = -1;
        promptTokenCountPromise.then(count => { promptTokenCount = count; }, () => { promptTokenCount = -2; });
        const request = new ChatWebSocketActiveRequest(requestId, options.model, options.summarizedAtRoundId, this._configurationService, this._logService);
        request.onDidSettle(({ outcome, closeCode, closeReason, serverErrorMessage, serverErrorCode }) => {
            if (this._activeRequest === request) {
                this._activeRequest = undefined;
            }
            const connectionDurationMs = Date.now() - (this._connectedTime ?? Date.now());
            const requestDurationMs = Date.now() - requestStartTime;
            const requestSentMessageCount = this._totalSentMessageCount - requestStartSentMessageCount;
            const requestReceivedMessageCount = this._totalReceivedMessageCount - requestStartReceivedMessageCount;
            const requestSentCharacters = this._totalSentCharacters - requestStartSentCharacters;
            const requestReceivedCharacters = this._totalReceivedCharacters - requestStartReceivedCharacters;
            chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendRequestOutcomeTelemetry(this._telemetryService, {
                conversationId: this._conversationId,
                initiatingRequestId: this._initiatingRequestId,
                turnId,
                previousTurnId,
                hadActiveRequest,
                requestId,
                gitHubRequestId: this.gitHubRequestId,
                modelId: options.model,
                requestOutcome: outcome,
                statefulMarkerMatched,
                previousResponseIdUnset,
                hasCompactionData,
                summarizedAtRoundIdSet,
                summarizedAtRoundIdMatched,
                modeChanged: options.modeChanged,
                compactionThreshold,
                promptTokenCount,
                tokenCountMax: options.tokenCountMax,
                modelMaxPromptTokens: options.modelMaxPromptTokens,
                connectionDurationMs,
                requestDurationMs,
                totalSentMessageCount: this._totalSentMessageCount,
                totalReceivedMessageCount: this._totalReceivedMessageCount,
                totalSentCharacters: this._totalSentCharacters,
                totalReceivedCharacters: this._totalReceivedCharacters,
                requestSentMessageCount,
                requestReceivedMessageCount,
                requestSentCharacters,
                requestReceivedCharacters,
                closeCode,
                closeReason,
                serverErrorMessage,
                serverErrorCode,
            });
        });
        this._activeRequest = request;
        // Handle cancellation
        const cancelDisposable = token.onCancellationRequested(() => {
            if (this._activeRequest === request) {
                request.handleCancellation();
                this._activeRequest = undefined;
            }
        });
        request.done.finally(() => cancelDisposable.dispose()).catch(() => { });
        const { stream: _, ...rest } = body;
        const message = {
            type: 'response.create',
            ...rest,
            initiator: options.userInitiated ? 'user' : 'agent',
        };
        const serializedMessage = JSON.stringify(message);
        const sentMessageCharacters = serializedMessage.length;
        this._totalSentMessageCount += 1;
        this._totalSentCharacters += sentMessageCharacters;
        const connectionDurationMs = Date.now() - (this._connectedTime ?? Date.now());
        this._logService.debug(`[ChatWebSocketManager] Sending request for conversation ${this._conversationId} turn ${this._turnId} (totalSentMessageCount: ${this._totalSentMessageCount}, sentMessageCharacters: ${sentMessageCharacters})`);
        chatWebSocketTelemetry_1.ChatWebSocketTelemetrySender.sendRequestSentTelemetry(this._telemetryService, {
            conversationId: this._conversationId,
            initiatingRequestId: this._initiatingRequestId,
            turnId,
            previousTurnId,
            hadActiveRequest,
            requestId,
            gitHubRequestId: this.gitHubRequestId,
            modelId: options.model,
            statefulMarkerMatched,
            previousResponseIdUnset,
            hasCompactionData,
            summarizedAtRoundIdSet,
            summarizedAtRoundIdMatched,
            modeChanged: options.modeChanged,
            compactionThreshold,
            tokenCountMax: options.tokenCountMax,
            modelMaxPromptTokens: options.modelMaxPromptTokens,
            connectionDurationMs,
            totalSentMessageCount: this._totalSentMessageCount,
            totalReceivedMessageCount: this._totalReceivedMessageCount,
            sentMessageCharacters,
            totalSentCharacters: this._totalSentCharacters,
            totalReceivedCharacters: this._totalReceivedCharacters,
        });
        this._ws.send(serializedMessage);
        return request;
    }
    dispose() {
        this._activeRequest?.handleConnectionDisposed();
        this._activeRequest = undefined;
        if (this._ws) {
            this._ws.close();
            this._ws = undefined;
        }
        this._state = 2 /* ConnectionState.Closed */;
        this._onDidDispose.fire();
        super.dispose();
    }
}
class ChatWebSocketActiveRequest {
    constructor(requestId, modelId, summarizedAtRoundId, _configurationService, _logService) {
        this.requestId = requestId;
        this.modelId = modelId;
        this.summarizedAtRoundId = summarizedAtRoundId;
        this._configurationService = _configurationService;
        this._logService = _logService;
        this._onEvent = new event_1.Emitter();
        this.onEvent = this._onEvent.event;
        this._onCAPIError = new event_1.Emitter();
        this.onCAPIError = this._onCAPIError.event;
        this._onError = new event_1.Emitter();
        this.onError = this._onError.event;
        this._firstEventSettled = false;
        this._settled = false;
        this.done = new Promise((resolve, reject) => {
            this._resolve = resolve;
            this._reject = reject;
        });
        this.firstEvent = new Promise((resolve, reject) => {
            this._resolveFirstEvent = resolve;
            this._rejectFirstEvent = reject;
        });
    }
    onDidSettle(callback) {
        this._onDidSettle = callback;
    }
    handleEvent(event) {
        if (this._settled) {
            return;
        }
        // E.g.: "github.copilot.chat.advanced.debug.simulateWebSocketResponse": "{\"type\":\"error\",\"error\":{\"code\":\"user_global_rate_limited:enterprise\",\"message\":\"Rate limit exceeded\"}}"
        // E.g.: "github.copilot.chat.advanced.debug.simulateWebSocketResponse": "{\"type\":\"error\",\"error\":{\"code\":\"service_unavailable\",\"message\":\"service temporarily unavailable, please retry\"}}"
        const simulateResponse = this._configurationService.getConfig(configurationService_1.ConfigKey.TeamInternal.DebugSimulateWebSocketResponse);
        if (simulateResponse) {
            try {
                event = JSON.parse(simulateResponse);
                this._logService.info(`[ChatWebSocketManager] Simulating WebSocket response event: ${simulateResponse}`);
            }
            catch (e) {
                this._logService.error(`[ChatWebSocketManager] Failed to parse simulated WebSocket response: ${(0, logService_1.collectSingleLineErrorMessage)(e)}`);
            }
        }
        if (!this._firstEventSettled) {
            this._firstEventSettled = true;
            this._resolveFirstEvent(event);
        }
        if (isCAPIWebSocketError(event)) {
            this._finalizeCAPIError(event);
            return;
        }
        this._onEvent.fire(event);
        const outcome = getStreamTerminatingOutcome(event);
        if (outcome) {
            this._finalizeSuccess(outcome);
        }
    }
    handleConnectionClose(code, reason, errorMessage) {
        if (this._settled) {
            return;
        }
        const error = errorMessage
            ? new Error(`${errorMessage} (close code: ${code} ${wsCloseCodeToString(code)}${reason ? `, reason: ${reason}` : ''})`)
            : new Error(`WebSocket closed (code: ${code} ${wsCloseCodeToString(code)}${reason ? `, reason: ${reason}` : ''})`);
        this._finalizeError('connection_closed', error, code, reason);
    }
    handleSuperseded() {
        if (this._settled) {
            return;
        }
        this._finalizeError('superseded', new Error('Request superseded by new request'));
    }
    handleCancellation() {
        if (this._settled) {
            return;
        }
        this._finalizeError('canceled', new errors_1.CancellationError());
    }
    handleConnectionDisposed() {
        if (this._settled) {
            return;
        }
        this._finalizeError('connection_disposed', new Error('Connection disposed'));
    }
    _finalizeSuccess(outcome) {
        this._settled = true;
        this._onDidSettle?.({ outcome });
        this._resolve();
        this._dispose();
    }
    _finalizeCAPIError(event) {
        const { code, message } = event.error;
        this._onCAPIError.fire(event);
        this._settled = true;
        this._onDidSettle?.({ outcome: 'error_response', serverErrorMessage: message, serverErrorCode: code });
        this._reject(new Error(`${message} (${code})`));
        this._dispose();
    }
    _finalizeError(outcome, error, closeCode, closeReason, serverErrorMessage, serverErrorCode) {
        if (!this._firstEventSettled) {
            this._firstEventSettled = true;
            this._rejectFirstEvent(error);
        }
        this._onError.fire(error);
        this._settled = true;
        this._onDidSettle?.({ outcome, closeCode, closeReason, serverErrorMessage, serverErrorCode });
        this._reject(error);
        this._dispose();
    }
    _dispose() {
        this._onEvent.dispose();
        this._onCAPIError.dispose();
        this._onError.dispose();
    }
}
//# sourceMappingURL=chatWebSocketManager.js.map