"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 OpenAIEndpoint_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAIEndpoint = void 0;
exports.isBYOKModel = isBYOKModel;
const chatMLFetcher_1 = require("../../../platform/chat/common/chatMLFetcher");
const commonTypes_1 = require("../../../platform/chat/common/commonTypes");
const configurationService_1 = require("../../../platform/configuration/common/configurationService");
const domainService_1 = require("../../../platform/endpoint/common/domainService");
const chatEndpoint_1 = require("../../../platform/endpoint/node/chatEndpoint");
const logService_1 = require("../../../platform/log/common/logService");
const fetch_1 = require("../../../platform/networking/common/fetch");
const networking_1 = require("../../../platform/networking/common/networking");
const chatWebSocketManager_1 = require("../../../platform/networking/node/chatWebSocketManager");
const nullExperimentationService_1 = require("../../../platform/telemetry/common/nullExperimentationService");
const tokenizer_1 = require("../../../platform/tokenizer/node/tokenizer");
const instantiation_1 = require("../../../util/vs/platform/instantiation/common/instantiation");
function hydrateBYOKErrorMessages(response) {
    if (response.type === commonTypes_1.ChatFetchResponseType.Failed && response.streamError) {
        return {
            type: response.type,
            requestId: response.requestId,
            serverRequestId: response.serverRequestId,
            reason: JSON.stringify(response.streamError),
        };
    }
    else if (response.type === commonTypes_1.ChatFetchResponseType.RateLimited) {
        return {
            type: response.type,
            requestId: response.requestId,
            serverRequestId: response.serverRequestId,
            reason: response.capiError ? 'Rate limit exceeded\n\n' + JSON.stringify(response.capiError) : 'Rate limit exceeded',
            rateLimitKey: '',
            retryAfter: undefined,
            isAuto: false,
            capiError: response.capiError
        };
    }
    return response;
}
/**
 * Checks to see if a given endpoint is a BYOK model.
 * @param endpoint The endpoint to check if it's a BYOK model
 * @returns 1 if client side byok, 2 if server side byok, -1 if not a byok model
 */
