openzoo 0.48.27 → 0.48.28

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/lib/launch.js CHANGED
@@ -164,7 +164,13 @@ export async function launchClaude(argv) {
164
164
  + `${JSON.stringify(process.execPath)} -e `
165
165
  + '\'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{'
166
166
  + 'try{const j=JSON.parse(s);'
167
- + 'process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo $"+(Number(j.spendUsd)||0).toFixed(4)+" "+(j.paidCalls||0)+" call"+((j.paidCalls||0)===1?"":"s")+" \\u00b7 x402")}'
167
+ // Spend alone is the COST with none of the benefit. The spill figure is
168
+ // the context that did NOT ride upstream — the thing being paid for.
169
+ + 'const sp=j.spilled||{};'
170
+ + 'const tk=Number(sp.tokensApprox)||0;'
171
+ + 'const ht=tk>=1e6?(tk/1e6).toFixed(1)+"M":tk>=1e3?Math.round(tk/1e3)+"k":String(tk);'
172
+ + 'const spill=tk?(" \\u00b7 "+ht+" tok offloaded"):"";'
173
+ + 'process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo $"+(Number(j.spendUsd)||0).toFixed(4)+" "+(j.paidCalls||0)+" call"+((j.paidCalls||0)===1?"":"s")+spill+" \\u00b7 x402")}'
168
174
  + 'catch{process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo \\u00b7 x402")}})\'\n');
169
175
  fs.chmodSync(scriptPath, 0o755);
170
176
  const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
@@ -172,12 +178,30 @@ export async function launchClaude(argv) {
172
178
  try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) || {}; } catch { settings = {}; }
173
179
  const prev = settings.statusLine;
174
180
  settings.statusLine = { type: 'command', command: `sh ${scriptPath}` };
181
+ // AUTO-COMPACT OFF — and only correct BECAUSE the proxy spills.
182
+ //
183
+ // Claude Code counts the WHOLE local transcript and compacts on its own
184
+ // ceiling; it cannot know the proxy binds the old prefix into leCore and
185
+ // forwards only the system block plus the recent tail. So it was
186
+ // destroying history to stay under a limit the upstream request never
187
+ // approached — on a product whose pitch is that it does not have to.
188
+ //
189
+ // This would have been RECKLESS before spillTranscript existed: with the
190
+ // full body going upstream, disabling compaction just moves the failure
191
+ // from a lossy summary to a hard context error. It is safe now precisely
192
+ // because OPENZOO_KEEP_TAIL_MSGS bounds what is actually sent.
193
+ // OPENZOO_KEEP_COMPACT=1 restores stock behaviour.
194
+ const prevCompact = settings.autoCompactEnabled;
195
+ if (process.env.OPENZOO_KEEP_COMPACT !== '1') settings.autoCompactEnabled = false;
175
196
  fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
176
197
  fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
177
198
  restoreStatus = () => {
178
199
  try {
179
200
  const cur = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
180
201
  if (prev === undefined) delete cur.statusLine; else cur.statusLine = prev;
202
+ // Never leave a global setting mutated after we exit.
203
+ if (prevCompact === undefined) delete cur.autoCompactEnabled;
204
+ else cur.autoCompactEnabled = prevCompact;
181
205
  fs.writeFileSync(settingsPath, `${JSON.stringify(cur, null, 2)}\n`);
182
206
  } catch { /* leave as-is */ }
183
207
  };
package/lib/proxy.js CHANGED
@@ -304,6 +304,17 @@ async function spillTranscript(body, log) {
304
304
  for (let i = msgs.length - keepTail; i > firstSpillable; i--) {
305
305
  if (msgs[i]?.role === 'user') { cut = i; break; }
306
306
  }
307
+ // FALL BACK TO THE LAST SEVERABLE TURN. The keepTail window is a preference,
308
+ // not a requirement: a transcript can be enormous and still have very few
309
+ // user turns (one huge document, then tool traffic), and on those the window
310
+ // contained no `user` message at all — so nothing spilled and the whole body
311
+ // went upstream while the counter honestly reported 0. Keep at least the
312
+ // final turn; anything earlier that is severable is better than not spilling.
313
+ if (cut <= firstSpillable) {
314
+ for (let i = msgs.length - 2; i > firstSpillable; i--) {
315
+ if (msgs[i]?.role === 'user') { cut = i; break; }
316
+ }
317
+ }
307
318
  if (cut <= firstSpillable) return null; // nothing safely severable
308
319
 
309
320
  const head = msgs.slice(0, firstSpillable); // system block, always kept
@@ -430,6 +441,12 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
430
441
  // "is the editor really routing through us?" — an editor that silently keeps
431
442
  // using its own backend leaves this at 0 while looking perfectly healthy.
432
443
  let servedRequests = 0;
444
+ // SPILL ACCOUNTING. The product's whole claim is that context is offloaded
445
+ // instead of re-sent, and nothing measured it — the status line showed spend
446
+ // and call count, which is the cost side with none of the benefit.
447
+ let spillCalls = 0;
448
+ let spilledChars = 0;
449
+ let spillReuses = 0;
433
450
  let tunnelError = null;
434
451
 
435
452
  const server = http.createServer(async (req, res) => {
@@ -533,6 +550,15 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
533
550
  reachedVia: viaTunnel ? 'public tunnel' : 'localhost',
534
551
  publicTunnel: tunnelGate?.publicUrl ? `${tunnelGate.publicUrl}/v1` : null,
535
552
  servedRequests,
553
+ spilled: {
554
+ calls: spillCalls,
555
+ chars: spilledChars,
556
+ // ~4 chars/token is the usual rough rule; this is the context that
557
+ // did NOT ride upstream on those calls, which is the number the
558
+ // saving is actually made of.
559
+ tokensApprox: Math.round(spilledChars / 4),
560
+ reusedBinds: spillReuses,
561
+ },
536
562
  spendUsd: sessionSpent,
537
563
  paidCalls,
538
564
  mcp: `${self.replace(/\/v1$/, '')}/mcp`,
@@ -798,6 +824,9 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
798
824
  });
799
825
  let result;
800
826
  if (cached) {
827
+ spillCalls += 1;
828
+ spilledChars += cached.corpus?.length || 0;
829
+ if (cached.reused) spillReuses += 1;
801
830
  result = await send(cached.body, cached.contextId);
802
831
  // Sidecar wiped between runs: the gateway 404s BEFORE the 402 (nothing
803
832
  // paid). Never fail on a stale manifest — re-bind once and retry.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.27",
3
+ "version": "0.48.28",
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",