"use strict";
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 ChatEndpoint_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoteAgentChatEndpoint = exports.ChatEndpoint = void 0;
exports.defaultChatResponseProcessor = defaultChatResponseProcessor;
exports.defaultNonStreamChatResponseProcessor = defaultNonStreamChatResponseProcessor;
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
const copilot_api_1 = require("@vscode/copilot-api");
const prompt_tsx_1 = require("@vscode/prompt-tsx");
const async_1 = require("../../../util/vs/base/common/async");
const objects_1 = require("../../../util/vs/base/common/objects");
const uuid_1 = require("../../../util/vs/base/common/uuid");
const instantiation_1 = require("../../../util/vs/platform/instantiation/common/instantiation");
const authentication_1 = require("../../authentication/common/authentication");
const chatMLFetcher_1 = require("../../chat/common/chatMLFetcher");
const commonTypes_1 = require("../../chat/common/commonTypes");
const globalStringUtils_1 = require("../../chat/common/globalStringUtils");
const configurationService_1 = require("../../configuration/common/configurationService");
const logService_1 = require("../../log/common/logService");
const anthropic_1 = require("../../networking/common/anthropic");
const fetch_1 = require("../../networking/common/fetch");
const fetcherService_1 = require("../../networking/common/fetcherService");
const networking_1 = require("../../networking/common/networking");
const chatStream_1 = require("../../networking/node/chatStream");
const chatWebSocketManager_1 = require("../../networking/node/chatWebSocketManager");
const stream_1 = require("../../networking/node/stream");
const nullExperimentationService_1 = require("../../telemetry/common/nullExperimentationService");
const telemetry_1 = require("../../telemetry/common/telemetry");
const tokenizer_1 = require("../../tokenizer/node/tokenizer");
const capiClient_1 = require("../common/capiClient");
const chatModelCapabilities_1 = require("../common/chatModelCapabilities");
const domainService_1 = require("../common/domainService");
const endpointProvider_1 = require("../common/endpointProvider");
const messagesApi_1 = require("./messagesApi");
const responsesApi_1 = require("./responsesApi");
const imageLimits_1 = require("./imageLimits");
/**
 * The default processor for the stream format from CAPI
 */
async function defaultChatResponseProcessor(telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData, cancellationToken) {
    const processor = await stream_1.SSEProcessor.create(logService, telemetryService, expectedNumChoices, response, cancellationToken);
    const finishedCompletions = processor.processSSE(finishCallback);
    const chatCompletions = async_1.AsyncIterableObject.map(finishedCompletions, (solution) => {
        const loggedReason = solution.reason ?? 'client-trimmed';
        const dataToSendToTelemetry = telemetryData.extendedBy({
            completionChoiceFinishReason: loggedReason,
            headerRequestId: solution.requestId.headerRequestId
        });
        telemetryService.sendGHTelemetryEvent('completion.finishReason', dataToSendToTelemetry.properties, dataToSendToTelemetry.measurements);
        return (0, chatStream_1.prepareChatCompletionForReturn)(telemetryService, logService, solution, telemetryData);
    });
    return chatCompletions;
}
async function defaultNonStreamChatResponseProcessor(response, finishCallback, telemetryData) {
    const textResponse = await response.text();
    const jsonResponse = JSON.parse(textResponse);
    const completions = [];
    for (let i = 0; i < (jsonResponse?.choices?.length || 0); i++) {
        const choice = jsonResponse.choices[i];
        const message = {
            role: choice.message.role,
            content: choice.message.content,
            name: choice.message.name,
            // Normalize property name: OpenAI API uses snake_case (tool_calls) but our types expect camelCase (toolCalls)
            // See: https://platform.openai.com/docs/api-reference/chat/object#chat-object-choices-message-tool_calls
            toolCalls: choice.message.toolCalls ?? choice.message.tool_calls,
        };
        const messageText = (0, globalStringUtils_1.getTextPart)(message.content);
        const requestId = response.headers.get('X-Request-ID') ?? (0, uuid_1.generateUuid)();
        const ghRequestId = response.headers.get('x-github-request-id') ?? '';
        const { serverExperiments } = (0, fetch_1.getRequestId)(response.headers);
        const completion = {
            blockFinished: false,
            choiceIndex: i,
            model: jsonResponse.model,
            filterReason: undefined,
            finishReason: choice.finish_reason,
            message: message,
            usage: jsonResponse.usage,
            tokens: [], // This is used for repetition detection so not super important to be accurate
            requestId: { headerRequestId: requestId, gitHubRequestId: ghRequestId, completionId: jsonResponse.id, created: jsonResponse.created, deploymentId: '', serverExperiments },
            telemetryData: telemetryData
        };
        const functionCall = [];
        for (const tool of message.toolCalls ?? []) {
            functionCall.push({
                name: tool.function?.name ?? '',
                arguments: tool.function?.arguments ?? '',
                id: tool.id ?? '',
            });
        }
        await finishCallback(messageText, i, {
            text: messageText,
            copilotToolCalls: functionCall,
        });
        completions.push(completion);
    }
    return async_1.AsyncIterableObject.fromArray(completions);
}
const AIC_DIVISOR = 1_000_000_000;
const TOKENS_PER_MILLION = 1_000_000;
/**
 * Converts raw billing token prices into normalized AICs per million tokens.
 *
 * Raw prices are divided by {@link AIC_DIVISOR} to get AICs, then scaled
 * so the result is always "per 1M tokens" regardless of the original batch_size.
 */
