import OpenAI from "openai"; const openRouterAI = new OpenAI({ apiKey: process.env.OPENROUTER_API_KEY, baseURL: "https://openrouter.ai/api/v1", defaultHeaders: { "HTTP-Referer": process.env.YOUR_SITE_URL || "https://everydaycats.web.app", "X-Title": process.env.YOUR_SITE_NAME || "Aura Scanner", } }); // --- MODEL ROUTER --- const getModelId = (modelName) => { switch (modelName?.toLowerCase()) { case "llama": case "maverick": return "meta-llama/llama-4-maverick:floor"; case "llama-3.2-11b": return "meta-llama/llama-3.2-11b-vision-instruct:free"; case "qwen-2-vl-7b": return "qwen/qwen-2-vl-7b-instruct:free"; case "nemotron-12b": return "nvidia/nemotron-nano-12b-v2-vl:free"; case "gpt": case "gpt-5.4-nano": return "openai/gpt-5.4-nano"; case "qwen-non": return "qwen/qwen3-vl-30b-a3b-instruct"; case "bytedance-1.6": return "bytedance-seed/seed-1.6-flash:floor"; case "mistral-small": return "mistralai/mistral-small-2603:floor"; case "grok-4.3": return "x-ai/grok-4.3:floor"; case "qwen": default: return "qwen/qwen3-vl-30b-a3b-thinking:floor"; } }; // --- IMAGE MODEL ROUTER --- const getImageModelId = (tier) => { switch (tier) { case "premium": return "bytedance-seed/seedream-4.5"; case "quick": return "sourceful/riverflow-v2-fast"; default: return "sourceful/riverflow-v2-fast"; } }; // --- ERROR LOGGER --- const logOpenRouterError = (error, context) => { const status = error.status || error.response?.status; if (status === 402) console.error(`💸 [402] ${context}: Insufficient credits.`); else if (status === 429) console.error(`🚨 [429] ${context}: Rate limit exceeded.`); else if (status === 401) console.error(`🛑 [401] ${context}: Invalid API Key.`); else if (status === 400) console.error(`⚠️ [400] ${context}: Bad request / invalid payload.`); else if (status >= 500) console.error(`🔥 [${status}] ${context}: Provider backend down.`); else if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT') console.error(`⏳ [TIMEOUT] ${context}: Connection dropped.`); else console.error(`❌ [ERROR] ${context}:`, error.message || error); }; // --- TEXT COMPLETION --- // forceJson: true → response_format json_object (structured calls, e.g. aura scan) // forceJson: false → plain text (e.g. ReplyGenius chat replies) export const generateCompletion = async ({ model, prompt, system_prompt, images, forceJson = true }) => { try { const targetModel = getModelId(model); let messagesPayload = [ { role: "system", content: system_prompt || "You are an elite AI assistant." } ]; if (images?.length > 0) { let userContent = [{ type: "text", text: prompt }]; images.forEach((imgStr) => { if (imgStr.length > 7000000) throw new Error("Image too large. Max ~4MB."); const formattedImg = imgStr.startsWith('data:image') ? imgStr : `data:image/jpeg;base64,${imgStr}`; userContent.push({ type: "image_url", image_url: { url: formattedImg } }); }); messagesPayload.push({ role: "user", content: userContent }); } else { messagesPayload.push({ role: "user", content: prompt }); } const requestParams = { model: targetModel, messages: messagesPayload, max_tokens: 1200, }; // ✅ Only enforce JSON schema when explicitly requested. // Sending response_format with a plain-text prompt causes 400 on most providers. if (forceJson) { requestParams.response_format = { type: "json_object" }; } const response = await openRouterAI.chat.completions.create(requestParams); return { success: true, data: response.choices[0].message.content, usage: response.usage, model_used: targetModel }; } catch (error) { logOpenRouterError(error, "generateCompletion"); return { success: false, error: error.message, status: error.status || 500 }; } }; // --- STREAMING TEXT COMPLETION --- export const streamCompletion = async ({ model, prompt, system_prompt, images, res }) => { try { const targetModel = getModelId(model); let messagesPayload = [ { role: "system", content: system_prompt || "You are an elite AI assistant." } ]; if (images?.length > 0) { let userContent = [{ type: "text", text: prompt }]; images.forEach((imgStr) => { if (imgStr.length > 7000000) throw new Error("Image too large. Max ~4MB."); const formattedImg = imgStr.startsWith('data:image') ? imgStr : `data:image/jpeg;base64,${imgStr}`; userContent.push({ type: "image_url", image_url: { url: formattedImg } }); }); messagesPayload.push({ role: "user", content: userContent }); } else { messagesPayload.push({ role: "user", content: prompt }); } const stream = await openRouterAI.chat.completions.create({ model: targetModel, messages: messagesPayload, stream: true, stream_options: { include_usage: true }, max_tokens: 2000, }); let totalTokenCount = 0; for await (const chunk of stream) { const delta = chunk.choices[0]?.delta; if (delta?.reasoning_content) res.write(`__THINK__${delta.reasoning_content}`); else if (delta?.content) res.write(delta.content); if (chunk.usage) totalTokenCount = chunk.usage.total_tokens; } res.write(`__USAGE__${JSON.stringify({ totalTokenCount })}`); res.end(); } catch (error) { logOpenRouterError(error, "streamCompletion"); res.write(`\n\n[ERROR]: ${error.message}`); res.end(); } }; // --- IMAGE GENERATION --- export const generateImage = async ({ prompt, tier = "quick" }) => { try { const model = getImageModelId(tier); const response = await fetch("https://openrouter.ai/api/v1/images/generations", { method: "POST", headers: { "Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`, "Content-Type": "application/json", "HTTP-Referer": process.env.YOUR_SITE_URL || "https://everydaycats.web.app", "X-Title": process.env.YOUR_SITE_NAME || "lightnovel.ai", }, body: JSON.stringify({ model, prompt, n: 1, size: "1024x1024" }) }); if (!response.ok) { const err = await response.json(); throw Object.assign(new Error(err?.error?.message || "Image gen failed"), { status: response.status }); } const data = await response.json(); const imageUrl = data?.data?.[0]?.url; if (!imageUrl) throw new Error("No image URL returned."); return { success: true, url: imageUrl, model_used: model }; } catch (error) { logOpenRouterError(error, "generateImage"); return { success: false, error: error.message, status: error.status || 500 }; } };