openzoo 0.48.46 → 0.48.47

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.
Files changed (2) hide show
  1. package/lib/proxy.js +45 -4
  2. package/package.json +1 -1
package/lib/proxy.js CHANGED
@@ -66,20 +66,43 @@ async function readBody(req) {
66
66
  return Buffer.concat(chunks);
67
67
  }
68
68
 
69
- /** Pipe an upstream fetch Response to the client, unbuffered (SSE-safe). */
70
- function relay(res, upstream) {
69
+ /** Pipe an upstream fetch Response to the client, unbuffered (SSE-safe).
70
+ *
71
+ * `onReceipt` is called with the gateway's x402 block when it arrives. On a
72
+ * STREAMED call there is no JSON body to carry that block, so the gateway
73
+ * emits it as an SSE COMMENT (`: x402 {...}`) after the last frame — comments
74
+ * are discarded by every compliant client, so nothing downstream sees it, but
75
+ * without reading it here every spend and savings figure on the status line
76
+ * would silently read zero the moment real streaming was switched on.
77
+ *
78
+ * Sniffing NEVER delays a byte: each chunk is written to the client first and
79
+ * only then scanned. */
80
+ function relay(res, upstream, onReceipt) {
71
81
  const headers = {};
72
82
  upstream.headers.forEach((v, k) => {
73
83
  if (!['transfer-encoding', 'connection', 'content-encoding', 'content-length'].includes(k)) headers[k] = v;
74
84
  });
75
85
  res.writeHead(upstream.status, headers);
76
86
  if (!upstream.body) { res.end(); return Promise.resolve(); }
87
+ const sse = (upstream.headers.get('content-type') || '').includes('text/event-stream');
77
88
  return new Promise((resolve) => {
78
89
  const body = Readable.fromWeb(upstream.body);
79
90
  body.on('error', () => res.destroy());
80
91
  res.on('close', () => body.destroy());
81
92
  body.on('end', resolve);
82
- body.pipe(res);
93
+ if (!sse || typeof onReceipt !== 'function') { body.pipe(res); return; }
94
+ let pending = '';
95
+ body.on('data', (c) => {
96
+ res.write(c);
97
+ pending += c.toString('utf8');
98
+ const lines = pending.split('\n');
99
+ pending = lines.pop() ?? ''; // a comment can straddle two chunks
100
+ for (const line of lines) {
101
+ if (!line.startsWith(': x402 ')) continue;
102
+ try { onReceipt(JSON.parse(line.slice(7))); } catch { /* not our frame */ }
103
+ }
104
+ });
105
+ body.on('end', () => res.end());
83
106
  });
84
107
  }
85
108
 
@@ -1197,7 +1220,25 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
1197
1220
  return;
1198
1221
  }
1199
1222
  }
1200
- await relay(res, response);
1223
+ // A STREAMED call is metered from the gateway's trailing SSE comment —
1224
+ // same figures the JSON path reads out of `data.x402`, same counters, so
1225
+ // the status line does not care which transport served the answer.
1226
+ await relay(res, response, (x) => {
1227
+ if (paid || typeof x?.billedUsd !== 'number') return;
1228
+ sessionSpent += x.billedUsd;
1229
+ sessionCogs += typeof x.cogsUsd === 'number' ? x.cogsUsd : x.billedUsd / MARKUP;
1230
+ sessionDirect += typeof x.directUsd === 'number' ? x.directUsd : x.billedUsd;
1231
+ if (typeof x.actualUsd === 'number' && x.actualUsd >= 0) { sessionActual += x.actualUsd; actualCalls += 1; }
1232
+ if (didSpill) {
1233
+ const lc = x.lecore || {};
1234
+ log(`spill priced (streamed): ${x.pricing} · basis ${x.counterfactualTokensUsed ?? '?'} tok vs sent ${lc.tokensBefore ?? '?'} -> ${lc.tokensAfter ?? '?'} · billed ${(x.billedUsd ?? 0).toFixed(5)} direct ${(x.directUsd ?? 0).toFixed(5)}`);
1235
+ spillSpend += x.billedUsd;
1236
+ spillDirect += typeof x.directUsd === 'number' ? x.directUsd : x.billedUsd;
1237
+ }
1238
+ paidCalls += 1;
1239
+ if (viaTunnel) tunnelSpent += x.billedUsd;
1240
+ say(`credit -> $${x.billedUsd.toFixed(6)} · session $${sessionSpent.toFixed(6)}`);
1241
+ });
1201
1242
  } catch (err) {
1202
1243
  if (err instanceof QuoteTooHighError) {
1203
1244
  log(err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.46",
3
+ "version": "0.48.47",
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",