// LLM client — Ollama (default), Anthropic, or any OpenAI-compatible endpoint // Configure via env: // LLM_PROVIDER=ollama|anthropic|openai (default: ollama) // LLM_BASE_URL=http://localhost:11434 (default for ollama) // LLM_MODEL=llama3 (default) // LLM_API_KEY=... (required for anthropic/openai) const PROVIDER = process.env.LLM_PROVIDER || 'ollama' const MODEL = process.env.LLM_MODEL || 'llama3' const API_KEY = process.env.LLM_API_KEY const BASE_URL = process.env.LLM_BASE_URL || 'http://localhost:11434' export async function chat(messages) { if (PROVIDER === 'anthropic') return anthropic(messages) if (PROVIDER === 'openai') return openaiCompat(messages, BASE_URL || 'https://api.openai.com/v1') return ollama(messages) } async function ollama(messages) { const r = await fetch(`${BASE_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: MODEL, messages, stream: false }), }) if (!r.ok) throw new Error(`Ollama ${r.status}: ${await r.text()}`) const data = await r.json() return data.message.content } async function anthropic(messages) { // Separate system message — Anthropic requires it at top level const system = messages.find(m => m.role === 'system')?.content const rest = messages.filter(m => m.role !== 'system') const r = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model: MODEL || 'claude-sonnet-4-6', max_tokens: 2048, ...(system && { system }), messages: rest, }), }) if (!r.ok) throw new Error(`Anthropic ${r.status}: ${await r.text()}`) const data = await r.json() return data.content[0].text } async function openaiCompat(messages, base) { const r = await fetch(`${base}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(API_KEY && { Authorization: `Bearer ${API_KEY}` }), }, body: JSON.stringify({ model: MODEL, messages, stream: false }), }) if (!r.ok) throw new Error(`LLM ${r.status}: ${await r.text()}`) const data = await r.json() return data.choices[0].message.content }