Files

1 line
17 KiB
JSON

{"repo": "applelamps/rotuer", "files": {"server.js": {"854cbf31b4fd892b577ff778488389bf85297124": {"t": 1785596057.919444, "n": 16923, "h": "e91b7ef8b17f8346", "c": "require(\"dotenv\").config();\nconst crypto = require(\"crypto\");\nconst express = require(\"express\");\nconst path = require(\"path\");\n\nconst app = express();\napp.use(express.json());\napp.use(express.static(path.join(__dirname, \"public\")));\n\nconst PORT = process.env.PORT || 3000;\n\n// ── Models pricing cache (fetched from OpenRouter, keyed by API key fingerprint) ──\nconst modelsCacheMap = new Map(); // fp -> { data, time }\nconst CACHE_TTL = 10 * 60 * 1000; // 10 minutes\n\nfunction fingerprintApiKey(apiKey) {\n const k = (apiKey && String(apiKey).trim()) || \"\";\n return crypto.createHash(\"sha256\").update(k || \"__EMPTY__\").digest(\"hex\").slice(0, 32);\n}\n\n/** Only client-supplied key: header (GET + POST) or JSON body. Server .env is not used. */\nfunction resolveOpenRouterApiKey(req) {\n const header = req.headers[\"x-openrouter-key\"];\n if (typeof header === \"string\" && header.trim()) return header.trim();\n const bodyKey = req.body?.openrouter_api_key;\n if (typeof bodyKey === \"string\" && bodyKey.trim()) return bodyKey.trim();\n return \"\";\n}\n\n// Simple rate limiting — one compare at a time, min 1s between requests\nlet lastCompareTime = 0;\nconst RATE_LIMIT_MS = 1000;\n\n// Models we feature in the dropdown\nconst FEATURED_IDS = [\n \"openai/gpt-5.4\",\n \"anthropic/claude-sonnet-4.6\",\n \"google/gemini-3-flash-preview\",\n \"deepseek/deepseek-chat\",\n \"qwen/qwen3-235b-a22b\",\n \"meta-llama/llama-4-maverick\",\n \"z-ai/glm-5.1\",\n \"google/gemma-4-26b-a4b-it:free\",\n \"google/gemma-4-31b-it:free\",\n \"qwen/qwen3.6-plus\",\n \"kwaipilot/kat-coder-pro-v2\",\n \"xiaomi/mimo-v2-omni\",\n \"minimax/minimax-m2.7\",\n \"google/gemini-3.1-flash-lite-preview\",\n];\n\nasync function fetchModelsFromOpenRouter(apiKey) {\n const key = (apiKey && String(apiKey).trim()) || \"\";\n const fp = fingerprintApiKey(key);\n const now = Date.now();\n const cached = modelsCacheMap.get(fp);\n if (cached && now - cached.time < CACHE_TTL) return cached.data;\n\n try {\n const headers = { \"Content-Type\": \"application/json\" };\n if (key) headers.Authorization = `Bearer ${key}`;\n const res = await fetch(\"https://openrouter.ai/api/v1/models\", { headers });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const json = await res.json();\n const list = json.data || [];\n modelsCacheMap.set(fp, { data: list, time: now });\n console.log(` Cached ${list.length} models from OpenRouter (key fp ${fp.slice(0, 8)}…)`);\n return list;\n } catch (err) {\n console.error(\" Failed to fetch models:\", err.message);\n return cached?.data || [];\n }\n}\n\n// Look up pricing for a model ID (exact match first, then prefix/fuzzy)\nfunction getPricing(allModels, modelId) {\n let m = allModels.find((x) => x.id === modelId);\n if (!m) m = allModels.find((x) => modelId.startsWith(x.id));\n if (!m) m = allModels.find((x) => x.id.startsWith(modelId));\n if (!m) {\n const stripped = modelId.replace(/-\\d{8,}$/, \"\");\n if (stripped !== modelId) {\n m = allModels.find((x) => x.id === stripped);\n if (!m) m = allModels.find((x) => stripped.startsWith(x.id));\n if (!m) m = allModels.find((x) => x.id.startsWith(stripped));\n }\n }\n if (!m && modelId.includes(\"/\")) {\n const org = modelId.split(\"/\")[0];\n const candidates = allModels.filter((x) => x.id.startsWith(org + \"/\"));\n const terms = modelId.split(\"/\").pop().replace(/-\\d{8,}$/, \"\")\n .split(/[-_.]/).filter((t) => t.length > 1 && !/^\\d+$/.test(t));\n let best = null, bestScore = 0;\n for (const c of candidates) {\n const cTerms = c.id.split(\"/\").pop().split(/[-_.]/);\n const score = terms.filter((t) => cTerms.some((ct) => ct.includes(t) || t.includes(ct))).length;\n if (score > bestScore) { best = c; bestScore = score; }\n }\n if (best && bestScore >= 2) m = best;\n else if (candidates.length === 1) m = candidates[0];\n }\n if (!m || !m.pricing) return null;\n return {\n prompt: parseFloat(m.pricing.prompt) || 0,\n completion: parseFloat(m.pricing.completion) || 0,\n request: parseFloat(m.pricing.request) || 0,\n image: parseFloat(m.pricing.image) || 0,\n name: m.name || modelId,\n context_length: m.context_length || 0,\n };\n}\n\n// Calculate actual cost from token counts and per-token pricing\nfunction calcCost(tokens, pricing) {\n if (!pricing) return { total: 0, input_cost: 0, output_cost: 0, per_million: null };\n const input_cost = tokens.input * pricing.prompt;\n const output_cost = tokens.output * pricing.completion;\n const total = input_cost + output_cost + pricing.request;\n return {\n total: parseFloat(total.toFixed(8)),\n input_cost: parseFloat(input_cost.toFixed(8)),\n output_cost: parseFloat(output_cost.toFixed(8)),\n per_million: {\n input: parseFloat((pricing.prompt * 1_000_000).toFixed(4)),\n output: parseFloat((pricing.completion * 1_000_000).toFixed(4)),\n },\n };\n}\n\n// ── GET /api/models ──\napp.get(\"/api/models\", async (req, res) => {\n const apiKey = resolveOpenRouterApiKey(req);\n const allModels = await fetchModelsFromOpenRouter(apiKey);\n const featured = FEATURED_IDS.map((id) => {\n const m = allModels.find((x) => x.id === id);\n const pricing = m?.pricing || {};\n return {\n id,\n name: m?.name || id,\n pricing: {\n input_per_million: parseFloat(((parseFloat(pricing.prompt) || 0) * 1_000_000).toFixed(4)),\n output_per_million: parseFloat(((parseFloat(pricing.completion) || 0) * 1_000_000).toFixed(4)),\n },\n context_length: m?.context_length || 0,\n };\n });\n res.json(featured);\n});\n\n// ── GET /api/models/lookup?id=org/model ──\napp.get(\"/api/models/lookup\", async (req, res) => {\n const modelId = req.query.id;\n if (!modelId) return res.status(400).json({ error: \"id query param required\" });\n\n const apiKey = resolveOpenRouterApiKey(req);\n const allModels = await fetchModelsFromOpenRouter(apiKey);\n const pricing = getPricing(allModels, modelId);\n if (pricing) {\n return res.json({\n id: modelId,\n name: pricing.name,\n pricing: {\n input_per_million: parseFloat((pricing.prompt * 1_000_000).toFixed(4)),\n output_per_million: parseFloat((pricing.completion * 1_000_000).toFixed(4)),\n },\n context_length: pricing.context_length,\n found: true,\n });\n }\n res.json({ id: modelId, name: modelId, pricing: null, context_length: 0, found: false });\n});\n\n// ── Helper: build request body ──\nfunction buildBody(model, messages, params, stream = false) {\n return JSON.stringify({ model, messages, ...params, ...(stream ? { stream: true } : {}) });\n}\n\n// ── Helper: non-streaming OpenRouter call (kept for /api/compare fallback) ──\nasync function callOpenRouter(model, messages, params, allModels, apiKey) {\n if (!apiKey) {\n return {\n error: true,\n text: \"No OpenRouter API key. Paste your key in the site header.\",\n latency_ms: 0,\n tokens: { input: 0, output: 0, total: 0 },\n cost: null,\n model,\n provider: \"openrouter\",\n };\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 300_000);\n const start = Date.now();\n\n try {\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n \"HTTP-Referer\": \"http://localhost:3000\",\n \"X-OpenRouter-Title\": \"Route or Die\",\n },\n body: buildBody(model, messages, params),\n signal: controller.signal,\n });\n clearTimeout(timeout);\n const latency_ms = Date.now() - start;\n const actualProvider = response.headers.get(\"x-openrouter-provider\") || \"auto-selected\";\n\n if (!response.ok) {\n const err = await response.json().catch(() => ({}));\n return {\n error: true,\n text: `OpenRouter error ${response.status}: ${err.error?.message || response.statusText}`,\n latency_ms,\n tokens: { input: 0, output: 0, total: 0 },\n cost: null,\n model,\n provider: actualProvider,\n };\n }\n\n const data = await response.json();\n const resolvedModel = data.model || model;\n const tokens = {\n input: data.usage?.prompt_tokens || 0,\n output: data.usage?.completion_tokens || 0,\n total: data.usage?.total_tokens || 0,\n };\n const pricing = getPricing(allModels, resolvedModel);\n const cost = calcCost(tokens, pricing);\n const msg = data.choices?.[0]?.message || {};\n let text = msg.content || \"\";\n if (!text && msg.reasoning) {\n text = \"[Model used all tokens for reasoning]\\n\\n\" + msg.reasoning;\n }\n\n return {\n text,\n latency_ms,\n tokens,\n cost,\n model: resolvedModel,\n model_name: pricing?.name || resolvedModel,\n context_length: pricing?.context_length || 0,\n provider: actualProvider,\n };\n } catch (err) {\n clearTimeout(timeout);\n return {\n error: true,\n text: err.name === \"AbortError\"\n ? \"Request timed out after 5 minutes. The model may be slow — try again.\"\n : `Call failed: ${err.message}`,\n latency_ms: Date.now() - start,\n tokens: { input: 0, output: 0, total: 0 },\n cost: null,\n model,\n provider: \"openrouter\",\n };\n }\n}\n\n// ── Helper: streaming OpenRouter call ──\nasync function streamOpenRouter(model, messages, params, allModels, onChunk, onDone, apiKey) {\n if (!apiKey) {\n return onDone({\n error: true,\n text: \"No OpenRouter API key. Paste your key in the site header.\",\n latency_ms: 0,\n tokens: { input: 0, output: 0, total: 0 },\n cost: null,\n model,\n provider: \"openrouter\",\n });\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 300_000);\n const start = Date.now();\n\n try {\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n \"HTTP-Referer\": \"http://localhost:3000\",\n \"X-OpenRouter-Title\": \"Route or Die\",\n },\n body: buildBody(model, messages, params, true),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n clearTimeout(timeout);\n const err = await response.json().catch(() => ({}));\n return onDone({\n error: true,\n text: `OpenRouter error ${response.status}: ${err.error?.message || response.statusText}`,\n latency_ms: Date.now() - start,\n tokens: { input: 0, output: 0, total: 0 },\n cost: null,\n model,\n provider: response.headers.get(\"x-openrouter-provider\") || \"auto-selected\",\n });\n }\n\n const actualProvider = response.headers.get(\"x-openrouter-provider\") || \"auto-selected\";\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let fullText = \"\";\n let reasoning = \"\";\n let resolvedModel = model;\n let usage = null;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"data: \")) continue;\n const raw = trimmed.slice(6);\n if (raw === \"[DONE]\") continue;\n try {\n const chunk = JSON.parse(raw);\n if (chunk.model) resolvedModel = chunk.model;\n if (chunk.usage) usage = chunk.usage;\n const delta = chunk.choices?.[0]?.delta || {};\n if (delta.reasoning) {\n reasoning += delta.reasoning;\n onChunk(delta.reasoning, true); // true = reasoning/thinking trace\n }\n if (delta.content) {\n fullText += delta.content;\n onChunk(delta.content, false);\n }\n } catch (_) { /* ignore malformed chunks */ }\n }\n }\n\n clearTimeout(timeout);\n\n // If model used all tokens for reasoning with no content, surface it as content\n if (!fullText && reasoning) {\n fullText = reasoning;\n onChunk(fullText, false);\n }\n\n const latency_ms = Date.now() - start;\n const tokens = {\n input: usage?.prompt_tokens || 0,\n output: usage?.completion_tokens || 0,\n total: usage?.total_tokens || 0,\n };\n const pricing = getPricing(allModels, resolvedModel);\n const cost = calcCost(tokens, pricing);\n\n onDone({\n text: fullText,\n latency_ms,\n tokens,\n cost,\n model: resolvedModel,\n model_name: pricing?.name || resolvedModel,\n context_length: pricing?.context_length || 0,\n provider: actualProvider,\n });\n } catch (err) {\n clearTimeout(timeout);\n onDone({\n error: true,\n text: err.name === \"AbortError\"\n ? \"Request timed out after 5 minutes. The model may be slow — try again.\"\n : `Call failed: ${err.message}`,\n latency_ms: Date.now() - start,\n tokens: { input: 0, output: 0, total: 0 },\n cost: null,\n model,\n provider: \"openrouter\",\n });\n }\n}\n\n// ── POST /api/compare/stream (SSE streaming endpoint) ──\napp.post(\"/api/compare/stream\", async (req, res) => {\n // Rate limit\n const now = Date.now();\n if (now - lastCompareTime < RATE_LIMIT_MS) {\n return res.status(429).json({ error: \"Too many requests — please wait a moment.\" });\n }\n lastCompareTime = now;\n\n const apiKey = resolveOpenRouterApiKey(req);\n const { prompt, model, routed_model, temperature, max_tokens, system_prompt } = req.body;\n if (!prompt || typeof prompt !== \"string\") {\n return res.status(400).json({ error: \"prompt is required\" });\n }\n\n const selectedModel = model || \"openai/gpt-4o\";\n const routedModel = (routed_model && typeof routed_model === \"string\" && routed_model.trim())\n ? routed_model.trim()\n : \"openrouter/auto\";\n\n const messages = [];\n if (system_prompt && system_prompt.trim()) {\n messages.push({ role: \"system\", content: system_prompt.trim() });\n }\n messages.push({ role: \"user\", content: prompt });\n\n const params = { temperature: temperature != null ? temperature : 0.7 };\n if (max_tokens && max_tokens > 0) params.max_tokens = max_tokens;\n\n // SSE headers\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n res.flushHeaders();\n\n const send = (obj) => {\n if (!res.writableEnded) res.write(`data: ${JSON.stringify(obj)}\\n\\n`);\n };\n\n const allModels = await fetchModelsFromOpenRouter(apiKey);\n\n let completed = 0;\n const checkDone = () => {\n completed++;\n if (completed === 2) {\n if (!res.writableEnded) {\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n }\n }\n };\n\n // Fire both in parallel — do NOT await, let them stream concurrently\n streamOpenRouter(\n selectedModel, messages, params, allModels,\n (chunk, isReasoning) => send({ panel: \"direct\", type: \"chunk\", text: chunk, reasoning: isReasoning }),\n (meta) => { send({ panel: \"direct\", type: \"done\", meta }); checkDone(); },\n apiKey\n );\n\n streamOpenRouter(\n routedModel, messages, params, allModels,\n (chunk, isReasoning) => send({ panel: \"routed\", type: \"chunk\", text: chunk, reasoning: isReasoning }),\n (meta) => { send({ panel: \"routed\", type: \"done\", meta }); checkDone(); },\n apiKey\n );\n});\n\n// ── POST /api/compare (non-streaming, kept for compatibility) ──\napp.post(\"/api/compare\", async (req, res) => {\n const now = Date.now();\n if (now - lastCompareTime < RATE_LIMIT_MS) {\n return res.status(429).json({ error: \"Too many requests — please wait a moment.\" });\n }\n lastCompareTime = now;\n\n const apiKey = resolveOpenRouterApiKey(req);\n const { prompt, model, routed_model, temperature, max_tokens, system_prompt } = req.body;\n if (!prompt || typeof prompt !== \"string\") {\n return res.status(400).json({ error: \"prompt is required\" });\n }\n\n const selectedModel = model || \"openai/gpt-4o\";\n const routedModel = (routed_model && typeof routed_model === \"string\" && routed_model.trim())\n ? routed_model.trim()\n : \"openrouter/auto\";\n const messages = [];\n if (system_prompt && system_prompt.trim()) {\n messages.push({ role: \"system\", content: system_prompt.trim() });\n }\n messages.push({ role: \"user\", content: prompt });\n\n const params = { temperature: temperature != null ? temperature : 0.7 };\n if (max_tokens && max_tokens > 0) params.max_tokens = max_tokens;\n\n const allModels = await fetchModelsFromOpenRouter(apiKey);\n\n const [direct, routed] = await Promise.all([\n callOpenRouter(selectedModel, messages, params, allModels, apiKey),\n callOpenRouter(routedModel, messages, params, allModels, apiKey),\n ]);\n\n res.json({ direct, routed });\n});\n\napp.listen(PORT, () => {\n console.log(`\\n ROUTE_OR_DIE running on http://localhost:${PORT}\\n`);\n});\n"}}}}