everydaycats commited on
Commit
f22a059
·
verified ·
1 Parent(s): df9374f

Upload openrouter.js

Browse files
Files changed (1) hide show
  1. 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
+ }