"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 OllamaLlmProvider_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OllamaLlmProvider = 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");
const OLLAMA_CLOUD_MODELS = [
    'glm-5.1:cloud',
    'minimax-m3:cloud',
    'minimax-m2.7:cloud',
    'deepseek-v4-flash:cloud',
    'deepseek-v4-pro:cloud',
    'gemma4:31b-cloud',
    'qwen3.5:cloud',
    'nemotron-3-super:cloud',
    'qwen3-next:cloud',
    'kimi-k2.6:cloud',
    'kimi-k2.5:cloud',
    'glm-5:cloud',
    'nemotron-3-nano:cloud',
    'minimax-m2.5:cloud',
    'gemini-3-flash-preview:cloud',
    'glm-4.7:cloud',
    'deepseek-v3.2:cloud',
    'minimax-m2:cloud',
    'kimi-k2-thinking:cloud',
    'gpt-oss:cloud',
    'qwen3-vl:cloud',
    'deepseek-v3.1:cloud',
    'glm-4.6:cloud',
];
let OllamaLlmProvider = OllamaLlmProvider_1 = class OllamaLlmProvider {
    constructor(applicationStateService, llmCallLogsService) {
        this.applicationStateService = applicationStateService;
        this.llmCallLogsService = llmCallLogsService;
        this.logger = new common_1.Logger(OllamaLlmProvider_1.name);
        this.baseUrl = 'https://ollama.com/api';
        this.activeStreams = new Map();
    }
    mapHistory(history) {
        return history.map((turn) => {
            if (turn.role === 'tool') {
                return {
                    role: 'tool',
                    tool_name: turn.tool_name,
                    content: turn.parts.map((p) => p.text).join(''),
                };
            }
            const content = turn.parts.map((p) => p.text).join('');
            const mappedTurn = {
                role: turn.role === 'model' ? 'assistant' : 'user',
                content: content,
            };
            if (turn.shortId) {
                mappedTurn.short_id = turn.shortId;
            }
            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.thinking = turn.thoughts;
            }
            return mappedTurn;
        });
    }
    convertToolCallArgumentsForOllama(messages) {
        return messages.map((turn) => {
            if (turn.tool_calls) {
                turn.tool_calls = turn.tool_calls.map((tc, index) => ({
                    function: {
                        index: index,
                        name: tc.function?.name || '',
                        arguments: (() => {
                            if (typeof tc.function?.arguments === 'string') {
                                try {
                                    return JSON.parse(tc.function.arguments);
                                }
                                catch {
                                    this.logger.warn(`Failed to parse tool arguments for Ollama: ${tc.function.arguments}`);
                                    return tc.function.arguments;
                                }
                            }
                            return tc.function?.arguments || {};
                        })(),
                    },
                }));
            }
            return turn;
        });
    }
    mapTools(tools) {
        if (!tools || tools.length === 0)
            return undefined;
        return tools;
    }
    async getModels() {
        const apiKey = await this.applicationStateService.getOllamaApiKey();
        if (!apiKey) {
            this.logger.warn('Ollama API key not configured, returning empty models list');
            return [];
        }
        const models = OLLAMA_CLOUD_MODELS.map((modelId) => ({
            id: `ollama/${modelId}`,
            name: modelId,
            provider: 'ollama',
        }));
        return models;
    }
    async generateContent(request) {
        const apiKey = await this.applicationStateService.getOllamaApiKey();
        if (!apiKey) {
            throw new Error('Ollama API Key is not configured in settings.');
        }
        const { prompt, systemInstruction, history, modelId, generationConfig, tools, onToken, abortController, } = request;
        const ollamaModelId = modelId
            ? modelId.replace(/^ollama\//, '')
            : 'glm-5.1:cloud';
        const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        const messages = [];
        if (systemInstruction) {
            messages.push({ role: 'system', content: systemInstruction });
        }
        if (history && history.length > 0) {
            const mappedHistory = this.mapHistory(history);
            const ollamaHistory = this.convertToolCallArgumentsForOllama(mappedHistory);
            messages.push(...ollamaHistory);
        }
        else {
            messages.push({ role: 'user', content: prompt });
        }
        await this.applicationStateService.trackUsedModel(modelId);
        this.logger.log(`Calling Ollama API with model: ${ollamaModelId}`);
        const startTime = Date.now();
        let llmCallLogId = null;
        let logAlreadyUpdated = false;
        try {
            llmCallLogId = (await this.llmCallLogsService.createLog({
                session_input_id: 'pending',
                provider: 'ollama',
                model_id: ollamaModelId,
                request_body: JSON.stringify({
                    model: ollamaModelId,
                    messages,
                    tools,
                    stream: !!onToken,
                    options: generationConfig,
                }),
            })).id;
        }
        catch (logError) {
            this.logger.warn(`Failed to create LLM call log: ${logError.message}`);
        }
        try {
            const body = {
                model: ollamaModelId,
                messages,
            };
            if (onToken) {
                body.stream = true;
            }
            else {
                body.stream = false;
            }
            if (generationConfig) {
                body.options = {};
                if (generationConfig.temperature !== undefined) {
                    body.options.temperature = generationConfig.temperature;
                }
                if (generationConfig.maxOutputTokens !== undefined) {
                    body.options.num_predict = generationConfig.maxOutputTokens;
                }
                if (generationConfig.topP !== undefined) {
                    body.options.top_p = generationConfig.topP;
                }
            }
            if (generationConfig?.reasoning?.effort) {
                body.think = generationConfig.reasoning.effort;
            }
            if (tools && tools.length > 0) {
                body.tools = this.mapTools(tools);
            }
            const response = await fetch(`${this.baseUrl}/chat`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${apiKey}`,
                },
                body: JSON.stringify(body),
                signal: abortController?.signal,
            });
            if (!response.ok) {
                const errorText = await response.text();
                const errorData = this.tryParseJson(errorText);
                const errorMessage = errorData?.error || `Ollama API error: ${response.statusText}`;
                this.logger.error(`Ollama API error: ${errorMessage}`);
                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) {
                if (abortController) {
                    this.activeStreams.set(requestId, abortController);
                }
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let buffer = '';
                let accumulatedResponse = '';
                let accumulatedThinking = '';
                let isFirst = true;
                let promptEvalCount = 0;
                let evalCount = 0;
                let finalToolCalls = [];
                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.trim())
                                continue;
                            let parsed;
                            try {
                                parsed = JSON.parse(line);
                            }
                            catch {
                                this.logger.warn(`Error parsing NDJSON line: ${line}`);
                                continue;
                            }
                            if (parsed.error) {
                                this.logger.error(`Ollama stream error: ${parsed.error}`);
                                const streamError = new Error(`Ollama stream error: ${parsed.error}`);
                                streamError.status = response.status;
                                throw streamError;
                            }
                            if (parsed.message?.content) {
                                accumulatedResponse += parsed.message.content;
                                onToken(parsed.message.content, isFirst);
                                if (isFirst)
                                    isFirst = false;
                            }
                            if (parsed.message?.thinking) {
                                accumulatedThinking += parsed.message.thinking;
                                onToken?.('', false, parsed.message.thinking);
                            }
                            if (parsed.message?.tool_calls) {
                                finalToolCalls = parsed.message.tool_calls;
                            }
                            if (parsed.done) {
                                promptEvalCount = parsed.prompt_eval_count || 0;
                                evalCount = parsed.eval_count || 0;
                                this.logger.log(`Stream finished. Tokens: ${promptEvalCount} prompt, ${evalCount} completion`);
                            }
                        }
                    }
                }
                catch (readError) {
                    if (abortController?.signal?.aborted) {
                        this.activeStreams.delete(requestId);
                        throw new DOMException('The operation was aborted', 'AbortError');
                    }
                    throw readError;
                }
                const parsedToolCalls = finalToolCalls.length > 0
                    ? finalToolCalls.map((tc) => ({
                        id: tc.id || `tool-${Date.now()}`,
                        type: tc.type || 'function',
                        function: {
                            name: tc.function?.name || '',
                            arguments: (() => {
                                if (typeof tc.function?.arguments === 'string') {
                                    try {
                                        return JSON.parse(tc.function.arguments);
                                    }
                                    catch {
                                        this.logger.warn(`Failed to parse tool arguments: ${tc.function.arguments}`);
                                        return tc.function.arguments;
                                    }
                                }
                                return tc.function?.arguments || {};
                            })(),
                        },
                    }))
                    : undefined;
                if (llmCallLogId) {
                    try {
                        await this.llmCallLogsService.updateLog(llmCallLogId, {
                            status_code: 200,
                            latency_ms: Date.now() - startTime,
                            response_body: JSON.stringify({
                                text: accumulatedResponse,
                                thinking: accumulatedThinking,
                                tool_calls: finalToolCalls,
                                prompt_eval_count: promptEvalCount,
                                eval_count: evalCount,
                            }),
                            input_tokens: promptEvalCount,
                            output_tokens: evalCount,
                        });
                    }
                    catch (updateError) {
                        this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                    }
                }
                this.activeStreams.delete(requestId);
                return {
                    text: accumulatedResponse,
                    usage: {
                        inputTokens: promptEvalCount,
                        outputTokens: evalCount,
                    },
                    tool_calls: parsedToolCalls,
                    thoughts: accumulatedThinking || undefined,
                };
            }
            const data = await response.json();
            const content = data.message?.content || '';
            const thinking = data.message?.thinking || '';
            const rawToolCalls = data.message?.tool_calls || [];
            const promptEvalCount = data.prompt_eval_count || 0;
            const evalCount = data.eval_count || 0;
            const parsedToolCalls = rawToolCalls.length > 0
                ? rawToolCalls.map((tc) => ({
                    id: tc.id || `tool-${Date.now()}`,
                    type: tc.type || 'function',
                    function: {
                        name: tc.function?.name || '',
                        arguments: (() => {
                            if (typeof tc.function?.arguments === 'string') {
                                try {
                                    return JSON.parse(tc.function.arguments);
                                }
                                catch {
                                    this.logger.warn(`Failed to parse tool arguments: ${tc.function.arguments}`);
                                    return tc.function.arguments;
                                }
                            }
                            return tc.function?.arguments || {};
                        })(),
                    },
                }))
                : undefined;
            if (!content && !parsedToolCalls) {
                const error = new Error('Ollama returned an empty response.');
                error.status = response.status || 0;
                throw error;
            }
            this.logger.log(`Response tokens: ${promptEvalCount} prompt, ${evalCount} completion`);
            if (llmCallLogId) {
                try {
                    await this.llmCallLogsService.updateLog(llmCallLogId, {
                        status_code: 200,
                        latency_ms: Date.now() - startTime,
                        response_body: JSON.stringify(data),
                        input_tokens: promptEvalCount,
                        output_tokens: evalCount,
                    });
                }
                catch (updateError) {
                    this.logger.warn(`Failed to update LLM call log: ${updateError.message}`);
                }
            }
            return {
                text: content,
                usage: {
                    inputTokens: promptEvalCount,
                    outputTokens: evalCount,
                },
                tool_calls: parsedToolCalls,
                thoughts: thinking || undefined,
            };
        }
        catch (error) {
            this.logger.error(`Ollama 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.OllamaLlmProvider = OllamaLlmProvider;
exports.OllamaLlmProvider = OllamaLlmProvider = OllamaLlmProvider_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [application_state_service_1.ApplicationStateService,
        llm_call_logs_service_1.LlmCallLogsService])
], OllamaLlmProvider);
//# sourceMappingURL=ollama-llm.provider.js.map