{"repo": "albertolive/ai-gateway", "files": {"app-callers/gateway-client.js": {"173abdc4d6548cad558be237ed5eecb7279505a3": {"t": 1786068266.912477, "n": 3104, "h": "0f5a57b5d10afbc3", "c": "// Vendored ai-gateway client for application runtime code (not CI).\n//\n// Copy this single file into your app. It fetches the shared provider/model\n// config from ai-gateway's models.json on each call, so swapping a model or\n// adding a provider (e.g. a paid one like DeepSeek) is a one-line edit in\n// ai-gateway -- this file and your app code never change. If the fetch fails\n// (GitHub outage), it falls back to OpenRouter's free auto-router so the app\n// doesn't hard-fail on a transient network issue.\n//\n// Usage:\n// const { complete } = require(\"./gateway-client\");\n// const text = await complete(\"Summarize this weather forecast: ...\");\n// const text = await complete(\"...\", { cascade: \"deepseek_cheap\" }); // paid, needs DEEPSEEK_API_KEY\n//\n// Env: one API key per provider you use (OPENROUTER_API_KEY, DEEPSEEK_API_KEY,\n// GEMINI_API_KEY, GROQ_API_KEY). Only providers with a key set are tried.\n\nconst CONFIG_URL =\n \"https://raw.githubusercontent.com/albertolive/ai-gateway/main/models.json\";\n\nconst FALLBACK_PROVIDERS = {\n openrouter: { url: \"https://openrouter.ai/api/v1\", key_env: \"OPENROUTER_API_KEY\" },\n};\nconst FALLBACK_ENTRIES = [{ provider: \"openrouter\", model: \"openrouter/free\" }];\n\nasync function loadConfig(timeoutMs = 10000) {\n try {\n const res = await fetch(CONFIG_URL, { signal: AbortSignal.timeout(timeoutMs) });\n return await res.json();\n } catch {\n return { providers: FALLBACK_PROVIDERS, cascades: {} };\n }\n}\n\nasync function complete(prompt, { cascade = \"general\", system, temperature = 0.1 } = {}) {\n const config = await loadConfig();\n const providers = Object.keys(config.providers || {}).length\n ? config.providers\n : FALLBACK_PROVIDERS;\n const entries = (config.cascades && config.cascades[cascade]) || FALLBACK_ENTRIES;\n\n const errors = [];\n for (const entry of entries) {\n const provider = providers[entry.provider];\n if (!provider) {\n errors.push(`${entry.provider}/${entry.model}: unknown provider '${entry.provider}'`);\n continue;\n }\n const apiKey = process.env[provider.key_env];\n if (!apiKey) {\n errors.push(`${entry.provider}/${entry.model}: ${provider.key_env} not set`);\n continue;\n }\n\n const messages = [\n ...(system ? [{ role: \"system\", content: system }] : []),\n { role: \"user\", content: prompt },\n ];\n\n try {\n const res = await fetch(`${provider.url.replace(/\\/$/, \"\")}/chat/completions`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ model: entry.model, messages, temperature }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);\n const body = await res.json();\n return body.choices[0].message.content;\n } catch (e) {\n errors.push(`${entry.provider}/${entry.model}: ${e}`);\n }\n }\n\n throw new Error(\n `All providers in cascade '${cascade}' failed or were unconfigured:\\n` +\n errors.map((e) => ` - ${e}`).join(\"\\n\")\n );\n}\n\nmodule.exports = { complete };\n"}}}}