function normalizeTokenPricing(tokenPrices) {
    if (!tokenPrices) {
        return undefined;
    }
    const { batch_size, input_price, output_price, cache_price } = tokenPrices;
    const scale = TOKENS_PER_MILLION / batch_size;
    return {
        inputPrice: (input_price / AIC_DIVISOR) * scale,
        outputPrice: (output_price / AIC_DIVISOR) * scale,
        cacheReadTokenPrice: (cache_price / AIC_DIVISOR) * scale,
    };
}
let ChatEndpoint = ChatEndpoint_1 = class ChatEndpoint {
    constructor(modelMetadata, _domainService, _chatMLFetcher, _tokenizerProvider, _instantiationService, _configurationService, _expService, _chatWebSocketService, _logService) {
        this.modelMetadata = modelMetadata;
        this._domainService = _domainService;
        this._chatMLFetcher = _chatMLFetcher;
        this._tokenizerProvider = _tokenizerProvider;
        this._instantiationService = _instantiationService;
        this._configurationService = _configurationService;
        this._expService = _expService;
        this._chatWebSocketService = _chatWebSocketService;
        // This metadata should always be present, but if not we will default to 8192 tokens
        this._maxTokens = modelMetadata.capabilities.limits?.max_prompt_tokens ?? 8192;
        // This metadata should always be present, but if not we will default to 4096 tokens
        this._maxOutputTokens = modelMetadata.capabilities.limits?.max_output_tokens ?? 4096;
        this.model = modelMetadata.id;
        this.modelProvider = modelMetadata.vendor;
        this.name = modelMetadata.name;
        this.version = modelMetadata.version;
        this.family = modelMetadata.capabilities.family;
        this.tokenizer = modelMetadata.capabilities.tokenizer;
        this.showInModelPicker = modelMetadata.model_picker_enabled;
        this.isPremium = modelMetadata.billing?.is_premium;
        this.multiplier = modelMetadata.billing?.multiplier;
        this.restrictedToSkus = modelMetadata.billing?.restricted_to;
        this.tokenPricing = normalizeTokenPricing(modelMetadata.billing?.token_prices);
        this.priceCategory = modelMetadata.model_picker_price_category;
        this.isFallback = modelMetadata.is_chat_fallback;
        this.supportsToolCalls = !!modelMetadata.capabilities.supports.tool_calls;
        this.supportsVision = !!modelMetadata.capabilities.supports.vision;
        this.supportsPrediction = !!modelMetadata.capabilities.supports.prediction;
        this.supportsAdaptiveThinking = modelMetadata.capabilities.supports.adaptive_thinking;
        this.minThinkingBudget = modelMetadata.capabilities.supports.min_thinking_budget;
        this.maxThinkingBudget = modelMetadata.capabilities.supports.max_thinking_budget;
        this.supportsReasoningEffort = modelMetadata.capabilities.supports.reasoning_effort;
        this.supportsToolSearch = modelMetadata.capabilities.supports.tool_search ?? (0, chatModelCapabilities_1.modelSupportsToolSearch)(this.model);
        this.supportsContextEditing = modelMetadata.capabilities.supports.context_editing ?? (0, chatModelCapabilities_1.modelSupportsContextEditing)(this.model);
        this._supportsStreaming = !!modelMetadata.capabilities.supports.streaming;
        this.customModel = modelMetadata.custom_model;
        this.maxPromptImages = modelMetadata.capabilities.limits?.vision?.max_prompt_images;
    }
    // TODO: Thread enableThinking through the fetch pipeline (INetworkRequestOptions / chatMLFetcher positional params)
    // so getExtraHeaders can gate the interleaved-thinking header on whether thinking is actually enabled for the
    // request, rather than using the location check. Once plumbed, replace isAllowedConversationAgentModel with
    // an enableThinking check for the thinking header (keep location gate for context management / tool search).
    getExtraHeaders(location, interactionTypeOverride) {
        const headers = { ...this.modelMetadata.requestHeaders };
        if (this.useMessagesApi) {
            const modelProviderPreference = this._configurationService.getConfig(configurationService_1.ConfigKey.TeamInternal.ModelProviderPreference);
            if (modelProviderPreference) {
                headers['X-Model-Provider-Preference'] = modelProviderPreference;
            }
        }
        Object.assign(headers, this.getAnthropicBetaHeader(location, interactionTypeOverride));
        return headers;
    }
    getAnthropicBetaHeader(location, interactionTypeOverride) {
        if (!this.useMessagesApi) {
            return {};
        }
        const betas = [];
        if (!this.supportsAdaptiveThinking) {
            betas.push('interleaved-thinking-2025-05-14');
        }
        if (this.supportsToolSearch) {
            betas.push('advanced-tool-use-2025-11-20');
        }
        if ((0, anthropic_1.isAnthropicContextEditingEnabled)(this, this._configurationService, this._expService)) {
            betas.push('context-management-2025-06-27');
        }
        // Mirror the body-side gate from messagesApi.ts so the beta header is never sent for
        // requests that won't actually emit `ttl: '1h'` (subagents, non-Agent locations, etc.).
        const isSubagent = interactionTypeOverride === 'conversation-subagent';
        if ((0, anthropic_1.isExtendedCacheTtlEnabled)(this, this._configurationService, this._expService, location, isSubagent)) {
            betas.push('extended-cache-ttl-2025-04-11');
        }
        return betas.length > 0 ? { 'anthropic-beta': betas.join(',') } : {};
    }
    get modelMaxPromptTokens() {
        return this._maxTokens;
    }
    get maxOutputTokens() {
        return this._maxOutputTokens;
    }
    get urlOrRequestMetadata() {
        // Use override or respect setting.
        // TODO unlikely but would break if it changes in the middle of a request being constructed
        return this.modelMetadata.urlOrRequestMetadata ??
            (this.useResponsesApi ? { type: copilot_api_1.RequestType.ChatResponses } :
                this.useMessagesApi ? { type: copilot_api_1.RequestType.ChatMessages } : { type: copilot_api_1.RequestType.ChatCompletions });
    }
    get useResponsesApi() {
        if (this.modelMetadata.supported_endpoints
            && !this.modelMetadata.supported_endpoints.includes(endpointProvider_1.ModelSupportedEndpoint.ChatCompletions)
            && this.modelMetadata.supported_endpoints.includes(endpointProvider_1.ModelSupportedEndpoint.Responses)) {
            return true;
        }
        return !!this.modelMetadata.supported_endpoints?.includes(endpointProvider_1.ModelSupportedEndpoint.Responses);
    }
    get useWebSocketResponsesApi() {
        return !!this.modelMetadata.supported_endpoints?.includes(endpointProvider_1.ModelSupportedEndpoint.WebSocketResponses);
    }
    get useMessagesApi() {
        const enableMessagesApi = this._configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.UseAnthropicMessagesApi, this._expService);
        return !!(enableMessagesApi && this.modelMetadata.supported_endpoints?.includes(endpointProvider_1.ModelSupportedEndpoint.Messages));
    }
    get degradationReason() {
        return this.modelMetadata.warning_messages?.at(0)?.message ?? this.modelMetadata.info_messages?.at(0)?.message;
    }
    get apiType() {
        return this.useResponsesApi ? 'responses' :
            this.useMessagesApi ? 'messages' : 'chatCompletions';
    }
    interceptBody(body) {
        // Remove tool calls from requests that don't support them
        // We really shouldn't make requests to models that don't support tool calls with tools though
        if (body && !this.supportsToolCalls) {
            delete body['tools'];
        }
        // If the model doesn't support streaming, don't ask for a streamed request
        if (body && !this._supportsStreaming) {
            body.stream = false;
        }
        // If it's o1 we must modify the body significantly as the request is very different
        if (body?.messages && (this.family.startsWith('o1') || this.model === "o1" /* CHAT_MODEL.O1 */ || this.model === "o1-mini" /* CHAT_MODEL.O1MINI */)) {
            const newMessages = body.messages.map((message) => {
                if (message.role === prompt_tsx_1.OpenAI.ChatRole.System) {
                    return {
                        role: prompt_tsx_1.OpenAI.ChatRole.User,
                        content: message.content,
                    };
                }
                else {
                    return message;
                }
            });
            // Add the messages & model back
            body['messages'] = newMessages;
        }
    }
    createRequestBody(options) {
        // Determine per-model image limit for APIs with known restrictions
        const imageLimit = this.getImageLimit();
        if (imageLimit !== undefined) {
            options = { ...options, messages: this.validateAndFilterImages(options.messages, imageLimit) };
        }
        if (this.useResponsesApi) {
            const body = this._instantiationService.invokeFunction(responsesApi_1.createResponsesRequestBody, options, this.model, this);
            return this.customizeResponsesBody(body);
        }
        else if (this.useMessagesApi) {
            const body = this._instantiationService.invokeFunction(messagesApi_1.createMessagesRequestBody, options, this.model, this);
            return this.customizeMessagesBody(body);
        }
        else {
            const body = (0, networking_1.createCapiRequestBody)(options, this.model, this.getCompletionsCallback());
            return this.customizeCapiBody(body, options);
        }
    }
    /**
     * Returns the model-specific image limit, or `undefined` if no limit applies.
     * Anthropic Messages API allows up to 20 images per request; Gemini allows up to 10.
     * These are hardcoded based on API documentation rather than model metadata to
     * avoid being clamped by unreliable server-provided values.
     */
    getImageLimit() {
        if (this.useMessagesApi && (0, chatModelCapabilities_1.isAnthropicFamily)(this)) {
            return 20;
        }
        if ((0, chatModelCapabilities_1.isGeminiFamily)(this)) {
            return 10;
        }
        return undefined;
    }
    /**
     * Thin wrapper around {@link filterHistoryImages} retained for test ergonomics.
     */
    validateAndFilterImages(messages, maxImages) {
        return (0, imageLimits_1.filterHistoryImages)(messages, maxImages);
    }
    getCompletionsCallback() {
        return undefined;
    }
    customizeMessagesBody(body) {
        return body;
    }
    customizeResponsesBody(body) {
        return body;
    }
    customizeCapiBody(body, options) {
        // Apply Gemini function calling mode if configured
        const hasTools = !!options.requestOptions?.tools?.length;
        if (hasTools && this.family.toLowerCase().includes('gemini-3')) {
            const geminiFunctionCallingMode = this._configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.GeminiFunctionCallingMode, this._expService);
            // Only override tool_choice if experiment provides a value and user hasn't specified a function call
            if (geminiFunctionCallingMode && typeof body.tool_choice !== 'object') {
                body.tool_choice = geminiFunctionCallingMode;
            }
        }
        return body;
    }
    async processResponseFromChatEndpoint(telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData, cancellationToken) {
        if (this.useResponsesApi) {
            const compactionThreshold = (0, responsesApi_1.getResponsesApiCompactionThreshold)(this._configurationService, this._expService, this);
            return (0, responsesApi_1.processResponseFromChatEndpoint)(this._instantiationService, telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData, compactionThreshold);
        }
        else if (this.useMessagesApi) {
            return (0, messagesApi_1.processResponseFromMessagesEndpoint)(this._instantiationService, telemetryService, logService, response, finishCallback, telemetryData);
        }
        else if (!this._supportsStreaming) {
            return defaultNonStreamChatResponseProcessor(response, finishCallback, telemetryData);
        }
        else {
            return defaultChatResponseProcessor(telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData, cancellationToken);
        }
    }
    acquireTokenizer() {
        return this._tokenizerProvider.acquireTokenizer(this);
    }
    async makeChatRequest2(options, token) {
        const useWebSocket = options.useWebSocket ?? !!(options.turnId
            && options.conversationId
            && this.useWebSocketResponsesApi
            && this._configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.ResponsesApiWebSocketEnabled, this._expService));
        const ignoreStatefulMarker = options.ignoreStatefulMarker ?? !(useWebSocket
            && options.conversationId
            && options.turnId
            && this._chatWebSocketService.hasActiveConnection(options.conversationId));
        const response = await this._makeChatRequest2({
            ...options,
            useWebSocket,
            ignoreStatefulMarker,
        }, token);
        if (response.type === commonTypes_1.ChatFetchResponseType.InvalidStatefulMarker) {
            return this._makeChatRequest2({
                ...options,
                useWebSocket,
                ignoreStatefulMarker: true
            }, token);
        }
        return response;
    }
    async _makeChatRequest2(options, token) {
        return this._chatMLFetcher.fetchOne({
            requestOptions: {},
            ...options,
            endpoint: this,
        }, token);
    }
    async makeChatRequest(debugName, messages, finishedCb, token, location, source, requestOptions, userInitiatedRequest, telemetryProperties) {
        return this.makeChatRequest2({
            debugName,
            messages,
            finishedCb,
            location,
            source,
            requestOptions,
            userInitiatedRequest,
            telemetryProperties,
        }, token);
    }
    cloneWithTokenOverride(modelMaxPromptTokens) {
        return this._instantiationService.createInstance(ChatEndpoint_1, (0, objects_1.mixin)((0, objects_1.deepClone)(this.modelMetadata), { capabilities: { limits: { max_prompt_tokens: modelMaxPromptTokens } } }));
    }
};
exports.ChatEndpoint = ChatEndpoint;
exports.ChatEndpoint = ChatEndpoint = ChatEndpoint_1 = __decorate([
    __param(1, domainService_1.IDomainService),
    __param(2, chatMLFetcher_1.IChatMLFetcher),
    __param(3, tokenizer_1.ITokenizerProvider),
    __param(4, instantiation_1.IInstantiationService),
    __param(5, configurationService_1.IConfigurationService),
    __param(6, nullExperimentationService_1.IExperimentationService),
    __param(7, chatWebSocketManager_1.IChatWebSocketManager),
    __param(8, logService_1.ILogService)
], ChatEndpoint);
let RemoteAgentChatEndpoint = class RemoteAgentChatEndpoint extends ChatEndpoint {
    constructor(modelMetadata, _requestMetadata, domainService, capiClientService, fetcherService, telemetryService, authService, chatMLFetcher, tokenizerProvider, instantiationService, configService, experimentService, chatWebSocketService, logService) {
        super(modelMetadata, domainService, chatMLFetcher, tokenizerProvider, instantiationService, configService, experimentService, chatWebSocketService, logService);
        this._requestMetadata = _requestMetadata;
    }
    processResponseFromChatEndpoint(telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData, cancellationToken, _location) {
        // We must override this to a num choices > 1 because remote agents can do internal function calls which emit multiple completions even when N > 1
        // It's awful that they do this, but we have to support it
        return defaultChatResponseProcessor(telemetryService, logService, response, 2, finishCallback, telemetryData, cancellationToken);
    }
    get urlOrRequestMetadata() {
        return this._requestMetadata;
    }
};
exports.RemoteAgentChatEndpoint = RemoteAgentChatEndpoint;
exports.RemoteAgentChatEndpoint = RemoteAgentChatEndpoint = __decorate([
    __param(2, domainService_1.IDomainService),
    __param(3, capiClient_1.ICAPIClientService),
    __param(4, fetcherService_1.IFetcherService),
    __param(5, telemetry_1.ITelemetryService),
    __param(6, authentication_1.IAuthenticationService),
    __param(7, chatMLFetcher_1.IChatMLFetcher),
    __param(8, tokenizer_1.ITokenizerProvider),
    __param(9, instantiation_1.IInstantiationService),
    __param(10, configurationService_1.IConfigurationService),
    __param(11, nullExperimentationService_1.IExperimentationService),
    __param(12, chatWebSocketManager_1.IChatWebSocketManager),
    __param(13, logService_1.ILogService)
], RemoteAgentChatEndpoint);
//# sourceMappingURL=chatEndpoint.js.map