"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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var OpenRouterLlmProvider_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenRouterLlmProvider = void 0;
const common_1 = require("@nestjs/common");
const application_state_service_1 = require("../application-state/application-state.service");
const llm_call_logs_service_1 = require("../llm-call-logs/llm-call-logs.service");
let OpenRouterLlmProvider = OpenRouterLlmProvider_1 = class OpenRouterLlmProvider {
    constructor(applicationStateService, llmCallLogsService) {
        this.applicationStateService = applicationStateService;
        this.llmCallLogsService = llmCallLogsService;
        this.logger = new common_1.Logger(OpenRouterLlmProvider_1.name);
        this.activeStreams = new Map();
    }
    mapHistory(history) {
        return history.map((turn) => {
            if (turn.role === 'tool') {
                return {
                    role: 'tool',
                    tool_call_id: turn.tool_call_id,
                    content: turn.parts.map((p) => p.text).join(''),
                };
            }
            const mappedTurn = {
                role: turn.role === 'model' ? 'assistant' : 'user',
                content: turn.parts.map((p) => p.text).join(''),
            };
            if (turn.tool_calls) {
                mappedTurn.tool_calls = turn.tool_calls.map((tc) => ({
                    id: tc.id,
                    type: tc.type,
                    function: {
                        name: tc.function.name,
                        arguments: typeof tc.function.arguments === 'string'
                            ? tc.function.arguments
                            : JSON.stringify(tc.function.arguments),
                    },
                }));
            }
            if (turn.thoughts) {
                mappedTurn.thoughts = turn.thoughts;
            }
            return mappedTurn;
            return mappedTurn;
        });
    }
    mapTools(tools) {
        if (!tools || tools.length === 0)
            return undefined;
        return tools;
    }
    async getModels() {
        const apiKey = await this.applicationStateService.getOpenrouterApiKey();
        if (!apiKey)
            return [];
        try {
            const response = await fetch('https://openrouter.ai/api/v1/models', {
                headers: {
                    Authorization: `Bearer ${apiKey}`,
                },
            });
            if (!response.ok)
                return [];
            const data = await response.json();
            return (data.data || []).map((m) => ({
                id: m.id,
                name: m.name,
                provider: 'openrouter',
            }));
        }
        catch (e) {
            this.logger.error(`Failed to fetch OpenRouter models: ${e.message}`);
            return [];
        }
    }
    async generateContent(request) {
        const apiKey = await this.applicationStateService.getOpenrouterApiKey();
        if (!apiKey) {
            throw new Error('OpenRouter API Key is not configured in settings.');
        }
        const { prompt, systemInstruction, history, modelId, generationConfig, tools, tool_choice, onToken, abortController, } = request;
        const model = modelId || 'openai/gpt-3.5-turbo';
        const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        const startTime = Date.now();
        let llmCallLogId = null;
        let logAlreadyUpdated = false;
        const messages = [];
        if (systemInstruction) {
            messages.push({ role: 'system', content: systemInstruction });
        }
        if (history && history.length > 0) {
            messages.push(...this.mapHistory(history));
        }
        else {
            messages.push({ role: 'user', content: prompt });
        }
        await this.applicationStateService.trackUsedModel(model);
        this.logger.log(`Calling OpenRouter API with model: ${model}`);
        try {
            llmCallLogId = (await this.llmCallLogsService.createLog({
                session_input_id: 'pending',
                provider: 'openrouter',
                model_id: model,
                request_body: JSON.stringify({
                    model,
                    messages,
                    tools,
                    tool_choice,
                    stream: !!onToken,
                    temperature: generationConfig?.temperature,
                    max_tokens: generationConfig?.maxOutputTokens,
                    top_p: generationConfig?.topP,
                    top_k: generationConfig?.topK,
                    reasoning: generationConfig?.reasoning,
                }),
            })).id;
        }
        catch (logError) {
            this.logger.warn(`Failed to create LLM call log: ${logError.message}`);
        }
        try {
            const body = {
                model,
                messages,
            };
            if (onToken) {
                body.stream = true;
                body.stream_options = { include_usage: true };
            }
            if (generationConfig) {
                if (generationConfig.temperature !== undefined)
                    body.temperature = generationConfig.temperature;
                if (generationConfig.maxOutputTokens !== undefined)
                    body.max_tokens = generationConfig.maxOutputTokens;
                if (generationConfig.topP !== undefined)
                    body.top_p = generationConfig.topP;
                if (generationConfig.topK !== undefined)
                    body.top_k = generationConfig.topK;
                if (generationConfig.reasoning !== undefined)
                    body.reasoning = generationConfig.reasoning;
            }
            if (tools && tools.length > 0) {
                body.tools = this.mapTools(tools);
                if (tool_choice) {
                    body.tool_choice = tool_choice;
                }
            }
            const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${apiKey}`,
                    'X-Title': 'Repoburg',
                },
                body: JSON.stringify(body),
                signal: abortController?.signal,
            });
            if (!response.ok) {
                const errorText = await response.text();
                const errorData = this.tryParseJson(errorText);
                const errorObj = errorData?.error;
                let errorMessage = errorObj?.message || `OpenRouter API error: ${response.statusText}`;
                if (errorObj?.code)
                    errorMessage = `[${errorObj.code}] ${errorMessage}`;
                if (errorObj?.metadata)
                    errorMessage += `\nMetadata: ${JSON.stringify(errorObj.metadata)}`;
                if (llmCallLogId) {
                    try {
                        await this.llmCallLogsService.updateLog(llmCallLogId, {
                            status_code: response.status,
                            latency_ms: Date.now() - startTime,
                            error_message: errorMessage,
                            response_body: errorText,
                        });
                        logAlreadyUpdated = true;
                    }
                    catch (updateError) {
                        this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                    }
                }
                const error = new Error(errorMessage);
                error.status = response.status;
                throw error;
            }
            if (onToken && response.body) {
                let accumulatedResponseBody = '';
                if (abortController) {
                    this.activeStreams.set(requestId, abortController);
                }
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let buffer = '';
                let accumulatedResponse = '';
                let accumulatedReasoning = '';
                let isFirst = true;
                let usageData = null;
                const accumulatedToolCalls = new Map();
                try {
                    while (true) {
                        if (abortController?.signal?.aborted) {
                            this.activeStreams.delete(requestId);
                            throw new DOMException('The operation was aborted', 'AbortError');
                        }
                        const { done, value } = await reader.read();
                        if (done) {
                            break;
                        }
                        buffer += decoder.decode(value, { stream: true });
                        const lines = buffer.split('\n');
                        buffer = lines.pop() || '';
                        for (const line of lines) {
                            if (line.startsWith('data: ')) {
                                const dataStr = line.substring(6);
                                if (dataStr.trim() === '[DONE]') {
                                    break;
                                }
                                try {
                                    const parsed = JSON.parse(dataStr);
                                    const chunk = parsed.choices?.[0]?.delta?.content;
                                    if (chunk) {
                                        accumulatedResponse += chunk;
                                        onToken(chunk, isFirst);
                                        if (isFirst) {
                                            isFirst = false;
                                        }
                                    }
                                    const reasoningChunk = parsed.choices?.[0]?.delta?.reasoning_content ||
                                        parsed.choices?.[0]?.delta?.reasoning;
                                    if (reasoningChunk) {
                                        accumulatedReasoning += reasoningChunk;
                                        onToken?.('', false, reasoningChunk);
                                    }
                                    const deltaToolCalls = parsed.choices?.[0]?.delta?.tool_calls;
                                    if (deltaToolCalls) {
                                        for (const deltaCall of deltaToolCalls) {
                                            const index = deltaCall.index;
                                            if (!accumulatedToolCalls.has(index)) {
                                                accumulatedToolCalls.set(index, {
                                                    id: deltaCall.id,
                                                    type: deltaCall.type,
                                                    function: {
                                                        name: deltaCall.function?.name || '',
                                                        arguments: '',
                                                    },
                                                });
                                            }
                                            const existing = accumulatedToolCalls.get(index);
                                            if (deltaCall.function?.arguments) {
                                                existing.function.arguments +=
                                                    deltaCall.function.arguments;
                                            }
                                        }
                                    }
                                    if (parsed.usage && !usageData) {
                                        usageData = {
                                            prompt_tokens: parsed.usage.prompt_tokens,
                                            completion_tokens: parsed.usage.completion_tokens,
                                            cached_tokens: parsed.usage.prompt_tokens_details?.cached_tokens || 0,
                                        };
                                        accumulatedResponseBody += dataStr + '\n';
                                    }
                                }
                                catch (e) {
                                    this.logger.warn(`Error parsing stream chunk: ${dataStr}`, e);
                                }
                            }
                        }
                    }
                }
                catch (readError) {
                    if (abortController?.signal?.aborted) {
                        this.activeStreams.delete(requestId);
                        throw new DOMException('The operation was aborted', 'AbortError');
                    }
                    throw readError;
                }
                const finalToolCalls = [];
                for (const toolCall of accumulatedToolCalls.values()) {
                    try {
                        finalToolCalls.push({
                            ...toolCall,
                            function: {
                                ...toolCall.function,
                                arguments: typeof toolCall.function.arguments === 'string'
                                    ? JSON.parse(toolCall.function.arguments)
                                    : toolCall.function.arguments,
                            },
                        });
                    }
                    catch (e) {
                        this.logger.warn(`Failed to parse tool call arguments: ${toolCall.function.arguments}`, e);
                    }
                }
                if (usageData) {
                    this.logger.log(`OpenRouter stream finished. Usage: ${usageData.prompt_tokens} prompt (${usageData.cached_tokens} cached), ${usageData.completion_tokens} completion tokens`);
                    if (llmCallLogId) {
                        try {
                            await this.llmCallLogsService.updateLog(llmCallLogId, {
                                status_code: 200,
                                latency_ms: Date.now() - startTime,
                                response_body: accumulatedResponseBody ||
                                    JSON.stringify({ usage: usageData, stream_chunks: '...' }),
                                input_tokens: usageData.prompt_tokens,
                                output_tokens: usageData.completion_tokens,
                                cached_tokens: usageData.cached_tokens,
                            });
                        }
                        catch (updateError) {
                            this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                        }
                    }
                    this.activeStreams.delete(requestId);
                    return {
                        text: accumulatedResponse,
                        usage: {
                            inputTokens: usageData.prompt_tokens,
                            outputTokens: usageData.completion_tokens,
                            cachedTokens: usageData.cached_tokens,
                        },
                        tool_calls: finalToolCalls.length > 0 ? finalToolCalls : undefined,
                        thoughts: accumulatedReasoning || undefined,
                    };
                }
                this.logger.warn('Usage data not returned from streaming response');
                if (llmCallLogId) {
                    try {
                        await this.llmCallLogsService.updateLog(llmCallLogId, {
                            status_code: 200,
                            latency_ms: Date.now() - startTime,
                            response_body: accumulatedResponseBody ||
                                JSON.stringify({ stream_chunks: '...' }),
                        });
                    }
                    catch (updateError) {
                        this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                    }
                }
                this.activeStreams.delete(requestId);
                return {
                    text: accumulatedResponse,
                    tool_calls: finalToolCalls.length > 0 ? finalToolCalls : undefined,
                    thoughts: accumulatedReasoning || undefined,
                };
            }
            const data = await response.json();
            const content = data.choices?.[0]?.message?.content;
            const rawToolCalls = data.choices?.[0]?.message?.tool_calls;
            const reasoningContent = data.choices?.[0]?.message?.reasoning ||
                data.choices?.[0]?.message?.reasoning_content;
            const usage = data.usage;
            let parsedToolCalls;
            if (rawToolCalls && rawToolCalls.length > 0) {
                parsedToolCalls = rawToolCalls.map((tc) => ({
                    id: tc.id,
                    type: tc.type,
                    function: {
                        name: tc.function.name,
                        arguments: typeof tc.function.arguments === 'string'
                            ? JSON.parse(tc.function.arguments)
                            : tc.function.arguments,
                    },
                }));
            }
            if (!content && !parsedToolCalls) {
                const error = new Error('OpenRouter returned an empty response.');
                error.status = response.status || 0;
                throw error;
            }
            if (usage) {
                const cachedTokens = usage.prompt_tokens_details?.cached_tokens || 0;
                this.logger.log(`OpenRouter response. Usage: ${usage.prompt_tokens} prompt (${cachedTokens} cached), ${usage.completion_tokens} completion tokens`);
                if (llmCallLogId) {
                    try {
                        await this.llmCallLogsService.updateLog(llmCallLogId, {
                            status_code: 200,
                            latency_ms: Date.now() - startTime,
                            response_body: JSON.stringify(data),
                            input_tokens: usage.prompt_tokens,
                            output_tokens: usage.completion_tokens,
                            cached_tokens: cachedTokens,
                        });
                    }
                    catch (updateError) {
                        this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                    }
                }
                return {
                    text: content || '',
                    usage: {
                        inputTokens: usage.prompt_tokens,
                        outputTokens: usage.completion_tokens,
                        cachedTokens: cachedTokens,
                    },
                    tool_calls: parsedToolCalls,
                    thoughts: reasoningContent || undefined,
                };
            }
            if (llmCallLogId) {
                try {
                    await this.llmCallLogsService.updateLog(llmCallLogId, {
                        status_code: 200,
                        latency_ms: Date.now() - startTime,
                        response_body: JSON.stringify(data),
                    });
                }
                catch (updateError) {
                    this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                }
            }
            this.logger.warn('Usage data not returned from response');
            return {
                text: content || '',
                tool_calls: parsedToolCalls,
                thoughts: reasoningContent || undefined,
            };
        }
        catch (error) {
            this.logger.error(`OpenRouter generation failed: ${error.message}`);
            if (llmCallLogId && !logAlreadyUpdated) {
                try {
                    const errorName = error instanceof Error ? error.name : 'UnknownError';
                    let errorMessage = `[${errorName}] ${error.message}`;
                    if (error.status)
                        errorMessage += `\nHTTP Status: ${error.status}`;
                    const errorCause = error instanceof Error ? error.cause : undefined;
                    if (errorCause) {
                        errorMessage += `\nCause: ${errorCause instanceof Error ? errorCause.message : String(errorCause)}`;
                    }
                    if (error instanceof Error && error.stack) {
                        const stackLines = error.stack.split('\n').slice(1, 4).join('\n');
                        errorMessage += `\n${stackLines}`;
                    }
                    await this.llmCallLogsService.updateLog(llmCallLogId, {
                        status_code: error.status || null,
                        latency_ms: Date.now() - startTime,
                        error_message: errorMessage,
                    });
                }
                catch (updateError) {
                    this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                }
            }
            this.activeStreams.delete(requestId);
            throw error;
        }
    }
    tryParseJson(text) {
        try {
            return JSON.parse(text);
        }
        catch {
            return null;
        }
    }
    abortBySessionId(sessionId) {
        for (const [reqId, controller] of this.activeStreams.entries()) {
            try {
                controller.abort();
                this.activeStreams.delete(reqId);
                return true;
            }
            catch (e) {
                this.logger.warn(`Failed to abort stream ${reqId}: ${e.message}`);
            }
        }
        return false;
    }
};
exports.OpenRouterLlmProvider = OpenRouterLlmProvider;
exports.OpenRouterLlmProvider = OpenRouterLlmProvider = OpenRouterLlmProvider_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [application_state_service_1.ApplicationStateService,
        llm_call_logs_service_1.LlmCallLogsService])
], OpenRouterLlmProvider);
//# sourceMappingURL=openrouter-llm.provider.js.map