ai_plugin_server / src /providers /openrouter.js
everydaycats's picture
Upload openrouter.js
f22a059 verified
Raw
History Blame Contribute Delete
2.77 kB
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;
}