function isBYOKModel(endpoint) {
    if (!endpoint) {
        return -1;
    }
    return (endpoint instanceof OpenAIEndpoint || endpoint.isExtensionContributed) ? 1 : (endpoint.customModel ? 2 : -1);
}
let OpenAIEndpoint = class OpenAIEndpoint extends chatEndpoint_1.ChatEndpoint {
    static { OpenAIEndpoint_1 = this; }
    // Reserved headers that cannot be overridden for security and functionality reasons
    // Including forbidden request headers: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header
    static { this._reservedHeaders = new Set([
        // Forbidden Request Headers
        'accept-charset',
        'accept-encoding',
        'access-control-request-headers',
        'access-control-request-method',
        'connection',
        'content-length',
        'cookie',
        'date',
        'dnt',
        'expect',
        'host',
        'keep-alive',
        'origin',
        'permissions-policy',
        'referer',
        'te',
        'trailer',
        'transfer-encoding',
        'upgrade',
        'user-agent',
        'via',
        // Forwarding & Routing
        'forwarded',
        'x-forwarded-for',
        'x-forwarded-host',
        'x-forwarded-proto',
        // Others
        'api-key',
        'authorization',
        'content-type',
        'openai-intent',
        'x-github-api-version',
        'x-initiator',
        'x-interaction-id',
        'x-interaction-type',
        'x-onbehalf-extension-id',
        'x-request-id',
        'x-vscode-user-agent-library-version',
        // Pattern-based forbidden headers are checked separately:
        // - 'proxy-*' headers (handled in sanitization logic)
        // - 'sec-*' headers (handled in sanitization logic)
        // - 'x-http-method*' with forbidden methods CONNECT, TRACE, TRACK (handled in sanitization logic)
    ]); }
    // RFC 7230 compliant header name pattern: token characters only
    static { this._validHeaderNamePattern = /^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$/; }
    // Maximum limits to prevent abuse
    static { this._maxHeaderNameLength = 256; }
    static { this._maxHeaderValueLength = 8192; }
    static { this._maxCustomHeaderCount = 20; }
    constructor(_modelMetadata, _apiKey, _modelUrl, domainService, chatMLFetcher, tokenizerProvider, instantiationService, configurationService, expService, chatWebSocketService, logService) {
        super(_modelMetadata, domainService, chatMLFetcher, tokenizerProvider, instantiationService, configurationService, expService, chatWebSocketService, logService);
        this._apiKey = _apiKey;
        this._modelUrl = _modelUrl;
        this.instantiationService = instantiationService;
        this.logService = logService;
        this._customHeaders = this._sanitizeCustomHeaders(_modelMetadata.requestHeaders);
    }
    _sanitizeCustomHeaders(headers) {
        if (!headers) {
            return {};
        }
        const entries = Object.entries(headers);
        if (entries.length > OpenAIEndpoint_1._maxCustomHeaderCount) {
            this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' has ${entries.length} custom headers, exceeding limit of ${OpenAIEndpoint_1._maxCustomHeaderCount}. Only first ${OpenAIEndpoint_1._maxCustomHeaderCount} will be processed.`);
        }
        const sanitized = {};
        let processedCount = 0;
        for (const [rawKey, rawValue] of entries) {
            if (processedCount >= OpenAIEndpoint_1._maxCustomHeaderCount) {
                break;
            }
            const key = rawKey.trim();
            if (!key) {
                this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' has empty header name, skipping.`);
                continue;
            }
            if (key.length > OpenAIEndpoint_1._maxHeaderNameLength) {
                this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' has header name exceeding ${OpenAIEndpoint_1._maxHeaderNameLength} characters, skipping.`);
                continue;
            }
            if (!OpenAIEndpoint_1._validHeaderNamePattern.test(key)) {
                this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' has invalid header name format: '${key}', Skipping.`);
                continue;
            }
            const lowerKey = key.toLowerCase();
            if (OpenAIEndpoint_1._reservedHeaders.has(lowerKey)) {
                this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' attempted to override reserved header '${key}', skipping.`);
                continue;
            }
            // Check for pattern-based forbidden headers
            if (lowerKey.startsWith('proxy-') || lowerKey.startsWith('sec-')) {
                this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' attempted to set forbidden header pattern '${key}', skipping.`);
                continue;
            }
            // Check for X-HTTP-Method* headers with forbidden methods
            if ((lowerKey === 'x-http-method' || lowerKey === 'x-http-method-override' || lowerKey === 'x-method-override')) {
                const forbiddenMethods = ['connect', 'trace', 'track'];
                const methodValue = String(rawValue).toLowerCase().trim();
                if (forbiddenMethods.includes(methodValue)) {
                    this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' attempted to set forbidden method '${methodValue}' in header '${key}', skipping.`);
                    continue;
                }
            }
            const sanitizedValue = this._sanitizeHeaderValue(rawValue);
            if (sanitizedValue === undefined) {
                this.logService.warn(`[OpenAIEndpoint] Model '${this.modelMetadata.id}' has invalid value for header '${key}': '${rawValue}', skipping.`);
                continue;
            }
            sanitized[key] = sanitizedValue;
            processedCount++;
        }
        return sanitized;
    }
    _sanitizeHeaderValue(value) {
        if (typeof value !== 'string') {
            return undefined;
        }
        const trimmed = value.trim();
        if (trimmed.length > OpenAIEndpoint_1._maxHeaderValueLength) {
            return undefined;
        }
        // Disallow control characters including CR, LF, and others (0x00-0x1F, 0x7F)
        // This prevents HTTP header injection and response splitting attacks
        if (/[\x00-\x1F\x7F]/.test(trimmed)) {
            return undefined;
        }
        // Additional check for potential Unicode issues
        // Reject headers with bidirectional override characters or zero-width characters
        if (/[\u200B-\u200D\u202A-\u202E\uFEFF]/.test(trimmed)) {
            return undefined;
        }
        return trimmed;
    }
    createRequestBody(options) {
        if (this.useResponsesApi) {
            // Handle Responses API: customize the body directly
            const zdr = !!this.modelMetadata.zeroDataRetentionEnabled;
            // When ZDR is on the server refuses to retain responses, so we must
            // not chain via `previous_response_id` and must not ask it to `store`.
            options.ignoreStatefulMarker = zdr;
            const body = super.createRequestBody(options);
            body.store = !zdr;
            body.n = undefined;
            body.stream_options = undefined;
            if (!this.modelMetadata.capabilities.supports.thinking) {
                body.reasoning = undefined;
                body.include = undefined;
            }
            if (body.previous_response_id && (!body.previous_response_id.startsWith('resp_') || zdr)) {
                // Don't use a response ID from CAPI or when zero data retention is enabled
                body.previous_response_id = undefined;
            }
            this._applyReasoningEffort(body, options);
            return body;
        }
        else if (this.useMessagesApi) {
            // Delegate to base ChatEndpoint for Messages API dispatch
            return super.createRequestBody(options);
        }
        else {
            // Handle CAPI: provide callback for thinking data processing
            const callback = (out, data) => {
                if (data && data.id) {
                    out.cot_id = data.id;
                    out.cot_summary = Array.isArray(data.text) ? data.text.join('') : data.text;
                }
            };
            const body = (0, networking_1.createCapiRequestBody)(options, this.model, callback);
            this._applyReasoningEffort(body, options);
            return body;
        }
    }
    /**
     * Forwards the per-request reasoning effort to the model body in the shape the endpoint expects.
     * Default shape mirrors the API path (`Responses` \u2192 nested `reasoning.effort`, `Chat Completions` \u2192 top-level `reasoning_effort`).
     * `IChatModelInformation.reasoningEffortFormat` overrides the default so users hosting OpenAI-compatible servers
     * with diverging conventions (e.g. nested `reasoning.effort` on `/chat/completions`) can opt in deterministically.
     */
    _applyReasoningEffort(body, options) {
        const supports = this.supportsReasoningEffort;
        if (!supports?.length) {
            return;
        }
        const format = this.modelMetadata.reasoningEffortFormat
            ?? (this.useResponsesApi ? 'responses' : 'chat-completions');
        const override = this._configurationService.getConfig(configurationService_1.ConfigKey.Advanced.ReasoningEffortOverride);
        const requested = override || options.modelCapabilities?.reasoningEffort || body.reasoning?.effort || body.reasoning_effort;
        const effort = requested && supports.includes(requested) ? requested : undefined;
        // Scrub any pre-populated effort first so unsupported values (e.g. the hard-coded `medium` default
        // from `createResponsesRequestBody`) cannot leak through, then write the resolved value into the
        // expected shape.
        if (body.reasoning) {
            const { effort: _drop, ...rest } = body.reasoning;
            body.reasoning = Object.keys(rest).length > 0 ? rest : undefined;
        }
        body.reasoning_effort = undefined;
        if (effort) {
            if (format === 'responses') {
                body.reasoning = { ...body.reasoning, effort };
            }
            else {
                body.reasoning_effort = effort;
            }
        }
    }
    interceptBody(body) {
        super.interceptBody(body);
        // TODO @lramos15 - We should do this for all models and not just here
        if (body?.tools?.length === 0) {
            delete body.tools;
        }
        if (body?.tools) {
            body.tools = body.tools.map(tool => {
                if ((0, fetch_1.isOpenAiFunctionTool)(tool) && tool.function.parameters === undefined) {
                    tool.function.parameters = { type: 'object', properties: {} };
                }
                return tool;
            });
        }
        if (body) {
            if (this.modelMetadata.capabilities.supports.thinking) {
                delete body.temperature;
                if (!this.useMessagesApi && !this.useResponsesApi) {
                    // OpenAI Chat Completions thinking models (e.g. o1/o3) require `max_completion_tokens` instead of `max_tokens`.
                    // Responses bodies use `max_output_tokens` natively, and Messages requires `max_tokens` — neither needs this rename.
                    body['max_completion_tokens'] = body.max_tokens;
                    delete body.max_tokens;
                }
            }
            // Chat Completions: drop `max_tokens` so the server defaults to its maximum (preferred for BYOK).
            // Responses uses `max_output_tokens`, so this delete is a no-op there. Messages requires `max_tokens`, so leave it alone.
            if (!this.useMessagesApi) {
                delete body.max_tokens;
            }
            if (!this.useResponsesApi && !this.useMessagesApi && body.stream) {
                body['stream_options'] = { 'include_usage': true };
            }
        }
    }
    get urlOrRequestMetadata() {
        return this._modelUrl;
    }
    getExtraHeaders() {
        const headers = {
            'Content-Type': 'application/json'
        };
        if (this._modelUrl.includes('openai.azure')) {
            headers['api-key'] = this._apiKey;
        }
        else {
            headers['Authorization'] = `Bearer ${this._apiKey}`;
        }
        for (const [key, value] of Object.entries(this._customHeaders)) {
            headers[key] = value;
        }
        return headers;
    }
    cloneWithTokenOverride(modelMaxPromptTokens) {
        const newModelInfo = { ...this.modelMetadata, maxInputTokens: modelMaxPromptTokens };
        return this.instantiationService.createInstance(OpenAIEndpoint_1, newModelInfo, this._apiKey, this._modelUrl);
    }
    async makeChatRequest2(options, token) {
        // Apply ignoreStatefulMarker: false for initial request
        const modifiedOptions = { ...options, ignoreStatefulMarker: false };
        const response = await super.makeChatRequest2(modifiedOptions, token);
        return hydrateBYOKErrorMessages(response);
    }
};
exports.OpenAIEndpoint = OpenAIEndpoint;
exports.OpenAIEndpoint = OpenAIEndpoint = OpenAIEndpoint_1 = __decorate([
    __param(3, domainService_1.IDomainService),
    __param(4, chatMLFetcher_1.IChatMLFetcher),
    __param(5, tokenizer_1.ITokenizerProvider),
    __param(6, instantiation_1.IInstantiationService),
    __param(7, configurationService_1.IConfigurationService),
    __param(8, nullExperimentationService_1.IExperimentationService),
    __param(9, chatWebSocketManager_1.IChatWebSocketManager),
    __param(10, logService_1.ILogService)
], OpenAIEndpoint);
//# sourceMappingURL=openAIEndpoint.js.map