1 line
143 KiB
JSON
1 line
143 KiB
JSON
{"repo": "ashu90-prog/anand-cli-chatbot", "files": {"NodeJS/providers.js": {"1c917333a57dd43d35021ff195088113f03a4670": {"t": 1785746022.2470334, "n": 34040, "h": "47dfec8a75e6cffd", "c": "export const modelContextLimits = {};\n\nexport class BaseProvider {\n listModels() {\n throw new Error('Not implemented');\n }\n async *generateStream(systemPrompt, messages, model, signal) {\n throw new Error('Not implemented');\n }\n}\n\nexport class GeminiProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n if (data.models) {\n return data.models\n .filter(m => m.supportedGenerationMethods?.includes('generateContent'))\n .map(m => m.name.replace('models/', ''));\n }\n }\n } catch (e) {\n // Fallback below\n }\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('Google Gemini API Key is not configured. Set GEMINI_API_KEY env var.');\n }\n\n const modelName = model.replace('models/', '');\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:streamGenerateContent?key=${this.apiKey}`;\n \n const mapped = messages.map(msg => {\n let role = 'model';\n let content = msg.content;\n if (msg.role === 'user') {\n role = 'user';\n } else if (msg.role === 'system') {\n role = 'user';\n content = `[System Message]:\\n${msg.content}`;\n }\n return { role, content };\n });\n\n // Merge consecutive turns of the same role to conform to Gemini API constraints\n const merged = [];\n for (const m of mapped) {\n if (merged.length > 0 && merged[merged.length - 1].role === m.role) {\n merged[merged.length - 1].content += '\\n\\n' + m.content;\n } else {\n merged.push({ ...m });\n }\n }\n\n const contents = merged.map(msg => ({\n role: msg.role,\n parts: [{ text: msg.content }]\n }));\n\n const payload = { contents };\n if (systemPrompt) {\n payload.systemInstruction = {\n parts: [{ text: systemPrompt }]\n };\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Gemini API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n let fullStreamText = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n const chunkStr = decoder.decode(value, { stream: true });\n fullStreamText += chunkStr;\n buffer += chunkStr;\n\n while (true) {\n const textMarker = '\"text\": \"';\n const idx = buffer.indexOf(textMarker);\n if (idx === -1) break;\n\n const startPos = idx + textMarker.length;\n let endPos = startPos;\n while (endPos < buffer.length) {\n if (buffer[endPos] === '\"') {\n let bsCount = 0;\n let p = endPos - 1;\n while (p >= startPos && buffer[p] === '\\\\') {\n bsCount++;\n p--;\n }\n if (bsCount % 2 === 0) {\n break;\n }\n }\n endPos++;\n }\n\n if (endPos >= buffer.length) break; // String not fully loaded\n\n const escapedText = buffer.substring(startPos, endPos);\n let text = escapedText;\n try {\n text = JSON.parse(`\"${escapedText}\"`);\n } catch (e) {\n text = escapedText\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n \n yield text;\n buffer = buffer.substring(endPos + 1);\n }\n }\n\n const usageMatch = fullStreamText.match(/\"usageMetadata\"\\s*:\\s*\\{([\\s\\S]*?)\\}/);\n if (usageMatch) {\n const promptMatch = usageMatch[1].match(/\"promptTokenCount\"\\s*:\\s*(\\d+)/);\n const candidatesMatch = usageMatch[1].match(/\"candidatesTokenCount\"\\s*:\\s*(\\d+)/);\n const totalMatch = usageMatch[1].match(/\"totalTokenCount\"\\s*:\\s*(\\d+)/);\n this.lastTokenUsage = {\n promptTokens: promptMatch ? parseInt(promptMatch[1], 10) : 0,\n completionTokens: candidatesMatch ? parseInt(candidatesMatch[1], 10) : 0,\n totalTokens: totalMatch ? parseInt(totalMatch[1], 10) : 0\n };\n }\n }\n}\n\nexport class OpenAIProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n try {\n const response = await fetch('https://api.openai.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .filter(id => id.includes('gpt') || id.includes('o1') || id.includes('o3') || id.startsWith('chatgpt'))\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('OpenAI API Key is not configured. Set OPENAI_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`OpenAI API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore parse errors on partial streams\n }\n }\n }\n }\n }\n}\n\nexport class AnthropicProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n try {\n const response = await fetch('https://api.anthropic.com/v1/models', {\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data.map(m => m.id);\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('Anthropic API Key is not configured. Set ANTHROPIC_API_KEY env var.');\n }\n\n const mapped = messages.map(msg => {\n let role = 'assistant';\n let content = msg.content;\n if (msg.role === 'user') {\n role = 'user';\n } else if (msg.role === 'system') {\n role = 'user';\n content = `[System Message]:\\n${msg.content}`;\n }\n return { role, content };\n });\n\n // Merge consecutive turns of the same role to conform to Anthropic API constraints\n const merged = [];\n for (const m of mapped) {\n if (merged.length > 0 && merged[merged.length - 1].role === m.role) {\n merged[merged.length - 1].content += '\\n\\n' + m.content;\n } else {\n merged.push({ ...m });\n }\n }\n\n const payload = {\n model,\n messages: merged,\n stream: true,\n max_tokens: 4096\n };\n if (systemPrompt) {\n payload.system = systemPrompt;\n }\n\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Anthropic API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n try {\n const data = JSON.parse(dataContent);\n if (data.type === 'content_block_delta') {\n const text = data.delta?.text || '';\n if (text) yield text;\n }\n if (data.type === 'message_start' && data.message?.usage) {\n this.lastTokenUsage = this.lastTokenUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.lastTokenUsage.promptTokens = data.message.usage.input_tokens || 0;\n this.lastTokenUsage.totalTokens = this.lastTokenUsage.promptTokens + this.lastTokenUsage.completionTokens;\n }\n if (data.type === 'message_delta' && data.usage) {\n this.lastTokenUsage = this.lastTokenUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.lastTokenUsage.completionTokens = data.usage.output_tokens || 0;\n this.lastTokenUsage.totalTokens = this.lastTokenUsage.promptTokens + this.lastTokenUsage.completionTokens;\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OllamaProvider extends BaseProvider {\n constructor() {\n super();\n this.baseUrl = 'http://localhost:11434';\n }\n\n async listModels() {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (response.ok) {\n const data = await response.json();\n return data.models?.map(m => m.name) || ['llama3', 'mistral', 'phi3'];\n }\n return ['llama3', 'mistral', 'phi3'];\n } catch (e) {\n return ['llama3', 'mistral', 'phi3'];\n }\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Ollama error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n const content = data.message?.content || '';\n if (content) yield content;\n if (data.prompt_eval_count || data.eval_count) {\n this.lastTokenUsage = {\n promptTokens: data.prompt_eval_count || 0,\n completionTokens: data.eval_count || 0,\n totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class NvidiaProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n try {\n const response = await fetch('https://integrate.api.nvidia.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('NVIDIA API Key is not configured. Set NVIDIA_API_KEY env var.');\n }\n\n const formattedMessages = [];\n const isKimi = model.toLowerCase().includes('kimi') || model.toLowerCase().includes('moonshot');\n\n if (systemPrompt) {\n if (isKimi) {\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n lastUserIdx = i;\n break;\n }\n }\n const newMessages = [];\n for (let i = 0; i < messages.length; i++) {\n if (i === lastUserIdx) {\n newMessages.push({\n role: 'user',\n content: `[System Instructions]\\n${systemPrompt}\\n\\n[User Query]\\n${messages[i].content}`\n });\n } else {\n newMessages.push({ ...messages[i] });\n }\n }\n if (lastUserIdx === -1) {\n newMessages.unshift({ role: 'user', content: `[System Instructions]\\n${systemPrompt}` });\n }\n formattedMessages.push(...newMessages);\n } else {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n formattedMessages.push(...messages);\n }\n } else {\n formattedMessages.push(...messages);\n }\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const response = await fetch('https://integrate.api.nvidia.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`NVIDIA API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OpenAICompatibleProvider extends BaseProvider {\n constructor(apiKey, baseUrl, defaultModels) {\n super();\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.defaultModels = defaultModels;\n }\n\n async listModels() {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n return this.defaultModels;\n }\n try {\n const headers = {};\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n const response = await fetch(`${this.baseUrl}/models`, { headers });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n for (const m of data.data) {\n if (m.id) {\n const limit = m.context_length || m.context_window || m.max_position_embeddings;\n if (limit) {\n modelContextLimits[m.id.toLowerCase()] = limit;\n }\n }\n }\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback below\n }\n return this.defaultModels;\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n throw new Error(`API Key is not configured for this provider. Configure it via /provider command.`);\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const headers = {\n 'Content-Type': 'application/json'\n };\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const response = await fetch(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n const cleaned = line.trim();\n if (cleaned.startsWith('data: ')) {\n const dataContent = cleaned.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport const PROVIDERS_CONFIG = {\n openrouter: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['google/gemini-2.5-flash', 'meta-llama/llama-3.3-70b-instruct', 'deepseek/deepseek-chat', 'anthropic/claude-3.5-sonnet']\n },\n groq: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama-3.3-70b-specdec', 'llama-3.3-70b-versatile', 'llama3-70b-8192', 'gemma2-9b-it']\n },\n deepseek: {\n baseUrl: 'https://api.deepseek.com',\n defaultModels: ['deepseek-chat', 'deepseek-coder']\n },\n together: {\n baseUrl: 'https://api.together.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'mistralai/Mixtral-8x22B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct']\n },\n mistral: {\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModels: ['mistral-tiny', 'mistral-small', 'mistral-medium', 'mistral-large-latest']\n },\n xai: {\n baseUrl: 'https://api.x.ai/v1',\n defaultModels: ['grok-2-1212', 'grok-2-vision-1212', 'grok-beta']\n },\n perplexity: {\n baseUrl: 'https://api.perplexity.ai',\n defaultModels: ['sonar-reasoning', 'sonar']\n },\n cerebras: {\n baseUrl: 'https://api.cerebras.ai/v1',\n defaultModels: ['llama3.1-8b', 'llama3.1-70b']\n },\n sambanova: {\n baseUrl: 'https://api.sambanova.ai/v1',\n defaultModels: ['Meta-Llama-3.1-8B-Instruct', 'Meta-Llama-3.1-70B-Instruct', 'Meta-Llama-3.1-405B-Instruct']\n },\n deepinfra: {\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1']\n },\n fireworks: {\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n defaultModels: ['accounts/fireworks/models/llama-v3-70b-instruct', 'accounts/fireworks/models/mixtral-8x22b-instruct']\n },\n novita: {\n baseUrl: 'https://api.novita.ai/v1/openai',\n defaultModels: ['meta-llama/llama-3-70b-instruct', 'mistralai/mystral-7b-instruct']\n },\n lepton: {\n baseUrl: 'https://api.lepton.ai/v1',\n defaultModels: ['llama3-70b', 'mixtral-8x22b']\n },\n hyperbolic: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n nebius: {\n baseUrl: 'https://api.studio.nebius.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3.1-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n friendli: {\n baseUrl: 'https://api.friendli.ai/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n runpod: {\n baseUrl: 'https://api.runpod.ai/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n lmstudio: {\n baseUrl: 'http://localhost:1234/v1',\n defaultModels: ['local-model']\n },\n localai: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n vllm: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n koboldcpp: {\n baseUrl: 'http://localhost:5001/v1',\n defaultModels: ['local-model']\n },\n opencodezen: {\n baseUrl: 'https://api.opencodezen.com/v1',\n defaultModels: ['opencodezen-chat', 'opencodezen-coder']\n },\n llamaapi: {\n baseUrl: 'https://api.llama-api.com',\n defaultModels: ['llama3-70b']\n },\n anyscale: {\n baseUrl: 'https://api.endpoints.anyscale.com/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n monsterapi: {\n baseUrl: 'https://api.monsterapi.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n openpipe: {\n baseUrl: 'https://api.openpipe.ai/v1',\n defaultModels: ['openpipe-model']\n },\n huggingface: {\n baseUrl: 'https://api-inference.huggingface.co/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n lambdalabs: {\n baseUrl: 'https://api.lambdalabs.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n octoai: {\n baseUrl: 'https://text.octoai.run/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n ai21: {\n baseUrl: 'https://api.ai21.com/studio/v1',\n defaultModels: ['jamba-instruct']\n },\n scale: {\n baseUrl: 'https://api.scale.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n gooseai: {\n baseUrl: 'https://api.goose.ai/v1',\n defaultModels: ['gpt-neo-20b']\n },\n alibaba: {\n baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n defaultModels: ['qwen-max', 'qwen-plus', 'qwen-turbo']\n },\n zhipu: {\n baseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n defaultModels: ['glm-4', 'glm-4-air', 'glm-4-flash']\n },\n moonshot: {\n baseUrl: 'https://api.moonshot.cn/v1',\n defaultModels: ['moonshot-v1-8k', 'moonshot-v1-32k']\n },\n minimax: {\n baseUrl: 'https://api.minimax.chat/v1',\n defaultModels: ['abab6.5-chat', 'abab6-chat']\n },\n yi: {\n baseUrl: 'https://api.01.ai/v1',\n defaultModels: ['yi-large', 'yi-medium']\n },\n baichuan: {\n baseUrl: 'https://api.baichuan-ai.com/v1',\n defaultModels: ['Baichuan3-Turbo', 'Baichuan2-Turbo']\n },\n doubao: {\n baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',\n defaultModels: ['doubao-pro-4k', 'doubao-lite-4k']\n },\n stepfun: {\n baseUrl: 'https://api.stepfun.com/v1',\n defaultModels: ['step-1-8k', 'step-1-32k']\n },\n siliconflow: {\n baseUrl: 'https://api.siliconflow.cn/v1',\n defaultModels: ['deepseek-ai/DeepSeek-V2.5', 'THUDM/glm-4-9b-chat']\n },\n textsynth: {\n baseUrl: 'https://textsynth.com/api/v1',\n defaultModels: ['llama3-8b']\n },\n api2d: {\n baseUrl: 'https://api.api2d.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n linkai: {\n baseUrl: 'https://api.link-ai.chat/v1',\n defaultModels: ['gpt-4o-mini']\n },\n oneapi: {\n baseUrl: 'https://api.oneapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n newapi: {\n baseUrl: 'https://api.newapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n opencode: {\n baseUrl: 'https://api.opencode.com/v1',\n defaultModels: ['opencode-chat']\n },\n openchat: {\n baseUrl: 'https://api.openchat.com/v1',\n defaultModels: ['openchat-model']\n },\n cloudl: {\n baseUrl: 'https://api.cloudl.com/v1',\n defaultModels: ['cloudl-chat']\n },\n deepgpt: {\n baseUrl: 'https://api.deepgpt.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n llamacloud: {\n baseUrl: 'https://api.llamacloud.com/v1',\n defaultModels: ['llama3-70b']\n },\n aimlapi: {\n baseUrl: 'https://api.aimlapi.com/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'gpt-4o-mini', 'claude-3-5-sonnet']\n },\n glider: {\n baseUrl: 'https://api.glider.ai/v1',\n defaultModels: ['glider-model']\n },\n openlayer: {\n baseUrl: 'https://api.openlayer.com/v1',\n defaultModels: ['openlayer-model']\n },\n databricks: {\n baseUrl: 'https://api.databricks.com/v1',\n defaultModels: ['databricks-meta-llama-3-1-70b']\n },\n workersai: {\n baseUrl: 'https://api.cloudflare.com/client/v4/accounts/default/ai/v1',\n defaultModels: ['llama-3']\n },\n portkey: {\n baseUrl: 'https://api.portkey.ai/v1',\n defaultModels: ['portkey-model']\n },\n llamacpp: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n textgenwebui: {\n baseUrl: 'http://localhost:5000/v1',\n defaultModels: ['local-model']\n },\n gpt4all: {\n baseUrl: 'http://localhost:4891/v1',\n defaultModels: ['local-model']\n },\n mlflow: {\n baseUrl: 'http://localhost:5000/api/2.0/mlflow/gateway',\n defaultModels: ['gateway-model']\n },\n langchainlocal: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n ollamaremote: {\n baseUrl: 'http://remote-ollama-host:11434/v1',\n defaultModels: ['llama3']\n },\n runpodserverless: {\n baseUrl: 'https://api.runpod.ai/v1/serverless',\n defaultModels: ['llama3-70b']\n },\n awsbedrockcompatible: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['meta.llama3-70b-instruct-v1:0']\n },\n azureopenaicompatible: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['gpt-4o']\n },\n custom: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['custom-model']\n },\n openrouterfree: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free', 'mistralai/mistral-7b-instruct:free']\n },\n openrouterbeta: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['openrouter/auto']\n },\n router: {\n baseUrl: 'https://api.router.ai/v1',\n defaultModels: ['router-model']\n },\n feather: {\n baseUrl: 'https://api.feather.ai/v1',\n defaultModels: ['feather-model']\n },\n sensenova: {\n baseUrl: 'https://api.sensetime.com/v1',\n defaultModels: ['sensenova-model']\n },\n hunyuan: {\n baseUrl: 'https://api.hunyuan.tencent.com/v1',\n defaultModels: ['hunyuan-model']\n },\n spark: {\n baseUrl: 'https://api.xfyun.cn/v1',\n defaultModels: ['spark-model']\n },\n baiduqianfan: {\n baseUrl: 'https://api.baidu.com/v1',\n defaultModels: ['ernie-bot']\n },\n copilot: {\n baseUrl: 'https://api.github.com',\n defaultModels: ['copilot-chat']\n },\n tabby: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['tabby-model']\n },\n continue: {\n baseUrl: 'http://localhost:5092/v1',\n defaultModels: ['local-model']\n },\n cursor: {\n baseUrl: 'https://api.cursor.sh/v1',\n defaultModels: ['cursor-model']\n },\n ghostcoder: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['ghostcoder-model']\n },\n codegpt: {\n baseUrl: 'https://api.codegpt.co/v1',\n defaultModels: ['codegpt-model']\n },\n codeium: {\n baseUrl: 'https://api.codeium.com/v1',\n defaultModels: ['codeium-model']\n },\n supermaven: {\n baseUrl: 'https://api.supermaven.com/v1',\n defaultModels: ['supermaven-model']\n },\n sourcegraphcody: {\n baseUrl: 'https://api.sourcegraph.com/v1',\n defaultModels: ['cody-model']\n },\n blackbox: {\n baseUrl: 'https://api.blackbox.ai/v1',\n defaultModels: ['blackbox-model']\n },\n phind: {\n baseUrl: 'https://api.phind.com/v1',\n defaultModels: ['phind-model']\n },\n you: {\n baseUrl: 'https://api.you.com/v1',\n defaultModels: ['you-model']\n },\n duckduckgo: {\n baseUrl: 'https://api.duckduckgo.com/v1',\n defaultModels: ['ddg-model']\n },\n brave: {\n baseUrl: 'https://api.brave.com/v1',\n defaultModels: ['brave-model']\n },\n kling: {\n baseUrl: 'https://api.kling.ai/v1',\n defaultModels: ['kling-model']\n },\n luma: {\n baseUrl: 'https://api.luma.ai/v1',\n defaultModels: ['luma-model']\n },\n runway: {\n baseUrl: 'https://api.runwayml.com/v1',\n defaultModels: ['runway-model']\n },\n sora: {\n baseUrl: 'https://api.sora.com/v1',\n defaultModels: ['sora-model']\n },\n midjourneycompatible: {\n baseUrl: 'https://api.midjourney.com/v1',\n defaultModels: ['mj-model']\n },\n stablediffusioncompatible: {\n baseUrl: 'https://api.stability.ai/v1',\n defaultModels: ['sd-model']\n },\n elevenlabscompatible: {\n baseUrl: 'https://api.elevenlabs.io/v1',\n defaultModels: ['elevenlabs-model']\n },\n voci: {\n baseUrl: 'https://api.voci.ai/v1',\n defaultModels: ['voci-model']\n },\n assemblyai: {\n baseUrl: 'https://api.assemblyai.com/v1',\n defaultModels: ['assemblyai-model']\n },\n deepgram: {\n baseUrl: 'https://api.deepgram.com/v1',\n defaultModels: ['deepgram-model']\n },\n whispercompatible: {\n baseUrl: 'https://api.whisper.com/v1',\n defaultModels: ['whisper-model']\n },\n glhf: {\n baseUrl: 'https://api.glhf.chat/v1',\n defaultModels: ['hf:meta-llama/Llama-3-8B-Instruct']\n },\n hyperbolicfree: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-8B-Instruct']\n },\n openrouterfreetier: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free']\n },\n cohere: {\n baseUrl: 'https://api.cohere.com/v1',\n defaultModels: ['command-r-plus', 'command-r']\n },\n writer: {\n baseUrl: 'https://api.writer.com/v1',\n defaultModels: ['palmyra-x']\n },\n groqfree: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama3-8b-8192']\n }\n};\n\nexport class ProviderManager {\n static getProvider(name, apiKey = '') {\n const providerName = name.toLowerCase();\n \n switch (providerName) {\n case 'gemini':\n return new GeminiProvider(apiKey);\n case 'openai':\n return new OpenAIProvider(apiKey);\n case 'anthropic':\n return new AnthropicProvider(apiKey);\n case 'nvidia':\n return new NvidiaProvider(apiKey);\n case 'ollama':\n return new OllamaProvider();\n }\n\n const customConfig = PROVIDERS_CONFIG[providerName];\n if (customConfig) {\n return new OpenAICompatibleProvider(apiKey, customConfig.baseUrl, customConfig.defaultModels);\n }\n\n throw new Error(`Unknown provider: ${name}. Supported: gemini, openai, anthropic, nvidia, ollama, and 100+ custom OpenAI-compatible providers (like openrouter, groq, etc.).`);\n }\n}\n\n"}, "7a3acc7c4360ae65b762439dd4362ec08d59aec1": {"t": 1785758874.331178, "n": 33710, "h": "eb9a85568c981ce0", "c": "export class BaseProvider {\n listModels() {\n throw new Error('Not implemented');\n }\n async *generateStream(systemPrompt, messages, model, signal) {\n throw new Error('Not implemented');\n }\n}\n\nexport class GeminiProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n if (data.models) {\n return data.models\n .filter(m => m.supportedGenerationMethods?.includes('generateContent'))\n .map(m => m.name.replace('models/', ''));\n }\n }\n } catch (e) {\n // Fallback below\n }\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('Google Gemini API Key is not configured. Set GEMINI_API_KEY env var.');\n }\n\n const modelName = model.replace('models/', '');\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:streamGenerateContent?key=${this.apiKey}`;\n \n const mapped = messages.map(msg => {\n let role = 'model';\n let content = msg.content;\n if (msg.role === 'user') {\n role = 'user';\n } else if (msg.role === 'system') {\n role = 'user';\n content = `[System Message]:\\n${msg.content}`;\n }\n return { role, content };\n });\n\n // Merge consecutive turns of the same role to conform to Gemini API constraints\n const merged = [];\n for (const m of mapped) {\n if (merged.length > 0 && merged[merged.length - 1].role === m.role) {\n merged[merged.length - 1].content += '\\n\\n' + m.content;\n } else {\n merged.push({ ...m });\n }\n }\n\n const contents = merged.map(msg => ({\n role: msg.role,\n parts: [{ text: msg.content }]\n }));\n\n const payload = { contents };\n if (systemPrompt) {\n payload.systemInstruction = {\n parts: [{ text: systemPrompt }]\n };\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Gemini API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n let fullStreamText = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n const chunkStr = decoder.decode(value, { stream: true });\n fullStreamText += chunkStr;\n buffer += chunkStr;\n\n while (true) {\n const textMarker = '\"text\": \"';\n const idx = buffer.indexOf(textMarker);\n if (idx === -1) break;\n\n const startPos = idx + textMarker.length;\n let endPos = startPos;\n while (endPos < buffer.length) {\n if (buffer[endPos] === '\"') {\n let bsCount = 0;\n let p = endPos - 1;\n while (p >= startPos && buffer[p] === '\\\\') {\n bsCount++;\n p--;\n }\n if (bsCount % 2 === 0) {\n break;\n }\n }\n endPos++;\n }\n\n if (endPos >= buffer.length) break; // String not fully loaded\n\n const escapedText = buffer.substring(startPos, endPos);\n let text = escapedText;\n try {\n text = JSON.parse(`\"${escapedText}\"`);\n } catch (e) {\n text = escapedText\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n \n yield text;\n buffer = buffer.substring(endPos + 1);\n }\n }\n\n const usageMatch = fullStreamText.match(/\"usageMetadata\"\\s*:\\s*\\{([\\s\\S]*?)\\}/);\n if (usageMatch) {\n const promptMatch = usageMatch[1].match(/\"promptTokenCount\"\\s*:\\s*(\\d+)/);\n const candidatesMatch = usageMatch[1].match(/\"candidatesTokenCount\"\\s*:\\s*(\\d+)/);\n const totalMatch = usageMatch[1].match(/\"totalTokenCount\"\\s*:\\s*(\\d+)/);\n this.lastTokenUsage = {\n promptTokens: promptMatch ? parseInt(promptMatch[1], 10) : 0,\n completionTokens: candidatesMatch ? parseInt(candidatesMatch[1], 10) : 0,\n totalTokens: totalMatch ? parseInt(totalMatch[1], 10) : 0\n };\n }\n }\n}\n\nexport class OpenAIProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n try {\n const response = await fetch('https://api.openai.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .filter(id => id.includes('gpt') || id.includes('o1') || id.includes('o3') || id.startsWith('chatgpt'))\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('OpenAI API Key is not configured. Set OPENAI_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`OpenAI API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore parse errors on partial streams\n }\n }\n }\n }\n }\n}\n\nexport class AnthropicProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n try {\n const response = await fetch('https://api.anthropic.com/v1/models', {\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data.map(m => m.id);\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('Anthropic API Key is not configured. Set ANTHROPIC_API_KEY env var.');\n }\n\n const mapped = messages.map(msg => {\n let role = 'assistant';\n let content = msg.content;\n if (msg.role === 'user') {\n role = 'user';\n } else if (msg.role === 'system') {\n role = 'user';\n content = `[System Message]:\\n${msg.content}`;\n }\n return { role, content };\n });\n\n // Merge consecutive turns of the same role to conform to Anthropic API constraints\n const merged = [];\n for (const m of mapped) {\n if (merged.length > 0 && merged[merged.length - 1].role === m.role) {\n merged[merged.length - 1].content += '\\n\\n' + m.content;\n } else {\n merged.push({ ...m });\n }\n }\n\n const payload = {\n model,\n messages: merged,\n stream: true,\n max_tokens: 4096\n };\n if (systemPrompt) {\n payload.system = systemPrompt;\n }\n\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Anthropic API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n try {\n const data = JSON.parse(dataContent);\n if (data.type === 'content_block_delta') {\n const text = data.delta?.text || '';\n if (text) yield text;\n }\n if (data.type === 'message_start' && data.message?.usage) {\n this.lastTokenUsage = this.lastTokenUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.lastTokenUsage.promptTokens = data.message.usage.input_tokens || 0;\n this.lastTokenUsage.totalTokens = this.lastTokenUsage.promptTokens + this.lastTokenUsage.completionTokens;\n }\n if (data.type === 'message_delta' && data.usage) {\n this.lastTokenUsage = this.lastTokenUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.lastTokenUsage.completionTokens = data.usage.output_tokens || 0;\n this.lastTokenUsage.totalTokens = this.lastTokenUsage.promptTokens + this.lastTokenUsage.completionTokens;\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OllamaProvider extends BaseProvider {\n constructor() {\n super();\n this.baseUrl = 'http://localhost:11434';\n }\n\n async listModels() {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (response.ok) {\n const data = await response.json();\n return data.models?.map(m => m.name) || ['llama3', 'mistral', 'phi3'];\n }\n return ['llama3', 'mistral', 'phi3'];\n } catch (e) {\n return ['llama3', 'mistral', 'phi3'];\n }\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Ollama error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n const content = data.message?.content || '';\n if (content) yield content;\n if (data.prompt_eval_count || data.eval_count) {\n this.lastTokenUsage = {\n promptTokens: data.prompt_eval_count || 0,\n completionTokens: data.eval_count || 0,\n totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class NvidiaProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n try {\n const response = await fetch('https://integrate.api.nvidia.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('NVIDIA API Key is not configured. Set NVIDIA_API_KEY env var.');\n }\n\n const formattedMessages = [];\n const isKimi = model.toLowerCase().includes('kimi') || model.toLowerCase().includes('moonshot');\n\n if (systemPrompt) {\n if (isKimi) {\n let lastUserIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n lastUserIdx = i;\n break;\n }\n }\n const newMessages = [];\n for (let i = 0; i < messages.length; i++) {\n if (i === lastUserIdx) {\n newMessages.push({\n role: 'user',\n content: `[System Instructions]\\n${systemPrompt}\\n\\n[User Query]\\n${messages[i].content}`\n });\n } else {\n newMessages.push({ ...messages[i] });\n }\n }\n if (lastUserIdx === -1) {\n newMessages.unshift({ role: 'user', content: `[System Instructions]\\n${systemPrompt}` });\n }\n formattedMessages.push(...newMessages);\n } else {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n formattedMessages.push(...messages);\n }\n } else {\n formattedMessages.push(...messages);\n }\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const response = await fetch('https://integrate.api.nvidia.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`NVIDIA API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OpenAICompatibleProvider extends BaseProvider {\n constructor(apiKey, baseUrl, defaultModels) {\n super();\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.defaultModels = defaultModels;\n }\n\n async listModels() {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n return this.defaultModels;\n }\n try {\n const headers = {};\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n const response = await fetch(`${this.baseUrl}/models`, { headers });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback below\n }\n return this.defaultModels;\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n throw new Error(`API Key is not configured for this provider. Configure it via /provider command.`);\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const headers = {\n 'Content-Type': 'application/json'\n };\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const response = await fetch(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n const cleaned = line.trim();\n if (cleaned.startsWith('data: ')) {\n const dataContent = cleaned.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport const PROVIDERS_CONFIG = {\n openrouter: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['google/gemini-2.5-flash', 'meta-llama/llama-3.3-70b-instruct', 'deepseek/deepseek-chat', 'anthropic/claude-3.5-sonnet']\n },\n groq: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama-3.3-70b-specdec', 'llama-3.3-70b-versatile', 'llama3-70b-8192', 'gemma2-9b-it']\n },\n deepseek: {\n baseUrl: 'https://api.deepseek.com',\n defaultModels: ['deepseek-chat', 'deepseek-coder']\n },\n together: {\n baseUrl: 'https://api.together.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'mistralai/Mixtral-8x22B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct']\n },\n mistral: {\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModels: ['mistral-tiny', 'mistral-small', 'mistral-medium', 'mistral-large-latest']\n },\n xai: {\n baseUrl: 'https://api.x.ai/v1',\n defaultModels: ['grok-2-1212', 'grok-2-vision-1212', 'grok-beta']\n },\n perplexity: {\n baseUrl: 'https://api.perplexity.ai',\n defaultModels: ['sonar-reasoning', 'sonar']\n },\n cerebras: {\n baseUrl: 'https://api.cerebras.ai/v1',\n defaultModels: ['llama3.1-8b', 'llama3.1-70b']\n },\n sambanova: {\n baseUrl: 'https://api.sambanova.ai/v1',\n defaultModels: ['Meta-Llama-3.1-8B-Instruct', 'Meta-Llama-3.1-70B-Instruct', 'Meta-Llama-3.1-405B-Instruct']\n },\n deepinfra: {\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1']\n },\n fireworks: {\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n defaultModels: ['accounts/fireworks/models/llama-v3-70b-instruct', 'accounts/fireworks/models/mixtral-8x22b-instruct']\n },\n novita: {\n baseUrl: 'https://api.novita.ai/v1/openai',\n defaultModels: ['meta-llama/llama-3-70b-instruct', 'mistralai/mystral-7b-instruct']\n },\n lepton: {\n baseUrl: 'https://api.lepton.ai/v1',\n defaultModels: ['llama3-70b', 'mixtral-8x22b']\n },\n hyperbolic: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n nebius: {\n baseUrl: 'https://api.studio.nebius.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3.1-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n friendli: {\n baseUrl: 'https://api.friendli.ai/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n runpod: {\n baseUrl: 'https://api.runpod.ai/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n lmstudio: {\n baseUrl: 'http://localhost:1234/v1',\n defaultModels: ['local-model']\n },\n localai: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n vllm: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n koboldcpp: {\n baseUrl: 'http://localhost:5001/v1',\n defaultModels: ['local-model']\n },\n opencodezen: {\n baseUrl: 'https://api.opencodezen.com/v1',\n defaultModels: ['opencodezen-chat', 'opencodezen-coder']\n },\n llamaapi: {\n baseUrl: 'https://api.llama-api.com',\n defaultModels: ['llama3-70b']\n },\n anyscale: {\n baseUrl: 'https://api.endpoints.anyscale.com/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n monsterapi: {\n baseUrl: 'https://api.monsterapi.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n openpipe: {\n baseUrl: 'https://api.openpipe.ai/v1',\n defaultModels: ['openpipe-model']\n },\n huggingface: {\n baseUrl: 'https://api-inference.huggingface.co/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n lambdalabs: {\n baseUrl: 'https://api.lambdalabs.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n octoai: {\n baseUrl: 'https://text.octoai.run/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n ai21: {\n baseUrl: 'https://api.ai21.com/studio/v1',\n defaultModels: ['jamba-instruct']\n },\n scale: {\n baseUrl: 'https://api.scale.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n gooseai: {\n baseUrl: 'https://api.goose.ai/v1',\n defaultModels: ['gpt-neo-20b']\n },\n alibaba: {\n baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n defaultModels: ['qwen-max', 'qwen-plus', 'qwen-turbo']\n },\n zhipu: {\n baseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n defaultModels: ['glm-4', 'glm-4-air', 'glm-4-flash']\n },\n moonshot: {\n baseUrl: 'https://api.moonshot.cn/v1',\n defaultModels: ['moonshot-v1-8k', 'moonshot-v1-32k']\n },\n minimax: {\n baseUrl: 'https://api.minimax.chat/v1',\n defaultModels: ['abab6.5-chat', 'abab6-chat']\n },\n yi: {\n baseUrl: 'https://api.01.ai/v1',\n defaultModels: ['yi-large', 'yi-medium']\n },\n baichuan: {\n baseUrl: 'https://api.baichuan-ai.com/v1',\n defaultModels: ['Baichuan3-Turbo', 'Baichuan2-Turbo']\n },\n doubao: {\n baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',\n defaultModels: ['doubao-pro-4k', 'doubao-lite-4k']\n },\n stepfun: {\n baseUrl: 'https://api.stepfun.com/v1',\n defaultModels: ['step-1-8k', 'step-1-32k']\n },\n siliconflow: {\n baseUrl: 'https://api.siliconflow.cn/v1',\n defaultModels: ['deepseek-ai/DeepSeek-V2.5', 'THUDM/glm-4-9b-chat']\n },\n textsynth: {\n baseUrl: 'https://textsynth.com/api/v1',\n defaultModels: ['llama3-8b']\n },\n api2d: {\n baseUrl: 'https://api.api2d.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n linkai: {\n baseUrl: 'https://api.link-ai.chat/v1',\n defaultModels: ['gpt-4o-mini']\n },\n oneapi: {\n baseUrl: 'https://api.oneapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n newapi: {\n baseUrl: 'https://api.newapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n opencode: {\n baseUrl: 'https://api.opencode.com/v1',\n defaultModels: ['opencode-chat']\n },\n openchat: {\n baseUrl: 'https://api.openchat.com/v1',\n defaultModels: ['openchat-model']\n },\n cloudl: {\n baseUrl: 'https://api.cloudl.com/v1',\n defaultModels: ['cloudl-chat']\n },\n deepgpt: {\n baseUrl: 'https://api.deepgpt.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n llamacloud: {\n baseUrl: 'https://api.llamacloud.com/v1',\n defaultModels: ['llama3-70b']\n },\n aimlapi: {\n baseUrl: 'https://api.aimlapi.com/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'gpt-4o-mini', 'claude-3-5-sonnet']\n },\n glider: {\n baseUrl: 'https://api.glider.ai/v1',\n defaultModels: ['glider-model']\n },\n openlayer: {\n baseUrl: 'https://api.openlayer.com/v1',\n defaultModels: ['openlayer-model']\n },\n databricks: {\n baseUrl: 'https://api.databricks.com/v1',\n defaultModels: ['databricks-meta-llama-3-1-70b']\n },\n workersai: {\n baseUrl: 'https://api.cloudflare.com/client/v4/accounts/default/ai/v1',\n defaultModels: ['llama-3']\n },\n portkey: {\n baseUrl: 'https://api.portkey.ai/v1',\n defaultModels: ['portkey-model']\n },\n llamacpp: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n textgenwebui: {\n baseUrl: 'http://localhost:5000/v1',\n defaultModels: ['local-model']\n },\n gpt4all: {\n baseUrl: 'http://localhost:4891/v1',\n defaultModels: ['local-model']\n },\n mlflow: {\n baseUrl: 'http://localhost:5000/api/2.0/mlflow/gateway',\n defaultModels: ['gateway-model']\n },\n langchainlocal: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n ollamaremote: {\n baseUrl: 'http://remote-ollama-host:11434/v1',\n defaultModels: ['llama3']\n },\n runpodserverless: {\n baseUrl: 'https://api.runpod.ai/v1/serverless',\n defaultModels: ['llama3-70b']\n },\n awsbedrockcompatible: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['meta.llama3-70b-instruct-v1:0']\n },\n azureopenaicompatible: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['gpt-4o']\n },\n custom: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['custom-model']\n },\n openrouterfree: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free', 'mistralai/mistral-7b-instruct:free']\n },\n openrouterbeta: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['openrouter/auto']\n },\n router: {\n baseUrl: 'https://api.router.ai/v1',\n defaultModels: ['router-model']\n },\n feather: {\n baseUrl: 'https://api.feather.ai/v1',\n defaultModels: ['feather-model']\n },\n sensenova: {\n baseUrl: 'https://api.sensetime.com/v1',\n defaultModels: ['sensenova-model']\n },\n hunyuan: {\n baseUrl: 'https://api.hunyuan.tencent.com/v1',\n defaultModels: ['hunyuan-model']\n },\n spark: {\n baseUrl: 'https://api.xfyun.cn/v1',\n defaultModels: ['spark-model']\n },\n baiduqianfan: {\n baseUrl: 'https://api.baidu.com/v1',\n defaultModels: ['ernie-bot']\n },\n copilot: {\n baseUrl: 'https://api.github.com',\n defaultModels: ['copilot-chat']\n },\n tabby: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['tabby-model']\n },\n continue: {\n baseUrl: 'http://localhost:5092/v1',\n defaultModels: ['local-model']\n },\n cursor: {\n baseUrl: 'https://api.cursor.sh/v1',\n defaultModels: ['cursor-model']\n },\n ghostcoder: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['ghostcoder-model']\n },\n codegpt: {\n baseUrl: 'https://api.codegpt.co/v1',\n defaultModels: ['codegpt-model']\n },\n codeium: {\n baseUrl: 'https://api.codeium.com/v1',\n defaultModels: ['codeium-model']\n },\n supermaven: {\n baseUrl: 'https://api.supermaven.com/v1',\n defaultModels: ['supermaven-model']\n },\n sourcegraphcody: {\n baseUrl: 'https://api.sourcegraph.com/v1',\n defaultModels: ['cody-model']\n },\n blackbox: {\n baseUrl: 'https://api.blackbox.ai/v1',\n defaultModels: ['blackbox-model']\n },\n phind: {\n baseUrl: 'https://api.phind.com/v1',\n defaultModels: ['phind-model']\n },\n you: {\n baseUrl: 'https://api.you.com/v1',\n defaultModels: ['you-model']\n },\n duckduckgo: {\n baseUrl: 'https://api.duckduckgo.com/v1',\n defaultModels: ['ddg-model']\n },\n brave: {\n baseUrl: 'https://api.brave.com/v1',\n defaultModels: ['brave-model']\n },\n kling: {\n baseUrl: 'https://api.kling.ai/v1',\n defaultModels: ['kling-model']\n },\n luma: {\n baseUrl: 'https://api.luma.ai/v1',\n defaultModels: ['luma-model']\n },\n runway: {\n baseUrl: 'https://api.runwayml.com/v1',\n defaultModels: ['runway-model']\n },\n sora: {\n baseUrl: 'https://api.sora.com/v1',\n defaultModels: ['sora-model']\n },\n midjourneycompatible: {\n baseUrl: 'https://api.midjourney.com/v1',\n defaultModels: ['mj-model']\n },\n stablediffusioncompatible: {\n baseUrl: 'https://api.stability.ai/v1',\n defaultModels: ['sd-model']\n },\n elevenlabscompatible: {\n baseUrl: 'https://api.elevenlabs.io/v1',\n defaultModels: ['elevenlabs-model']\n },\n voci: {\n baseUrl: 'https://api.voci.ai/v1',\n defaultModels: ['voci-model']\n },\n assemblyai: {\n baseUrl: 'https://api.assemblyai.com/v1',\n defaultModels: ['assemblyai-model']\n },\n deepgram: {\n baseUrl: 'https://api.deepgram.com/v1',\n defaultModels: ['deepgram-model']\n },\n whispercompatible: {\n baseUrl: 'https://api.whisper.com/v1',\n defaultModels: ['whisper-model']\n },\n glhf: {\n baseUrl: 'https://api.glhf.chat/v1',\n defaultModels: ['hf:meta-llama/Llama-3-8B-Instruct']\n },\n hyperbolicfree: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-8B-Instruct']\n },\n openrouterfreetier: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free']\n },\n cohere: {\n baseUrl: 'https://api.cohere.com/v1',\n defaultModels: ['command-r-plus', 'command-r']\n },\n writer: {\n baseUrl: 'https://api.writer.com/v1',\n defaultModels: ['palmyra-x']\n },\n groqfree: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama3-8b-8192']\n }\n};\n\nexport class ProviderManager {\n static getProvider(name, apiKey = '') {\n const providerName = name.toLowerCase();\n \n switch (providerName) {\n case 'gemini':\n return new GeminiProvider(apiKey);\n case 'openai':\n return new OpenAIProvider(apiKey);\n case 'anthropic':\n return new AnthropicProvider(apiKey);\n case 'nvidia':\n return new NvidiaProvider(apiKey);\n case 'ollama':\n return new OllamaProvider();\n }\n\n const customConfig = PROVIDERS_CONFIG[providerName];\n if (customConfig) {\n return new OpenAICompatibleProvider(apiKey, customConfig.baseUrl, customConfig.defaultModels);\n }\n\n throw new Error(`Unknown provider: ${name}. Supported: gemini, openai, anthropic, nvidia, ollama, and 100+ custom OpenAI-compatible providers (like openrouter, groq, etc.).`);\n }\n}\n\n"}, "6626b0b1cd4edb88540542a6617a172ad67bca88": {"t": 1785758875.809266, "n": 31208, "h": "c22a68608eae1c29", "c": "export class BaseProvider {\n listModels() {\n throw new Error('Not implemented');\n }\n async *generateStream(systemPrompt, messages, model, signal) {\n throw new Error('Not implemented');\n }\n}\n\nexport class GeminiProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n if (data.models) {\n return data.models\n .filter(m => m.supportedGenerationMethods?.includes('generateContent'))\n .map(m => m.name.replace('models/', ''));\n }\n }\n } catch (e) {\n // Fallback below\n }\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('Google Gemini API Key is not configured. Set GEMINI_API_KEY env var.');\n }\n\n const modelName = model.replace('models/', '');\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:streamGenerateContent?key=${this.apiKey}`;\n \n const contents = messages.map(msg => ({\n role: msg.role === 'user' ? 'user' : 'model',\n parts: [{ text: msg.content }]\n }));\n\n const payload = { contents };\n if (systemPrompt) {\n payload.systemInstruction = {\n parts: [{ text: systemPrompt }]\n };\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Gemini API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n let fullStreamText = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n const chunkStr = decoder.decode(value, { stream: true });\n fullStreamText += chunkStr;\n buffer += chunkStr;\n\n while (true) {\n const textMarker = '\"text\": \"';\n const idx = buffer.indexOf(textMarker);\n if (idx === -1) break;\n\n const startPos = idx + textMarker.length;\n let endPos = startPos;\n while (endPos < buffer.length) {\n if (buffer[endPos] === '\"' && buffer[endPos - 1] !== '\\\\') {\n break;\n }\n endPos++;\n }\n\n if (endPos >= buffer.length) break; // String not fully loaded\n\n const escapedText = buffer.substring(startPos, endPos);\n let text = escapedText;\n try {\n text = JSON.parse(`\"${escapedText}\"`);\n } catch (e) {\n text = escapedText\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n \n yield text;\n buffer = buffer.substring(endPos + 1);\n }\n }\n\n const usageMatch = fullStreamText.match(/\"usageMetadata\"\\s*:\\s*\\{([\\s\\S]*?)\\}/);\n if (usageMatch) {\n const promptMatch = usageMatch[1].match(/\"promptTokenCount\"\\s*:\\s*(\\d+)/);\n const candidatesMatch = usageMatch[1].match(/\"candidatesTokenCount\"\\s*:\\s*(\\d+)/);\n const totalMatch = usageMatch[1].match(/\"totalTokenCount\"\\s*:\\s*(\\d+)/);\n this.lastTokenUsage = {\n promptTokens: promptMatch ? parseInt(promptMatch[1], 10) : 0,\n completionTokens: candidatesMatch ? parseInt(candidatesMatch[1], 10) : 0,\n totalTokens: totalMatch ? parseInt(totalMatch[1], 10) : 0\n };\n }\n }\n}\n\nexport class OpenAIProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n try {\n const response = await fetch('https://api.openai.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .filter(id => id.includes('gpt') || id.includes('o1') || id.includes('o3') || id.startsWith('chatgpt'))\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('OpenAI API Key is not configured. Set OPENAI_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`OpenAI API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore parse errors on partial streams\n }\n }\n }\n }\n }\n}\n\nexport class AnthropicProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n try {\n const response = await fetch('https://api.anthropic.com/v1/models', {\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data.map(m => m.id);\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('Anthropic API Key is not configured. Set ANTHROPIC_API_KEY env var.');\n }\n\n const payload = {\n model,\n messages,\n stream: true,\n max_tokens: 4096\n };\n if (systemPrompt) {\n payload.system = systemPrompt;\n }\n\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Anthropic API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n try {\n const data = JSON.parse(dataContent);\n if (data.type === 'content_block_delta') {\n const text = data.delta?.text || '';\n if (text) yield text;\n }\n if (data.type === 'message_start' && data.message?.usage) {\n this.lastTokenUsage = this.lastTokenUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.lastTokenUsage.promptTokens = data.message.usage.input_tokens || 0;\n this.lastTokenUsage.totalTokens = this.lastTokenUsage.promptTokens + this.lastTokenUsage.completionTokens;\n }\n if (data.type === 'message_delta' && data.usage) {\n this.lastTokenUsage = this.lastTokenUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.lastTokenUsage.completionTokens = data.usage.output_tokens || 0;\n this.lastTokenUsage.totalTokens = this.lastTokenUsage.promptTokens + this.lastTokenUsage.completionTokens;\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OllamaProvider extends BaseProvider {\n constructor() {\n super();\n this.baseUrl = 'http://localhost:11434';\n }\n\n async listModels() {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (response.ok) {\n const data = await response.json();\n return data.models?.map(m => m.name) || ['llama3', 'mistral', 'phi3'];\n }\n return ['llama3', 'mistral', 'phi3'];\n } catch (e) {\n return ['llama3', 'mistral', 'phi3'];\n }\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Ollama error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n const content = data.message?.content || '';\n if (content) yield content;\n if (data.prompt_eval_count || data.eval_count) {\n this.lastTokenUsage = {\n promptTokens: data.prompt_eval_count || 0,\n completionTokens: data.eval_count || 0,\n totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class NvidiaProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n try {\n const response = await fetch('https://integrate.api.nvidia.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n if (!this.apiKey) {\n throw new Error('NVIDIA API Key is not configured. Set NVIDIA_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const response = await fetch('https://integrate.api.nvidia.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`NVIDIA API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OpenAICompatibleProvider extends BaseProvider {\n constructor(apiKey, baseUrl, defaultModels) {\n super();\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.defaultModels = defaultModels;\n }\n\n async listModels() {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n return this.defaultModels;\n }\n try {\n const headers = {};\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n const response = await fetch(`${this.baseUrl}/models`, { headers });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback below\n }\n return this.defaultModels;\n }\n\n async *generateStream(systemPrompt, messages, model, signal) {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n throw new Error(`API Key is not configured for this provider. Configure it via /provider command.`);\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true,\n stream_options: { include_usage: true }\n };\n\n const headers = {\n 'Content-Type': 'application/json'\n };\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const response = await fetch(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n const cleaned = line.trim();\n if (cleaned.startsWith('data: ')) {\n const dataContent = cleaned.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n if (data.usage) {\n this.lastTokenUsage = {\n promptTokens: data.usage.prompt_tokens || 0,\n completionTokens: data.usage.completion_tokens || 0,\n totalTokens: data.usage.total_tokens || 0\n };\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport const PROVIDERS_CONFIG = {\n openrouter: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['google/gemini-2.5-flash', 'meta-llama/llama-3.3-70b-instruct', 'deepseek/deepseek-chat', 'anthropic/claude-3.5-sonnet']\n },\n groq: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama-3.3-70b-specdec', 'llama-3.3-70b-versatile', 'llama3-70b-8192', 'gemma2-9b-it']\n },\n deepseek: {\n baseUrl: 'https://api.deepseek.com',\n defaultModels: ['deepseek-chat', 'deepseek-coder']\n },\n together: {\n baseUrl: 'https://api.together.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'mistralai/Mixtral-8x22B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct']\n },\n mistral: {\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModels: ['mistral-tiny', 'mistral-small', 'mistral-medium', 'mistral-large-latest']\n },\n xai: {\n baseUrl: 'https://api.x.ai/v1',\n defaultModels: ['grok-2-1212', 'grok-2-vision-1212', 'grok-beta']\n },\n perplexity: {\n baseUrl: 'https://api.perplexity.ai',\n defaultModels: ['sonar-reasoning', 'sonar']\n },\n cerebras: {\n baseUrl: 'https://api.cerebras.ai/v1',\n defaultModels: ['llama3.1-8b', 'llama3.1-70b']\n },\n sambanova: {\n baseUrl: 'https://api.sambanova.ai/v1',\n defaultModels: ['Meta-Llama-3.1-8B-Instruct', 'Meta-Llama-3.1-70B-Instruct', 'Meta-Llama-3.1-405B-Instruct']\n },\n deepinfra: {\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1']\n },\n fireworks: {\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n defaultModels: ['accounts/fireworks/models/llama-v3-70b-instruct', 'accounts/fireworks/models/mixtral-8x22b-instruct']\n },\n novita: {\n baseUrl: 'https://api.novita.ai/v1/openai',\n defaultModels: ['meta-llama/llama-3-70b-instruct', 'mistralai/mystral-7b-instruct']\n },\n lepton: {\n baseUrl: 'https://api.lepton.ai/v1',\n defaultModels: ['llama3-70b', 'mixtral-8x22b']\n },\n hyperbolic: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n nebius: {\n baseUrl: 'https://api.studio.nebius.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3.1-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n friendli: {\n baseUrl: 'https://api.friendli.ai/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n runpod: {\n baseUrl: 'https://api.runpod.ai/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n lmstudio: {\n baseUrl: 'http://localhost:1234/v1',\n defaultModels: ['local-model']\n },\n localai: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n vllm: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n koboldcpp: {\n baseUrl: 'http://localhost:5001/v1',\n defaultModels: ['local-model']\n },\n opencodezen: {\n baseUrl: 'https://api.opencodezen.com/v1',\n defaultModels: ['opencodezen-chat', 'opencodezen-coder']\n },\n llamaapi: {\n baseUrl: 'https://api.llama-api.com',\n defaultModels: ['llama3-70b']\n },\n anyscale: {\n baseUrl: 'https://api.endpoints.anyscale.com/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n monsterapi: {\n baseUrl: 'https://api.monsterapi.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n openpipe: {\n baseUrl: 'https://api.openpipe.ai/v1',\n defaultModels: ['openpipe-model']\n },\n huggingface: {\n baseUrl: 'https://api-inference.huggingface.co/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n lambdalabs: {\n baseUrl: 'https://api.lambdalabs.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n octoai: {\n baseUrl: 'https://text.octoai.run/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n ai21: {\n baseUrl: 'https://api.ai21.com/studio/v1',\n defaultModels: ['jamba-instruct']\n },\n scale: {\n baseUrl: 'https://api.scale.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n gooseai: {\n baseUrl: 'https://api.goose.ai/v1',\n defaultModels: ['gpt-neo-20b']\n },\n alibaba: {\n baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n defaultModels: ['qwen-max', 'qwen-plus', 'qwen-turbo']\n },\n zhipu: {\n baseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n defaultModels: ['glm-4', 'glm-4-air', 'glm-4-flash']\n },\n moonshot: {\n baseUrl: 'https://api.moonshot.cn/v1',\n defaultModels: ['moonshot-v1-8k', 'moonshot-v1-32k']\n },\n minimax: {\n baseUrl: 'https://api.minimax.chat/v1',\n defaultModels: ['abab6.5-chat', 'abab6-chat']\n },\n yi: {\n baseUrl: 'https://api.01.ai/v1',\n defaultModels: ['yi-large', 'yi-medium']\n },\n baichuan: {\n baseUrl: 'https://api.baichuan-ai.com/v1',\n defaultModels: ['Baichuan3-Turbo', 'Baichuan2-Turbo']\n },\n doubao: {\n baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',\n defaultModels: ['doubao-pro-4k', 'doubao-lite-4k']\n },\n stepfun: {\n baseUrl: 'https://api.stepfun.com/v1',\n defaultModels: ['step-1-8k', 'step-1-32k']\n },\n siliconflow: {\n baseUrl: 'https://api.siliconflow.cn/v1',\n defaultModels: ['deepseek-ai/DeepSeek-V2.5', 'THUDM/glm-4-9b-chat']\n },\n textsynth: {\n baseUrl: 'https://textsynth.com/api/v1',\n defaultModels: ['llama3-8b']\n },\n api2d: {\n baseUrl: 'https://api.api2d.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n linkai: {\n baseUrl: 'https://api.link-ai.chat/v1',\n defaultModels: ['gpt-4o-mini']\n },\n oneapi: {\n baseUrl: 'https://api.oneapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n newapi: {\n baseUrl: 'https://api.newapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n opencode: {\n baseUrl: 'https://api.opencode.com/v1',\n defaultModels: ['opencode-chat']\n },\n openchat: {\n baseUrl: 'https://api.openchat.com/v1',\n defaultModels: ['openchat-model']\n },\n cloudl: {\n baseUrl: 'https://api.cloudl.com/v1',\n defaultModels: ['cloudl-chat']\n },\n deepgpt: {\n baseUrl: 'https://api.deepgpt.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n llamacloud: {\n baseUrl: 'https://api.llamacloud.com/v1',\n defaultModels: ['llama3-70b']\n },\n aimlapi: {\n baseUrl: 'https://api.aimlapi.com/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'gpt-4o-mini', 'claude-3-5-sonnet']\n },\n glider: {\n baseUrl: 'https://api.glider.ai/v1',\n defaultModels: ['glider-model']\n },\n openlayer: {\n baseUrl: 'https://api.openlayer.com/v1',\n defaultModels: ['openlayer-model']\n },\n databricks: {\n baseUrl: 'https://api.databricks.com/v1',\n defaultModels: ['databricks-meta-llama-3-1-70b']\n },\n workersai: {\n baseUrl: 'https://api.cloudflare.com/client/v4/accounts/default/ai/v1',\n defaultModels: ['llama-3']\n },\n portkey: {\n baseUrl: 'https://api.portkey.ai/v1',\n defaultModels: ['portkey-model']\n },\n llamacpp: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n textgenwebui: {\n baseUrl: 'http://localhost:5000/v1',\n defaultModels: ['local-model']\n },\n gpt4all: {\n baseUrl: 'http://localhost:4891/v1',\n defaultModels: ['local-model']\n },\n mlflow: {\n baseUrl: 'http://localhost:5000/api/2.0/mlflow/gateway',\n defaultModels: ['gateway-model']\n },\n langchainlocal: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n ollamaremote: {\n baseUrl: 'http://remote-ollama-host:11434/v1',\n defaultModels: ['llama3']\n },\n runpodserverless: {\n baseUrl: 'https://api.runpod.ai/v1/serverless',\n defaultModels: ['llama3-70b']\n },\n awsbedrockcompatible: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['meta.llama3-70b-instruct-v1:0']\n },\n azureopenaicompatible: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['gpt-4o']\n },\n custom: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['custom-model']\n },\n openrouterfree: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free', 'mistralai/mistral-7b-instruct:free']\n },\n openrouterbeta: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['openrouter/auto']\n },\n router: {\n baseUrl: 'https://api.router.ai/v1',\n defaultModels: ['router-model']\n },\n feather: {\n baseUrl: 'https://api.feather.ai/v1',\n defaultModels: ['feather-model']\n },\n sensenova: {\n baseUrl: 'https://api.sensetime.com/v1',\n defaultModels: ['sensenova-model']\n },\n hunyuan: {\n baseUrl: 'https://api.hunyuan.tencent.com/v1',\n defaultModels: ['hunyuan-model']\n },\n spark: {\n baseUrl: 'https://api.xfyun.cn/v1',\n defaultModels: ['spark-model']\n },\n baiduqianfan: {\n baseUrl: 'https://api.baidu.com/v1',\n defaultModels: ['ernie-bot']\n },\n copilot: {\n baseUrl: 'https://api.github.com',\n defaultModels: ['copilot-chat']\n },\n tabby: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['tabby-model']\n },\n continue: {\n baseUrl: 'http://localhost:5092/v1',\n defaultModels: ['local-model']\n },\n cursor: {\n baseUrl: 'https://api.cursor.sh/v1',\n defaultModels: ['cursor-model']\n },\n ghostcoder: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['ghostcoder-model']\n },\n codegpt: {\n baseUrl: 'https://api.codegpt.co/v1',\n defaultModels: ['codegpt-model']\n },\n codeium: {\n baseUrl: 'https://api.codeium.com/v1',\n defaultModels: ['codeium-model']\n },\n supermaven: {\n baseUrl: 'https://api.supermaven.com/v1',\n defaultModels: ['supermaven-model']\n },\n sourcegraphcody: {\n baseUrl: 'https://api.sourcegraph.com/v1',\n defaultModels: ['cody-model']\n },\n blackbox: {\n baseUrl: 'https://api.blackbox.ai/v1',\n defaultModels: ['blackbox-model']\n },\n phind: {\n baseUrl: 'https://api.phind.com/v1',\n defaultModels: ['phind-model']\n },\n you: {\n baseUrl: 'https://api.you.com/v1',\n defaultModels: ['you-model']\n },\n duckduckgo: {\n baseUrl: 'https://api.duckduckgo.com/v1',\n defaultModels: ['ddg-model']\n },\n brave: {\n baseUrl: 'https://api.brave.com/v1',\n defaultModels: ['brave-model']\n },\n kling: {\n baseUrl: 'https://api.kling.ai/v1',\n defaultModels: ['kling-model']\n },\n luma: {\n baseUrl: 'https://api.luma.ai/v1',\n defaultModels: ['luma-model']\n },\n runway: {\n baseUrl: 'https://api.runwayml.com/v1',\n defaultModels: ['runway-model']\n },\n sora: {\n baseUrl: 'https://api.sora.com/v1',\n defaultModels: ['sora-model']\n },\n midjourneycompatible: {\n baseUrl: 'https://api.midjourney.com/v1',\n defaultModels: ['mj-model']\n },\n stablediffusioncompatible: {\n baseUrl: 'https://api.stability.ai/v1',\n defaultModels: ['sd-model']\n },\n elevenlabscompatible: {\n baseUrl: 'https://api.elevenlabs.io/v1',\n defaultModels: ['elevenlabs-model']\n },\n voci: {\n baseUrl: 'https://api.voci.ai/v1',\n defaultModels: ['voci-model']\n },\n assemblyai: {\n baseUrl: 'https://api.assemblyai.com/v1',\n defaultModels: ['assemblyai-model']\n },\n deepgram: {\n baseUrl: 'https://api.deepgram.com/v1',\n defaultModels: ['deepgram-model']\n },\n whispercompatible: {\n baseUrl: 'https://api.whisper.com/v1',\n defaultModels: ['whisper-model']\n },\n glhf: {\n baseUrl: 'https://api.glhf.chat/v1',\n defaultModels: ['hf:meta-llama/Llama-3-8B-Instruct']\n },\n hyperbolicfree: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-8B-Instruct']\n },\n openrouterfreetier: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free']\n },\n cohere: {\n baseUrl: 'https://api.cohere.com/v1',\n defaultModels: ['command-r-plus', 'command-r']\n },\n writer: {\n baseUrl: 'https://api.writer.com/v1',\n defaultModels: ['palmyra-x']\n },\n groqfree: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama3-8b-8192']\n }\n};\n\nexport class ProviderManager {\n static getProvider(name, apiKey = '') {\n const providerName = name.toLowerCase();\n \n switch (providerName) {\n case 'gemini':\n return new GeminiProvider(apiKey);\n case 'openai':\n return new OpenAIProvider(apiKey);\n case 'anthropic':\n return new AnthropicProvider(apiKey);\n case 'nvidia':\n return new NvidiaProvider(apiKey);\n case 'ollama':\n return new OllamaProvider();\n }\n\n const customConfig = PROVIDERS_CONFIG[providerName];\n if (customConfig) {\n return new OpenAICompatibleProvider(apiKey, customConfig.baseUrl, customConfig.defaultModels);\n }\n\n throw new Error(`Unknown provider: ${name}. Supported: gemini, openai, anthropic, nvidia, ollama, and 100+ custom OpenAI-compatible providers (like openrouter, groq, etc.).`);\n }\n}\n\n"}, "d02deccb9191786f7d3bcdab7d96421833218020": {"t": 1785758877.210882, "n": 28211, "h": "d6535e9a7ffd70ff", "c": "export class BaseProvider {\n listModels() {\n throw new Error('Not implemented');\n }\n async *generateStream(systemPrompt, messages, model) {\n throw new Error('Not implemented');\n }\n}\n\nexport class GeminiProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n if (data.models) {\n return data.models\n .filter(m => m.supportedGenerationMethods?.includes('generateContent'))\n .map(m => m.name.replace('models/', ''));\n }\n }\n } catch (e) {\n // Fallback below\n }\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('Google Gemini API Key is not configured. Set GEMINI_API_KEY env var.');\n }\n\n const modelName = model.replace('models/', '');\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:streamGenerateContent?key=${this.apiKey}`;\n \n const contents = messages.map(msg => ({\n role: msg.role === 'user' ? 'user' : 'model',\n parts: [{ text: msg.content }]\n }));\n\n const payload = { contents };\n if (systemPrompt) {\n payload.systemInstruction = {\n parts: [{ text: systemPrompt }]\n };\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Gemini API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n while (true) {\n const textMarker = '\"text\": \"';\n const idx = buffer.indexOf(textMarker);\n if (idx === -1) break;\n\n const startPos = idx + textMarker.length;\n let endPos = startPos;\n while (endPos < buffer.length) {\n if (buffer[endPos] === '\"' && buffer[endPos - 1] !== '\\\\') {\n break;\n }\n endPos++;\n }\n\n if (endPos >= buffer.length) break; // String not fully loaded\n\n const escapedText = buffer.substring(startPos, endPos);\n let text = escapedText;\n try {\n text = JSON.parse(`\"${escapedText}\"`);\n } catch (e) {\n text = escapedText\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n \n yield text;\n buffer = buffer.substring(endPos + 1);\n }\n }\n }\n}\n\nexport class OpenAIProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n try {\n const response = await fetch('https://api.openai.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .filter(id => id.includes('gpt') || id.includes('o1') || id.includes('o3') || id.startsWith('chatgpt'))\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('OpenAI API Key is not configured. Set OPENAI_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`OpenAI API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore parse errors on partial streams\n }\n }\n }\n }\n }\n}\n\nexport class AnthropicProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n try {\n const response = await fetch('https://api.anthropic.com/v1/models', {\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data.map(m => m.id);\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('Anthropic API Key is not configured. Set ANTHROPIC_API_KEY env var.');\n }\n\n const payload = {\n model,\n messages,\n stream: true,\n max_tokens: 4096\n };\n if (systemPrompt) {\n payload.system = systemPrompt;\n }\n\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Anthropic API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n try {\n const data = JSON.parse(dataContent);\n if (data.type === 'content_block_delta') {\n const text = data.delta?.text || '';\n if (text) yield text;\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OllamaProvider extends BaseProvider {\n constructor() {\n super();\n this.baseUrl = 'http://localhost:11434';\n }\n\n async listModels() {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (response.ok) {\n const data = await response.json();\n return data.models?.map(m => m.name) || ['llama3', 'mistral', 'phi3'];\n }\n return ['llama3', 'mistral', 'phi3'];\n } catch (e) {\n return ['llama3', 'mistral', 'phi3'];\n }\n }\n\n async *generateStream(systemPrompt, messages, model) {\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Ollama error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n const content = data.message?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class NvidiaProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n try {\n const response = await fetch('https://integrate.api.nvidia.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('NVIDIA API Key is not configured. Set NVIDIA_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch('https://integrate.api.nvidia.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`NVIDIA API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OpenAICompatibleProvider extends BaseProvider {\n constructor(apiKey, baseUrl, defaultModels) {\n super();\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.defaultModels = defaultModels;\n }\n\n async listModels() {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n return this.defaultModels;\n }\n try {\n const headers = {};\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n const response = await fetch(`${this.baseUrl}/models`, { headers });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback below\n }\n return this.defaultModels;\n }\n\n async *generateStream(systemPrompt, messages, model) {\n const isLocal = this.baseUrl.includes('localhost') || this.baseUrl.includes('127.0.0.1') || this.baseUrl.includes('::1');\n if (!this.apiKey && !isLocal) {\n throw new Error(`API Key is not configured for this provider. Configure it via /provider command.`);\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const headers = {\n 'Content-Type': 'application/json'\n };\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const response = await fetch(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n const cleaned = line.trim();\n if (cleaned.startsWith('data: ')) {\n const dataContent = cleaned.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport const PROVIDERS_CONFIG = {\n openrouter: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['google/gemini-2.5-flash', 'meta-llama/llama-3.3-70b-instruct', 'deepseek/deepseek-chat', 'anthropic/claude-3.5-sonnet']\n },\n groq: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama-3.3-70b-specdec', 'llama-3.3-70b-versatile', 'llama3-70b-8192', 'gemma2-9b-it']\n },\n deepseek: {\n baseUrl: 'https://api.deepseek.com',\n defaultModels: ['deepseek-chat', 'deepseek-coder']\n },\n together: {\n baseUrl: 'https://api.together.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'mistralai/Mixtral-8x22B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct']\n },\n mistral: {\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModels: ['mistral-tiny', 'mistral-small', 'mistral-medium', 'mistral-large-latest']\n },\n xai: {\n baseUrl: 'https://api.x.ai/v1',\n defaultModels: ['grok-2-1212', 'grok-2-vision-1212', 'grok-beta']\n },\n perplexity: {\n baseUrl: 'https://api.perplexity.ai',\n defaultModels: ['sonar-reasoning', 'sonar']\n },\n cerebras: {\n baseUrl: 'https://api.cerebras.ai/v1',\n defaultModels: ['llama3.1-8b', 'llama3.1-70b']\n },\n sambanova: {\n baseUrl: 'https://api.sambanova.ai/v1',\n defaultModels: ['Meta-Llama-3.1-8B-Instruct', 'Meta-Llama-3.1-70B-Instruct', 'Meta-Llama-3.1-405B-Instruct']\n },\n deepinfra: {\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1']\n },\n fireworks: {\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n defaultModels: ['accounts/fireworks/models/llama-v3-70b-instruct', 'accounts/fireworks/models/mixtral-8x22b-instruct']\n },\n novita: {\n baseUrl: 'https://api.novita.ai/v1/openai',\n defaultModels: ['meta-llama/llama-3-70b-instruct', 'mistralai/mystral-7b-instruct']\n },\n lepton: {\n baseUrl: 'https://api.lepton.ai/v1',\n defaultModels: ['llama3-70b', 'mixtral-8x22b']\n },\n hyperbolic: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n nebius: {\n baseUrl: 'https://api.studio.nebius.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3.1-70B-Instruct', 'Qwen/Qwen2.5-72B-Instruct']\n },\n friendli: {\n baseUrl: 'https://api.friendli.ai/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n runpod: {\n baseUrl: 'https://api.runpod.ai/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n lmstudio: {\n baseUrl: 'http://localhost:1234/v1',\n defaultModels: ['local-model']\n },\n localai: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n vllm: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n koboldcpp: {\n baseUrl: 'http://localhost:5001/v1',\n defaultModels: ['local-model']\n },\n opencodezen: {\n baseUrl: 'https://api.opencodezen.com/v1',\n defaultModels: ['opencodezen-chat', 'opencodezen-coder']\n },\n llamaapi: {\n baseUrl: 'https://api.llama-api.com',\n defaultModels: ['llama3-70b']\n },\n anyscale: {\n baseUrl: 'https://api.endpoints.anyscale.com/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n monsterapi: {\n baseUrl: 'https://api.monsterapi.ai/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n openpipe: {\n baseUrl: 'https://api.openpipe.ai/v1',\n defaultModels: ['openpipe-model']\n },\n huggingface: {\n baseUrl: 'https://api-inference.huggingface.co/v1',\n defaultModels: ['meta-llama/Meta-Llama-3-70B-Instruct']\n },\n lambdalabs: {\n baseUrl: 'https://api.lambdalabs.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n octoai: {\n baseUrl: 'https://text.octoai.run/v1',\n defaultModels: ['meta-llama-3-70b-instruct']\n },\n ai21: {\n baseUrl: 'https://api.ai21.com/studio/v1',\n defaultModels: ['jamba-instruct']\n },\n scale: {\n baseUrl: 'https://api.scale.com/v1',\n defaultModels: ['llama3-70b-instruct']\n },\n gooseai: {\n baseUrl: 'https://api.goose.ai/v1',\n defaultModels: ['gpt-neo-20b']\n },\n alibaba: {\n baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n defaultModels: ['qwen-max', 'qwen-plus', 'qwen-turbo']\n },\n zhipu: {\n baseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n defaultModels: ['glm-4', 'glm-4-air', 'glm-4-flash']\n },\n moonshot: {\n baseUrl: 'https://api.moonshot.cn/v1',\n defaultModels: ['moonshot-v1-8k', 'moonshot-v1-32k']\n },\n minimax: {\n baseUrl: 'https://api.minimax.chat/v1',\n defaultModels: ['abab6.5-chat', 'abab6-chat']\n },\n yi: {\n baseUrl: 'https://api.01.ai/v1',\n defaultModels: ['yi-large', 'yi-medium']\n },\n baichuan: {\n baseUrl: 'https://api.baichuan-ai.com/v1',\n defaultModels: ['Baichuan3-Turbo', 'Baichuan2-Turbo']\n },\n doubao: {\n baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',\n defaultModels: ['doubao-pro-4k', 'doubao-lite-4k']\n },\n stepfun: {\n baseUrl: 'https://api.stepfun.com/v1',\n defaultModels: ['step-1-8k', 'step-1-32k']\n },\n siliconflow: {\n baseUrl: 'https://api.siliconflow.cn/v1',\n defaultModels: ['deepseek-ai/DeepSeek-V2.5', 'THUDM/glm-4-9b-chat']\n },\n textsynth: {\n baseUrl: 'https://textsynth.com/api/v1',\n defaultModels: ['llama3-8b']\n },\n api2d: {\n baseUrl: 'https://api.api2d.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n linkai: {\n baseUrl: 'https://api.link-ai.chat/v1',\n defaultModels: ['gpt-4o-mini']\n },\n oneapi: {\n baseUrl: 'https://api.oneapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n newapi: {\n baseUrl: 'https://api.newapi.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n opencode: {\n baseUrl: 'https://api.opencode.com/v1',\n defaultModels: ['opencode-chat']\n },\n openchat: {\n baseUrl: 'https://api.openchat.com/v1',\n defaultModels: ['openchat-model']\n },\n cloudl: {\n baseUrl: 'https://api.cloudl.com/v1',\n defaultModels: ['cloudl-chat']\n },\n deepgpt: {\n baseUrl: 'https://api.deepgpt.com/v1',\n defaultModels: ['gpt-4o-mini']\n },\n llamacloud: {\n baseUrl: 'https://api.llamacloud.com/v1',\n defaultModels: ['llama3-70b']\n },\n aimlapi: {\n baseUrl: 'https://api.aimlapi.com/v1',\n defaultModels: ['meta-llama/Llama-3-70b-chat-hf', 'gpt-4o-mini', 'claude-3-5-sonnet']\n },\n glider: {\n baseUrl: 'https://api.glider.ai/v1',\n defaultModels: ['glider-model']\n },\n openlayer: {\n baseUrl: 'https://api.openlayer.com/v1',\n defaultModels: ['openlayer-model']\n },\n databricks: {\n baseUrl: 'https://api.databricks.com/v1',\n defaultModels: ['databricks-meta-llama-3-1-70b']\n },\n workersai: {\n baseUrl: 'https://api.cloudflare.com/client/v4/accounts/default/ai/v1',\n defaultModels: ['llama-3']\n },\n portkey: {\n baseUrl: 'https://api.portkey.ai/v1',\n defaultModels: ['portkey-model']\n },\n llamacpp: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['local-model']\n },\n textgenwebui: {\n baseUrl: 'http://localhost:5000/v1',\n defaultModels: ['local-model']\n },\n gpt4all: {\n baseUrl: 'http://localhost:4891/v1',\n defaultModels: ['local-model']\n },\n mlflow: {\n baseUrl: 'http://localhost:5000/api/2.0/mlflow/gateway',\n defaultModels: ['gateway-model']\n },\n langchainlocal: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['local-model']\n },\n ollamaremote: {\n baseUrl: 'http://remote-ollama-host:11434/v1',\n defaultModels: ['llama3']\n },\n runpodserverless: {\n baseUrl: 'https://api.runpod.ai/v1/serverless',\n defaultModels: ['llama3-70b']\n },\n awsbedrockcompatible: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['meta.llama3-70b-instruct-v1:0']\n },\n azureopenaicompatible: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['gpt-4o']\n },\n custom: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['custom-model']\n },\n openrouterfree: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free', 'mistralai/mistral-7b-instruct:free']\n },\n openrouterbeta: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['openrouter/auto']\n },\n router: {\n baseUrl: 'https://api.router.ai/v1',\n defaultModels: ['router-model']\n },\n feather: {\n baseUrl: 'https://api.feather.ai/v1',\n defaultModels: ['feather-model']\n },\n sensenova: {\n baseUrl: 'https://api.sensetime.com/v1',\n defaultModels: ['sensenova-model']\n },\n hunyuan: {\n baseUrl: 'https://api.hunyuan.tencent.com/v1',\n defaultModels: ['hunyuan-model']\n },\n spark: {\n baseUrl: 'https://api.xfyun.cn/v1',\n defaultModels: ['spark-model']\n },\n baiduqianfan: {\n baseUrl: 'https://api.baidu.com/v1',\n defaultModels: ['ernie-bot']\n },\n copilot: {\n baseUrl: 'https://api.github.com',\n defaultModels: ['copilot-chat']\n },\n tabby: {\n baseUrl: 'http://localhost:8080/v1',\n defaultModels: ['tabby-model']\n },\n continue: {\n baseUrl: 'http://localhost:5092/v1',\n defaultModels: ['local-model']\n },\n cursor: {\n baseUrl: 'https://api.cursor.sh/v1',\n defaultModels: ['cursor-model']\n },\n ghostcoder: {\n baseUrl: 'http://localhost:8000/v1',\n defaultModels: ['ghostcoder-model']\n },\n codegpt: {\n baseUrl: 'https://api.codegpt.co/v1',\n defaultModels: ['codegpt-model']\n },\n codeium: {\n baseUrl: 'https://api.codeium.com/v1',\n defaultModels: ['codeium-model']\n },\n supermaven: {\n baseUrl: 'https://api.supermaven.com/v1',\n defaultModels: ['supermaven-model']\n },\n sourcegraphcody: {\n baseUrl: 'https://api.sourcegraph.com/v1',\n defaultModels: ['cody-model']\n },\n blackbox: {\n baseUrl: 'https://api.blackbox.ai/v1',\n defaultModels: ['blackbox-model']\n },\n phind: {\n baseUrl: 'https://api.phind.com/v1',\n defaultModels: ['phind-model']\n },\n you: {\n baseUrl: 'https://api.you.com/v1',\n defaultModels: ['you-model']\n },\n duckduckgo: {\n baseUrl: 'https://api.duckduckgo.com/v1',\n defaultModels: ['ddg-model']\n },\n brave: {\n baseUrl: 'https://api.brave.com/v1',\n defaultModels: ['brave-model']\n },\n kling: {\n baseUrl: 'https://api.kling.ai/v1',\n defaultModels: ['kling-model']\n },\n luma: {\n baseUrl: 'https://api.luma.ai/v1',\n defaultModels: ['luma-model']\n },\n runway: {\n baseUrl: 'https://api.runwayml.com/v1',\n defaultModels: ['runway-model']\n },\n sora: {\n baseUrl: 'https://api.sora.com/v1',\n defaultModels: ['sora-model']\n },\n midjourneycompatible: {\n baseUrl: 'https://api.midjourney.com/v1',\n defaultModels: ['mj-model']\n },\n stablediffusioncompatible: {\n baseUrl: 'https://api.stability.ai/v1',\n defaultModels: ['sd-model']\n },\n elevenlabscompatible: {\n baseUrl: 'https://api.elevenlabs.io/v1',\n defaultModels: ['elevenlabs-model']\n },\n voci: {\n baseUrl: 'https://api.voci.ai/v1',\n defaultModels: ['voci-model']\n },\n assemblyai: {\n baseUrl: 'https://api.assemblyai.com/v1',\n defaultModels: ['assemblyai-model']\n },\n deepgram: {\n baseUrl: 'https://api.deepgram.com/v1',\n defaultModels: ['deepgram-model']\n },\n whispercompatible: {\n baseUrl: 'https://api.whisper.com/v1',\n defaultModels: ['whisper-model']\n },\n glhf: {\n baseUrl: 'https://api.glhf.chat/v1',\n defaultModels: ['hf:meta-llama/Llama-3-8B-Instruct']\n },\n hyperbolicfree: {\n baseUrl: 'https://api.hyperbolic.xyz/v1',\n defaultModels: ['meta-llama/Llama-3-8B-Instruct']\n },\n openrouterfreetier: {\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModels: ['meta-llama/llama-3-8b-instruct:free']\n },\n cohere: {\n baseUrl: 'https://api.cohere.com/v1',\n defaultModels: ['command-r-plus', 'command-r']\n },\n writer: {\n baseUrl: 'https://api.writer.com/v1',\n defaultModels: ['palmyra-x']\n },\n groqfree: {\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModels: ['llama3-8b-8192']\n }\n};\n\nexport class ProviderManager {\n static getProvider(name, apiKey = '') {\n const providerName = name.toLowerCase();\n \n switch (providerName) {\n case 'gemini':\n return new GeminiProvider(apiKey);\n case 'openai':\n return new OpenAIProvider(apiKey);\n case 'anthropic':\n return new AnthropicProvider(apiKey);\n case 'nvidia':\n return new NvidiaProvider(apiKey);\n case 'ollama':\n return new OllamaProvider();\n }\n\n const customConfig = PROVIDERS_CONFIG[providerName];\n if (customConfig) {\n return new OpenAICompatibleProvider(apiKey, customConfig.baseUrl, customConfig.defaultModels);\n }\n\n throw new Error(`Unknown provider: ${name}. Supported: gemini, openai, anthropic, nvidia, ollama, and 100+ custom OpenAI-compatible providers (like openrouter, groq, etc.).`);\n }\n}\n\n"}, "8cd3a888dd1d4cb9795eefc4e003114c18c33898": {"t": 1785758878.5366142, "n": 13168, "h": "02045b1835696e37", "c": "export class BaseProvider {\n listModels() {\n throw new Error('Not implemented');\n }\n async *generateStream(systemPrompt, messages, model) {\n throw new Error('Not implemented');\n }\n}\n\nexport class GeminiProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;\n const response = await fetch(url);\n if (response.ok) {\n const data = await response.json();\n if (data.models) {\n return data.models\n .filter(m => m.supportedGenerationMethods?.includes('generateContent'))\n .map(m => m.name.replace('models/', ''));\n }\n }\n } catch (e) {\n // Fallback below\n }\n return ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('Google Gemini API Key is not configured. Set GEMINI_API_KEY env var.');\n }\n\n const modelName = model.replace('models/', '');\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:streamGenerateContent?key=${this.apiKey}`;\n \n const contents = messages.map(msg => ({\n role: msg.role === 'user' ? 'user' : 'model',\n parts: [{ text: msg.content }]\n }));\n\n const payload = { contents };\n if (systemPrompt) {\n payload.systemInstruction = {\n parts: [{ text: systemPrompt }]\n };\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Gemini API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n while (true) {\n const textMarker = '\"text\": \"';\n const idx = buffer.indexOf(textMarker);\n if (idx === -1) break;\n\n const startPos = idx + textMarker.length;\n let endPos = startPos;\n while (endPos < buffer.length) {\n if (buffer[endPos] === '\"' && buffer[endPos - 1] !== '\\\\') {\n break;\n }\n endPos++;\n }\n\n if (endPos >= buffer.length) break; // String not fully loaded\n\n const escapedText = buffer.substring(startPos, endPos);\n let text = escapedText;\n try {\n text = JSON.parse(`\"${escapedText}\"`);\n } catch (e) {\n text = escapedText\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\\\\/g, '\\\\');\n }\n \n yield text;\n buffer = buffer.substring(endPos + 1);\n }\n }\n }\n}\n\nexport class OpenAIProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n try {\n const response = await fetch('https://api.openai.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .filter(id => id.includes('gpt') || id.includes('o1') || id.includes('o3') || id.startsWith('chatgpt'))\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['gpt-4o', 'gpt-4o-mini', 'o1-mini', 'gpt-4-turbo'];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('OpenAI API Key is not configured. Set OPENAI_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`OpenAI API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore parse errors on partial streams\n }\n }\n }\n }\n }\n}\n\nexport class AnthropicProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n try {\n const response = await fetch('https://api.anthropic.com/v1/models', {\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data.map(m => m.id);\n }\n }\n } catch (e) {\n // Fallback\n }\n return ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('Anthropic API Key is not configured. Set ANTHROPIC_API_KEY env var.');\n }\n\n const payload = {\n model,\n messages,\n stream: true,\n max_tokens: 4096\n };\n if (systemPrompt) {\n payload.system = systemPrompt;\n }\n\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Anthropic API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n try {\n const data = JSON.parse(dataContent);\n if (data.type === 'content_block_delta') {\n const text = data.delta?.text || '';\n if (text) yield text;\n }\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class OllamaProvider extends BaseProvider {\n constructor() {\n super();\n this.baseUrl = 'http://localhost:11434';\n }\n\n async listModels() {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (response.ok) {\n const data = await response.json();\n return data.models?.map(m => m.name) || ['llama3', 'mistral', 'phi3'];\n }\n return ['llama3', 'mistral', 'phi3'];\n } catch (e) {\n return ['llama3', 'mistral', 'phi3'];\n }\n }\n\n async *generateStream(systemPrompt, messages, model) {\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Ollama error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n\n for (const line of lines) {\n if (line.trim()) {\n try {\n const data = JSON.parse(line);\n const content = data.message?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class NvidiaProvider extends BaseProvider {\n constructor(apiKey) {\n super();\n this.apiKey = apiKey;\n }\n\n async listModels() {\n if (!this.apiKey) {\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n try {\n const response = await fetch('https://integrate.api.nvidia.com/v1/models', {\n headers: { 'Authorization': `Bearer ${this.apiKey}` }\n });\n if (response.ok) {\n const data = await response.json();\n if (data.data) {\n return data.data\n .map(m => m.id)\n .sort();\n }\n }\n } catch (e) {\n // Fallback\n }\n return [\n 'meta/llama-3.1-405b-instruct',\n 'meta/llama-3.1-70b-instruct',\n 'meta/llama-3.1-8b-instruct',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'mistralai/mixtral-8x22b-instruct-v0.1'\n ];\n }\n\n async *generateStream(systemPrompt, messages, model) {\n if (!this.apiKey) {\n throw new Error('NVIDIA API Key is not configured. Set NVIDIA_API_KEY env var.');\n }\n\n const formattedMessages = [];\n if (systemPrompt) {\n formattedMessages.push({ role: 'system', content: systemPrompt });\n }\n formattedMessages.push(...messages);\n\n const payload = {\n model,\n messages: formattedMessages,\n stream: true\n };\n\n const response = await fetch('https://integrate.api.nvidia.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`NVIDIA API error (${response.status}): ${errText}`);\n }\n\n const decoder = new TextDecoder();\n const reader = response.body.getReader();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // Keep incomplete line\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const dataContent = line.slice(6).trim();\n if (dataContent === '[DONE]') break;\n try {\n const data = JSON.parse(dataContent);\n const content = data.choices?.[0]?.delta?.content || '';\n if (content) yield content;\n } catch (e) {\n // Ignore partial errors\n }\n }\n }\n }\n }\n}\n\nexport class ProviderManager {\n static getProvider(name, apiKey = '') {\n const providerName = name.toLowerCase();\n switch (providerName) {\n case 'gemini':\n return new GeminiProvider(apiKey);\n case 'openai':\n return new OpenAIProvider(apiKey);\n case 'anthropic':\n return new AnthropicProvider(apiKey);\n case 'nvidia':\n return new NvidiaProvider(apiKey);\n case 'ollama':\n return new OllamaProvider();\n default:\n throw new Error(`Unknown provider: ${name}. Supported: gemini, openai, anthropic, nvidia, ollama.`);\n }\n }\n}\n"}}}} |