openzoo 0.51.10 → 0.51.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/launcher/savings-renderer.js +3 -2
- package/lib/billing.js +7 -4
- package/lib/proxy.js +15 -6
- package/lib/receipt-ledger.js +22 -0
- package/lib/session.js +4 -0
- package/package.json +1 -1
|
@@ -2,8 +2,9 @@ const usd = n => typeof n === 'number' && Number.isFinite(n) ? '$' + Math.abs(n)
|
|
|
2
2
|
window.savings.listen(d => {
|
|
3
3
|
for (const [id, value] of [['wallet',d.walletUsd],['credit',d.creditUsd],['spent',d.billing?.calls > 0 ? d.billing.chargedUsd : null],['direct',d.comparison?.calls > 0 ? d.comparison.directUsd : null]]) document.getElementById(id).textContent=usd(value);
|
|
4
4
|
const saved=d.comparison?.calls > 0 ? d.comparison.directUsd-d.comparison.spentUsd : null;
|
|
5
|
-
|
|
5
|
+
const pct=d.comparison?.directUsd > 0 && saved !== null ? Math.abs(saved)/d.comparison.directUsd*100 : null;
|
|
6
|
+
document.getElementById('saved').textContent=usd(saved)+(pct===null?'':` (${pct.toFixed(1)}%)`);
|
|
6
7
|
document.getElementById('saving-label').textContent=saved!==null && saved<0 ? 'Extra cost' : 'Saved';
|
|
7
|
-
document.getElementById('state').textContent=d.online ? (d.billing?.calls > 0 ? 'Final charges ·
|
|
8
|
+
document.getElementById('state').textContent=d.online ? (d.billing?.calls > 0 ? 'Final charges · saved receipts' : 'Waiting for final billing · balance is live') : 'Reconnecting · showing last known amounts';
|
|
8
9
|
});
|
|
9
10
|
document.getElementById('close').addEventListener('click',()=>window.savings.close());
|
package/lib/billing.js
CHANGED
|
@@ -2,10 +2,13 @@ const money = n => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : n
|
|
|
2
2
|
|
|
3
3
|
// Only final, explicitly reported charges. Never substitute upstream cost or a quote.
|
|
4
4
|
export function finalBilling(x = {}, usage = {}) {
|
|
5
|
+
const receipt = x._completedResponse === true || x.settle?.success === true;
|
|
5
6
|
const charged = [usage.billedUsd, x.billedActualUsd, x.settledBilledUsd, x.chargedUsd,
|
|
6
|
-
x.settledUsd,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
x.settledUsd, receipt ? x.billedUsd : null,
|
|
8
|
+
usage.cost_details?.upstream_inference_cost != null ? usage.cost : null].map(money).find(n => n !== null) ?? null;
|
|
9
|
+
const direct = receipt ? money(x.directUsd) : null;
|
|
10
|
+
const upstream = money(usage.cost_details?.upstream_inference_cost) ?? (receipt ? money(x.actualUsd) : null);
|
|
11
|
+
return { charged, direct, upstream };
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
// Observe billing without retaining prompts, output text, or changing the stream.
|
|
@@ -21,7 +24,7 @@ export function billingObserver(onReceipt) {
|
|
|
21
24
|
else if (line.startsWith('data:')) {
|
|
22
25
|
const event = JSON.parse(line.slice(5).trim());
|
|
23
26
|
const value = event.response || event;
|
|
24
|
-
if (value.usage || value.x402) onReceipt({ ...value.x402, usage: value.usage });
|
|
27
|
+
if (value.usage || value.x402) onReceipt({ ...value.x402, usage: value.usage, _responseId: value.id, _completedResponse: event.type === 'response.completed' || value.status === 'completed' });
|
|
25
28
|
}
|
|
26
29
|
} catch { /* Not a billing event. */ }
|
|
27
30
|
}
|
package/lib/proxy.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { receiptLedger } from './receipt-ledger.js';
|
|
1
2
|
import { billingObserver, finalBilling } from './billing.js';
|
|
2
3
|
import { liveBalance } from './funding-status.js';
|
|
3
4
|
import { paymentError } from './payment-error.js';
|
|
@@ -35,7 +36,7 @@ async function catalogIdsCached(modelsUrl, headers) {
|
|
|
35
36
|
return ids;
|
|
36
37
|
}
|
|
37
38
|
import { withNamespace } from './namespace.js';
|
|
38
|
-
import { loadSessionSpend, saveSessionSpend } from './session.js';
|
|
39
|
+
import { loadSessionSpend, saveSessionSpend, sessionSpendFile } from './session.js';
|
|
39
40
|
import { creditBalance, quotedPrices } from './info.js';
|
|
40
41
|
import { priceHoldings } from './livestatus.js';
|
|
41
42
|
import { receiptUsedCogs, receiptDirectUsd, pairActualBilled } from './racesettle.js';
|
|
@@ -424,10 +425,16 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
424
425
|
let lastQuoteUsd = null;
|
|
425
426
|
let sessionCogs = restored.cogsUsd;
|
|
426
427
|
let sessionDirect = restored.directUsd;
|
|
427
|
-
let comparedSpent = 0, comparedDirect = 0, comparedCalls = 0;
|
|
428
|
-
let verifiedCharged = 0, verifiedCalls = 0;
|
|
428
|
+
let comparedSpent = restored.comparison?.spentUsd ?? 0, comparedDirect = restored.comparison?.directUsd ?? 0, comparedCalls = restored.comparison?.calls ?? 0;
|
|
429
|
+
let verifiedCharged = restored.billing?.chargedUsd ?? 0, verifiedCalls = restored.billing?.calls ?? 0;
|
|
430
|
+
const ledger = receiptLedger(sessionSpendFile() + '.receipts.json', { billing: restored.billing, comparison: restored.comparison });
|
|
431
|
+
const applyLedger = ({billing, comparison}) => {
|
|
432
|
+
verifiedCharged = billing.chargedUsd; verifiedCalls = billing.calls;
|
|
433
|
+
comparedSpent = comparison.spentUsd; comparedDirect = comparison.directUsd; comparedCalls = comparison.calls;
|
|
434
|
+
};
|
|
435
|
+
applyLedger(ledger.totals());
|
|
429
436
|
const rememberSpend = () => {
|
|
430
|
-
saveSessionSpend({ spentUsd: sessionSpent, cogsUsd: sessionCogs, directUsd: sessionDirect, paidCalls });
|
|
437
|
+
saveSessionSpend({ spentUsd: sessionSpent, cogsUsd: sessionCogs, directUsd: sessionDirect, paidCalls, billing: { chargedUsd: verifiedCharged, calls: verifiedCalls }, comparison: { spentUsd: comparedSpent, directUsd: comparedDirect, calls: comparedCalls } });
|
|
431
438
|
};
|
|
432
439
|
if (restored.ok && (sessionSpent > 0 || paidCalls > 0)) {
|
|
433
440
|
say(`session restored: $${sessionSpent.toFixed(6)} · ${paidCalls} paid call${paidCalls === 1 ? '' : 's'}`);
|
|
@@ -656,7 +663,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
656
663
|
creditUsd,
|
|
657
664
|
// WHAT THE SAME CALLS WOULD HAVE COST DIRECT. Spend on its own is a
|
|
658
665
|
// bill; spend beside the counterfactual is the product.
|
|
659
|
-
billing: { chargedUsd: verifiedCharged, calls: verifiedCalls, scope: "verified
|
|
666
|
+
billing: { chargedUsd: verifiedCharged, calls: verifiedCalls, scope: "saved verified receipts" },
|
|
660
667
|
comparison: { spentUsd: comparedSpent, directUsd: comparedDirect, calls: comparedCalls },
|
|
661
668
|
directUsd: sessionDirect,
|
|
662
669
|
savedUsd: Math.max(0, sessionDirect - sessionSpent),
|
|
@@ -964,6 +971,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
964
971
|
}
|
|
965
972
|
scheduleRefresh(4000); // settlement lands on-chain in a few seconds
|
|
966
973
|
}
|
|
974
|
+
const requestReceiptId = crypto.randomUUID();
|
|
967
975
|
let accounted = paid && receipt?.ok && typeof receipt.billedUsd === 'number' ? receipt.billedUsd : 0;
|
|
968
976
|
let counted = paid && receipt?.ok && typeof receipt.billedUsd === 'number';
|
|
969
977
|
let finalX = {}, finalUsage = {}, comparison = null, actualPair = null, verified = null;
|
|
@@ -989,6 +997,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
989
997
|
if (actualPair) { sessionActual -= actualPair.upstream; billedWithActual -= actualPair.charged; actualCalls--; }
|
|
990
998
|
actualPair = final.upstream === null ? null : final;
|
|
991
999
|
if (actualPair) { sessionActual += final.upstream; billedWithActual += final.charged; actualCalls++; }
|
|
1000
|
+
applyLedger(ledger.record(finalX.settle?.transaction || finalX._responseId || receipt?.tx || requestReceiptId, final));
|
|
992
1001
|
rememberSpend();
|
|
993
1002
|
log(`final billing: charged $${final.charged.toFixed(6)} · upstream ${final.upstream === null ? 'unreported' : '$' + final.upstream.toFixed(6)} · direct ${final.direct === null ? 'unreported' : '$' + final.direct.toFixed(6)}`);
|
|
994
1003
|
};
|
|
@@ -996,7 +1005,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
996
1005
|
if (isPaidPost && response.ok && upCt.includes('application/json')) {
|
|
997
1006
|
let data = null;
|
|
998
1007
|
try { data = await response.clone().json(); } catch { /* not JSON after all */ }
|
|
999
|
-
meterFinal({ ...data?.x402, usage: data?.usage });
|
|
1008
|
+
meterFinal({ ...data?.x402, usage: data?.usage, _responseId: data?.id, _completedResponse: response.ok });
|
|
1000
1009
|
if (data?.object === 'chat.completion') {
|
|
1001
1010
|
if (statsOff) {
|
|
1002
1011
|
// The gateway already dropped its half; drop ours, and any block an
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function receiptLedger(file, base = {}) {
|
|
4
|
+
let state = { base, receipts: {} };
|
|
5
|
+
try { const saved=JSON.parse(fs.readFileSync(file,'utf8')); if(saved.receipts && saved.base)state=saved; } catch {}
|
|
6
|
+
const totals = () => {
|
|
7
|
+
const billing={chargedUsd:state.base.billing?.chargedUsd || 0,calls:state.base.billing?.calls || 0};
|
|
8
|
+
const comparison={spentUsd:state.base.comparison?.spentUsd || 0,directUsd:state.base.comparison?.directUsd || 0,calls:state.base.comparison?.calls || 0};
|
|
9
|
+
for(const r of Object.values(state.receipts)) {
|
|
10
|
+
billing.chargedUsd+=r.charged;billing.calls++;
|
|
11
|
+
if(r.direct!==null){comparison.spentUsd+=r.charged;comparison.directUsd+=r.direct;comparison.calls++;}
|
|
12
|
+
}
|
|
13
|
+
return {billing,comparison};
|
|
14
|
+
};
|
|
15
|
+
return { totals, record(id, receipt) {
|
|
16
|
+
if(!id || typeof receipt.charged!=='number' || !Number.isFinite(receipt.charged) || receipt.charged<0) return totals();
|
|
17
|
+
state.receipts[id]={charged:receipt.charged,direct:typeof receipt.direct==='number'&&Number.isFinite(receipt.direct)&&receipt.direct>=0?receipt.direct:null};
|
|
18
|
+
fs.mkdirSync(path.dirname(file),{recursive:true});
|
|
19
|
+
const tmp=file+'.tmp';fs.writeFileSync(tmp,JSON.stringify(state),{mode:0o600});fs.renameSync(tmp,file);
|
|
20
|
+
return totals();
|
|
21
|
+
}};
|
|
22
|
+
}
|
package/lib/session.js
CHANGED
|
@@ -30,6 +30,8 @@ export function loadSessionSpend(file = sessionSpendFile()) {
|
|
|
30
30
|
try { data = JSON.parse(raw); }
|
|
31
31
|
catch { return { ...EMPTY, ok: false, reason: 'corrupt' }; }
|
|
32
32
|
return {
|
|
33
|
+
...(data.billing ? { billing: { chargedUsd: num(data.billing.chargedUsd), calls: Math.floor(num(data.billing.calls)) } } : {}),
|
|
34
|
+
...(data.comparison ? { comparison: { spentUsd: num(data.comparison.spentUsd), directUsd: num(data.comparison.directUsd), calls: Math.floor(num(data.comparison.calls)) } } : {}),
|
|
33
35
|
spentUsd: num(data.spentUsd),
|
|
34
36
|
cogsUsd: num(data.cogsUsd),
|
|
35
37
|
directUsd: num(data.directUsd),
|
|
@@ -40,6 +42,8 @@ export function loadSessionSpend(file = sessionSpendFile()) {
|
|
|
40
42
|
|
|
41
43
|
export function saveSessionSpend(stats, file = sessionSpendFile()) {
|
|
42
44
|
const payload = {
|
|
45
|
+
...(stats?.billing ? { billing: { chargedUsd: num(stats.billing.chargedUsd), calls: Math.floor(num(stats.billing.calls)) } } : {}),
|
|
46
|
+
...(stats?.comparison ? { comparison: { spentUsd: num(stats.comparison.spentUsd), directUsd: num(stats.comparison.directUsd), calls: Math.floor(num(stats.comparison.calls)) } } : {}),
|
|
43
47
|
spentUsd: num(stats?.spentUsd),
|
|
44
48
|
cogsUsd: num(stats?.cogsUsd),
|
|
45
49
|
directUsd: num(stats?.directUsd),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.51.
|
|
3
|
+
"version": "0.51.12",
|
|
4
4
|
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|