/**
 * @license
 * Copyright 2025 OElite Platform
 * SPDX-License-Identifier: Apache-2.0
 */
import { BaseProvider } from './baseProvider.js';
import { ProviderType } from './types.js';
/**
 * Ollama provider implementation
 * Supports running large language models locally
 */
export class OllamaProvider extends BaseProvider {
    static DEFAULT_BASE_URL = 'http://localhost:11434';
    static REQUEST_TIMEOUT_MS = 5000; // 5 second timeout for all requests
    constructor(config) {
        super({
            ...config,
            type: ProviderType.OLLAMA,
            baseUrl: config.baseUrl || OllamaProvider.DEFAULT_BASE_URL,
        });
    }
    /**
     * Fetch with timeout to prevent hanging
     */
    async fetchWithTimeout(url, options = {}) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), OllamaProvider.REQUEST_TIMEOUT_MS);
        try {
            const response = await fetch(url, {
                ...options,
                signal: controller.signal,
            });
            clearTimeout(timeoutId);
            return response;
        }
        catch (error) {
            clearTimeout(timeoutId);
            if (error instanceof Error && error.name === 'AbortError') {
                throw new Error(`Request to ${url} timed out after ${OllamaProvider.REQUEST_TIMEOUT_MS}ms`);
            }
            throw error;
        }
    }
    async doInitialize() {
        const baseUrl = this.getEffectiveBaseUrl();
        // Try to ping Ollama server with timeout
        try {
            const response = await this.fetchWithTimeout(`${baseUrl}/api/tags`, {
                method: 'GET',
            });
            if (!response.ok) {
                throw new Error(`Ollama server returned ${response.status}`);
            }
        }
        catch (error) {
            throw new Error(`Cannot connect to Ollama server at ${baseUrl}. Make sure Ollama is running. ${error instanceof Error ? error.message : ''}`);
        }
    }
    isConfigured() {
        // Ollama doesn't require API keys, just needs to be running
        return true;
    }
    async getAvailableModels() {
        const baseUrl = this.getEffectiveBaseUrl();
        try {
            const response = await this.fetchWithTimeout(`${baseUrl}/api/tags`, {
                method: 'GET',
            });
            if (!response.ok) {
                throw new Error(`Ollama API error: ${response.status}`);
            }
            const data = await response.json();
            const models = [];
            if (data.models && Array.isArray(data.models)) {
                for (const model of data.models) {
                    models.push({
                        id: model.name,
                        name: model.name,
                        provider: ProviderType.OLLAMA,
                        description: `Local model: ${model.name}`,
                        capabilities: {
                            supportsVision: model.name.includes('vision'),
                            supportsStreaming: true,
                            supportsFunctionCalling: false,
                            contextWindow: this.estimateContextWindow(model.name),
                        },
                    });
                }
            }
            return models;
        }
        catch (error) {
            console.error('Failed to fetch Ollama models:', error);
            return [];
        }
    }
    async doHealthCheck() {
        const baseUrl = this.getEffectiveBaseUrl();
        const response = await this.fetchWithTimeout(`${baseUrl}/api/tags`, {
            method: 'GET',
        });
        if (!response.ok) {
            throw new Error(`Ollama health check failed: ${response.status}`);
        }
    }
    async createCompletion(model, messages, options) {
        const baseUrl = this.getEffectiveBaseUrl();
        // Convert messages to Ollama format
        const prompt = this.formatMessagesAsPrompt(messages);
        const response = await fetch(`${baseUrl}/api/generate`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                model,
                prompt,
                stream: false,
                options: {
                    temperature: options?.temperature,
                    top_p: options?.top_p,
                    num_predict: options?.max_tokens,
                    ...options,
                },
            }),
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`Ollama API error: ${response.status} - ${error}`);
        }
        return await response.json();
    }
    async *createStreamingCompletion(model, messages, options) {
        const baseUrl = this.getEffectiveBaseUrl();
        const prompt = this.formatMessagesAsPrompt(messages);
        const response = await fetch(`${baseUrl}/api/generate`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                model,
                prompt,
                stream: true,
                options: {
                    temperature: options?.temperature,
                    top_p: options?.top_p,
                    num_predict: options?.max_tokens,
                    ...options,
                },
            }),
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`Ollama API error: ${response.status} - ${error}`);
        }
        if (!response.body) {
            throw new Error('No response body');
        }
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = '';
        try {
            while (true) {
                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;
                    try {
                        const parsed = JSON.parse(line);
                        yield parsed;
                    }
                    catch (e) {
                        // Skip invalid JSON
                    }
                }
            }
        }
        finally {
            reader.releaseLock();
        }
    }
    /**
     * Format messages as a single prompt string for Ollama
     */
    formatMessagesAsPrompt(messages) {
        return messages
            .map((msg) => {
            const role = msg.role === 'assistant' ? 'Assistant' : 'User';
            return `${role}: ${msg.content}`;
        })
            .join('\n\n');
    }
    /**
     * Estimate context window based on model name
     */
    estimateContextWindow(modelName) {
        if (modelName.includes('32k'))
            return 32768;
        if (modelName.includes('16k'))
            return 16384;
        if (modelName.includes('128k'))
            return 131072;
        return 4096; // Default
    }
    /**
     * Pull a new model from Ollama registry
     */
    async pullModel(modelName) {
        const baseUrl = this.getEffectiveBaseUrl();
        const response = await fetch(`${baseUrl}/api/pull`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                name: modelName,
            }),
        });
        if (!response.ok) {
            throw new Error(`Failed to pull model: ${response.status}`);
        }
        console.log(`Successfully pulled model: ${modelName}`);
    }
    /**
     * Get Ollama server version
     */
    async getVersion() {
        const baseUrl = this.getEffectiveBaseUrl();
        try {
            const response = await fetch(`${baseUrl}/api/version`, {
                method: 'GET',
            });
            if (response.ok) {
                const data = await response.json();
                return data.version || 'unknown';
            }
        }
        catch (error) {
            // Ignore errors
        }
        return 'unknown';
    }
}
//# sourceMappingURL=ollamaProvider.js.map