Spaces:
Runtime error
Runtime error
| import { config, ROUGH_PRICE_PER_1K } from '../config.js'; | |
| // A process-wide spend counter. Good enough for a single-machine fleet; | |
| // swap for a shared store (redis/file) if you run true multi-process. | |
| let spentUsd = 0; | |
| function estimateCost(model, promptTokensApprox, completionTokensApprox) { | |
| const rate = ROUGH_PRICE_PER_1K[model] ?? ROUGH_PRICE_PER_1K.default; | |
| const totalTokens = promptTokensApprox + completionTokensApprox; | |
| return (totalTokens / 1000) * rate; | |
| } | |
| function approxTokens(text) { | |
| // ~4 chars per token is a fine rough estimate for budgeting purposes | |
| return Math.ceil((text || '').length / 4); | |
| } | |
| /** | |
| * Calls a model through OpenRouter's OpenAI-compatible chat completions API. | |
| * @param {string} model - OpenRouter model slug, e.g. "google/gemini-flash-1.5" | |
| * @param {string} systemPrompt | |
| * @param {string} userPrompt | |
| * @param {object} opts - { temperature, maxTokens, jsonMode } | |
| */ | |
| export async function callModel(model, systemPrompt, userPrompt, opts = {}) { | |
| const { temperature = 0.9, maxTokens = 800, jsonMode = false } = opts; | |
| const promptApprox = approxTokens(systemPrompt) + approxTokens(userPrompt); | |
| const projectedCost = estimateCost(model, promptApprox, maxTokens); | |
| if (spentUsd + projectedCost > config.maxSpendUsd) { | |
| throw new Error( | |
| `Spend guard tripped: estimated $${(spentUsd + projectedCost).toFixed(4)} ` + | |
| `would exceed MAX_SPEND_USD=$${config.maxSpendUsd}. Stopping before the call.` | |
| ); | |
| } | |
| if (!config.openRouterKey) { | |
| throw new Error('OPENROUTER_API_KEY is not set. Copy .env.example to .env and fill it in.'); | |
| } | |
| const body = { | |
| model, | |
| temperature, | |
| max_tokens: maxTokens, | |
| messages: [ | |
| { role: 'system', content: systemPrompt }, | |
| { role: 'user', content: userPrompt }, | |
| ], | |
| }; | |
| if (jsonMode) { | |
| body.response_format = { type: 'json_object' }; | |
| } | |
| const res = await fetch('https://openrouter.ai/api/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${config.openRouterKey}`, | |
| }, | |
| body: JSON.stringify(body), | |
| }); | |
| if (!res.ok) { | |
| const errText = await res.text(); | |
| throw new Error(`OpenRouter error ${res.status}: ${errText}`); | |
| } | |
| const data = await res.json(); | |
| const content = data?.choices?.[0]?.message?.content ?? ''; | |
| // Update running spend with actual usage if OpenRouter returns it, else fall back to estimate | |
| const usage = data?.usage; | |
| const actualCost = usage | |
| ? estimateCost(model, usage.prompt_tokens ?? promptApprox, usage.completion_tokens ?? maxTokens) | |
| : projectedCost; | |
| spentUsd += actualCost; | |
| return { content, spentUsd, callCostUsd: actualCost }; | |
| } | |
| export function getSpentUsd() { | |
| return spentUsd; | |
| } | |