"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
const http_1 = __importDefault(require("../utils/http"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class HttpClient {
    baseUrl;
    timeout;
    headers;
    extra_body;
    pricingMap = {};
    constructor(baseUrl, options = {}) {
        this.baseUrl = baseUrl;
        this.headers = options.headers ?? {};
        this.timeout = options.timeout ?? 30000;
        this.extra_body = options.extra_body ?? {};
    }
    async withRetry(fn, retries = 3) {
        let lastError;
        for (let attempt = 0; attempt <= retries; attempt++) {
            try {
                return await fn();
            }
            catch (e) {
                lastError = e;
                const errorStr = e.toString();
                const isNonRetriable = errorStr.includes("401") ||
                    errorStr.includes("403") ||
                    errorStr.includes("404") ||
                    errorStr.includes("429");
                const isRetriable = !isNonRetriable &&
                    (errorStr.match(/5\d\d/) ||
                        errorStr.includes("timeout") ||
                        errorStr.includes("ECONNRESET") ||
                        errorStr.includes("ETIMEDOUT") ||
                        errorStr.includes("Invalid response format from MCP") ||
                        errorStr.includes("Failed to get models"));
                if (!isRetriable || attempt >= retries) {
                    throw e;
                }
                const delay = 1000 * Math.pow(2, attempt);
                console.warn(`HTTP request failed (attempt ${attempt + 1}/${retries}), retrying in ${delay}ms...`, e.message);
                await new Promise((resolve) => setTimeout(resolve, delay));
            }
        }
        throw lastError;
    }
    setJwt(jwt) {
        this.headers = {
            ...this.headers,
            Authorization: `Bearer ${jwt}`,
        };
    }
    setBaseUrl(baseUrl) {
        this.baseUrl = baseUrl;
    }
    setKey(key) {
        this.setJwt(key);
    }
    setPrices(pricingMap) {
        this.pricingMap = pricingMap;
    }
    calculateCost(model, usage) {
        if (!usage)
            return undefined;
        const pricing = this.pricingMap[model];
        if (!pricing)
            return undefined;
        const cachedInputTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
        const inputTokens = usage.prompt_tokens ?? 0;
        const outputTokens = usage.completion_tokens ?? 0;
        const cachedInputCost = (cachedInputTokens * (pricing.cache_hit ?? pricing.cached_input ?? 0)) / 1e6;
        const inputCost = ((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
        const outputCost = (outputTokens * (pricing.output ?? 0)) / 1e6;
        return cachedInputCost + inputCost + outputCost;
    }
    setOptions(options) {
        if (options.timeout !== undefined)
            this.timeout = options.timeout;
        if (options.extra_body !== undefined)
            this.extra_body = options.extra_body;
        if (options.headers) {
            this.headers = { ...this.headers, ...options.headers };
        }
    }
    loadJwtFile(filePath) {
        try {
            const jwtFile = path_1.default.join(process.cwd(), filePath);
            if (!fs_1.default.existsSync(jwtFile)) {
                throw new Error(`JWT file not found: ${filePath}`);
            }
            const jwt = fs_1.default.readFileSync(jwtFile, "utf-8").trim();
            this.setJwt(jwt);
        }
        catch (error) {
            console.error(`Error loading JWT file: ${error}`);
        }
    }
    async createChatCompletion(options) {
        return this.withRetry(async () => {
            const body = {
                ...options,
                model: options.model,
                messages: options.messages,
                max_tokens: options.max_tokens || 4000,
                ...this.extra_body,
                ...(options.tools && {
                    tools: options.tools,
                    tool_choice: "auto",
                }),
            };
            const response = await http_1.default.post(`${this.baseUrl}/v1/chat/completions`, body, { headers: this.headers, timeout: this.timeout });
            const data = response.data;
            if (data.error) {
                throw new Error(JSON.stringify(data.error, null, 2));
            }
            return {
                choices: data.choices.map((choice) => ({
                    message: {
                        role: choice.message.role,
                        content: choice.message.content,
                        tool_calls: choice.message.tool_calls,
                    },
                })),
                model: data.model,
                usage: data.usage,
                usd_cost: data.usd_cost ?? this.calculateCost(options.model, data.usage),
            };
        });
    }
    async createResponse(options, store = false) {
        return this.withRetry(async () => {
            const systemMessages = options.messages.filter((m) => m.role === "system");
            const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
            const instructions = systemMessages
                .map((m) => (typeof m.content === "string" ? m.content : ""))
                .join("\n")
                .trim() || undefined;
            const input = nonSystemMessages.map((msg) => {
                if (msg.role === "tool") {
                    return {
                        type: "function_call_output",
                        call_id: msg.tool_call_id,
                        output: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
                    };
                }
                if (msg.role === "assistant" && msg.tool_calls?.length) {
                    return msg.tool_calls.map((tc) => ({
                        type: "function_call",
                        id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
                        call_id: tc.id,
                        name: tc.function.name,
                        arguments: tc.function.arguments,
                    }));
                }
                return {
                    role: msg.role,
                    content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
                };
            }).flat();
            const tools = options.tools?.map((tool) => ({
                type: "function",
                name: tool.function.name,
                description: tool.function.description,
                parameters: tool.function.parameters,
                strict: false,
            }));
            const body = {
                model: options.model,
                input,
                ...(instructions && { instructions }),
                ...(options.max_tokens && { max_output_tokens: options.max_tokens }),
                ...(tools?.length && { tools, tool_choice: "auto" }),
                store,
                ...this.extra_body,
            };
            const response = await http_1.default.post(`${this.baseUrl}/v1/responses`, body, { headers: this.headers, timeout: this.timeout });
            const data = response.data;
            if (data.error) {
                throw new Error(JSON.stringify(data.error, null, 2));
            }
            const usage = data.usage
                ? {
                    prompt_tokens: data.usage.input_tokens,
                    completion_tokens: data.usage.output_tokens,
                    total_tokens: data.usage.input_tokens + data.usage.output_tokens,
                    prompt_tokens_details: {
                        cached_tokens: data.usage.input_tokens_details?.cached_tokens ?? 0,
                    },
                }
                : undefined;
            let textContent = null;
            const toolCalls = [];
            for (const item of data.output ?? []) {
                if (item.type === "message") {
                    for (const part of item.content ?? []) {
                        if (part.type === "output_text") {
                            textContent = (textContent ?? "") + part.text;
                        }
                    }
                }
                else if (item.type === "function_call") {
                    toolCalls.push({
                        id: item.call_id,
                        type: "function",
                        function: { name: item.name, arguments: item.arguments },
                    });
                }
            }
            return {
                choices: [
                    {
                        message: {
                            role: "assistant",
                            content: textContent,
                            ...(toolCalls.length > 0 && { tool_calls: toolCalls }),
                        },
                    },
                ],
                model: data.model ?? options.model,
                usage,
                usd_cost: data.usd_cost ?? this.calculateCost(options.model, usage),
            };
        });
    }
    async createEmbedding(options) {
        return this.withRetry(async () => {
            const response = await http_1.default.post(`${this.baseUrl}/v1/embeddings`, {
                model: options.model,
                input: options.input,
            }, { headers: this.headers, timeout: this.timeout });
            const data = response.data;
            if (data.error) {
                throw new Error(JSON.stringify(data.error, null, 2));
            }
            return {
                data: data.data,
                model: options.model,
                usage: data.usage,
                usd_cost: data.usd_cost ?? this.calculateCost(options.model, data.usage),
            };
        });
    }
    async getModels(type = "all") {
        return this.withRetry(async () => {
            const response = await http_1.default.get(`${this.baseUrl}/v1/models?type=${type}`, {
                headers: this.headers,
                timeout: this.timeout,
            });
            const data = response.data?.data;
            return data.map((model) => ({
                id: model.id,
                object: model.object,
                owned_by: model.owned_by,
            }));
        });
    }
}
exports.HttpClient = HttpClient;
//# sourceMappingURL=http.js.map