Spaces:
Runtime error
Runtime error
Upload openrouter.js
Browse files- src/providers/openrouter.js +84 -0
src/providers/openrouter.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { config, ROUGH_PRICE_PER_1K } from '../config.js';
|
| 2 |
+
|
| 3 |
+
// A process-wide spend counter. Good enough for a single-machine fleet;
|
| 4 |
+
// swap for a shared store (redis/file) if you run true multi-process.
|
| 5 |
+
let spentUsd = 0;
|
| 6 |
+
|
| 7 |
+
function estimateCost(model, promptTokensApprox, completionTokensApprox) {
|
| 8 |
+
const rate = ROUGH_PRICE_PER_1K[model] ?? ROUGH_PRICE_PER_1K.default;
|
| 9 |
+
const totalTokens = promptTokensApprox + completionTokensApprox;
|
| 10 |
+
return (totalTokens / 1000) * rate;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
function approxTokens(text) {
|
| 14 |
+
// ~4 chars per token is a fine rough estimate for budgeting purposes
|
| 15 |
+
return Math.ceil((text || '').length / 4);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Calls a model through OpenRouter's OpenAI-compatible chat completions API.
|
| 20 |
+
* @param {string} model - OpenRouter model slug, e.g. "google/gemini-flash-1.5"
|
| 21 |
+
* @param {string} systemPrompt
|
| 22 |
+
* @param {string} userPrompt
|
| 23 |
+
* @param {object} opts - { temperature, maxTokens, jsonMode }
|
| 24 |
+
*/
|
| 25 |
+
export async function callModel(model, systemPrompt, userPrompt, opts = {}) {
|
| 26 |
+
const { temperature = 0.9, maxTokens = 800, jsonMode = false } = opts;
|
| 27 |
+
|
| 28 |
+
const promptApprox = approxTokens(systemPrompt) + approxTokens(userPrompt);
|
| 29 |
+
const projectedCost = estimateCost(model, promptApprox, maxTokens);
|
| 30 |
+
|
| 31 |
+
if (spentUsd + projectedCost > config.maxSpendUsd) {
|
| 32 |
+
throw new Error(
|
| 33 |
+
`Spend guard tripped: estimated $${(spentUsd + projectedCost).toFixed(4)} ` +
|
| 34 |
+
`would exceed MAX_SPEND_USD=$${config.maxSpendUsd}. Stopping before the call.`
|
| 35 |
+
);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
if (!config.openRouterKey) {
|
| 39 |
+
throw new Error('OPENROUTER_API_KEY is not set. Copy .env.example to .env and fill it in.');
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
const body = {
|
| 43 |
+
model,
|
| 44 |
+
temperature,
|
| 45 |
+
max_tokens: maxTokens,
|
| 46 |
+
messages: [
|
| 47 |
+
{ role: 'system', content: systemPrompt },
|
| 48 |
+
{ role: 'user', content: userPrompt },
|
| 49 |
+
],
|
| 50 |
+
};
|
| 51 |
+
if (jsonMode) {
|
| 52 |
+
body.response_format = { type: 'json_object' };
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
| 56 |
+
method: 'POST',
|
| 57 |
+
headers: {
|
| 58 |
+
'Content-Type': 'application/json',
|
| 59 |
+
Authorization: `Bearer ${config.openRouterKey}`,
|
| 60 |
+
},
|
| 61 |
+
body: JSON.stringify(body),
|
| 62 |
+
});
|
| 63 |
+
|
| 64 |
+
if (!res.ok) {
|
| 65 |
+
const errText = await res.text();
|
| 66 |
+
throw new Error(`OpenRouter error ${res.status}: ${errText}`);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
const data = await res.json();
|
| 70 |
+
const content = data?.choices?.[0]?.message?.content ?? '';
|
| 71 |
+
|
| 72 |
+
// Update running spend with actual usage if OpenRouter returns it, else fall back to estimate
|
| 73 |
+
const usage = data?.usage;
|
| 74 |
+
const actualCost = usage
|
| 75 |
+
? estimateCost(model, usage.prompt_tokens ?? promptApprox, usage.completion_tokens ?? maxTokens)
|
| 76 |
+
: projectedCost;
|
| 77 |
+
spentUsd += actualCost;
|
| 78 |
+
|
| 79 |
+
return { content, spentUsd, callCostUsd: actualCost };
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
export function getSpentUsd() {
|
| 83 |
+
return spentUsd;
|
| 84 |
+
}
|