openzoo 0.48.32 → 0.48.34
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/proxy.js +46 -2
- package/package.json +1 -1
package/lib/proxy.js
CHANGED
|
@@ -300,9 +300,31 @@ async function spillTranscript(body, log) {
|
|
|
300
300
|
const firstSpillable = msgs.findIndex((m) => m?.role !== 'system');
|
|
301
301
|
if (firstSpillable < 0) return null;
|
|
302
302
|
|
|
303
|
+
// A SEVERABLE BOUNDARY IS NOT ONLY A `user` TURN.
|
|
304
|
+
//
|
|
305
|
+
// That was the whole bug: in a Claude Code agent loop the human speaks ONCE
|
|
306
|
+
// and everything after is assistant->tool pairs, so a rule that only cuts at
|
|
307
|
+
// role:"user" finds nothing and the spill returns null on EVERY turn.
|
|
308
|
+
// MEASURED against the live proxy log: 104 real claude-cli requests, zero
|
|
309
|
+
// spills, while every synthetic transcript I tested spilled fine because I
|
|
310
|
+
// had written user turns into it.
|
|
311
|
+
//
|
|
312
|
+
// What actually matters is that no assistant `tool_calls` is left unanswered
|
|
313
|
+
// across the cut. So any message is a legal boundary when the one BEFORE it
|
|
314
|
+
// is not an unanswered tool call — i.e. the previous message is a plain
|
|
315
|
+
// assistant/user/tool with every call already resolved.
|
|
316
|
+
const severable = (i) => {
|
|
317
|
+
if (i <= firstSpillable || i >= msgs.length) return false;
|
|
318
|
+
const prev = msgs[i - 1];
|
|
319
|
+
if (!prev) return false;
|
|
320
|
+
// an assistant that issued tool_calls must be followed by its tool results
|
|
321
|
+
if (prev.role === 'assistant' && Array.isArray(prev.tool_calls) && prev.tool_calls.length) return false;
|
|
322
|
+
// never split a tool_call/tool_result run in the middle
|
|
323
|
+
return msgs[i].role !== 'tool';
|
|
324
|
+
};
|
|
303
325
|
let cut = -1;
|
|
304
326
|
for (let i = msgs.length - keepTail; i > firstSpillable; i--) {
|
|
305
|
-
if (
|
|
327
|
+
if (severable(i)) { cut = i; break; }
|
|
306
328
|
}
|
|
307
329
|
// FALL BACK TO THE LAST SEVERABLE TURN. The keepTail window is a preference,
|
|
308
330
|
// not a requirement: a transcript can be enormous and still have very few
|
|
@@ -312,11 +334,33 @@ async function spillTranscript(body, log) {
|
|
|
312
334
|
// final turn; anything earlier that is severable is better than not spilling.
|
|
313
335
|
if (cut <= firstSpillable) {
|
|
314
336
|
for (let i = msgs.length - 2; i > firstSpillable; i--) {
|
|
315
|
-
if (
|
|
337
|
+
if (severable(i)) { cut = i; break; }
|
|
316
338
|
}
|
|
317
339
|
}
|
|
318
340
|
if (cut <= firstSpillable) return null; // nothing safely severable
|
|
319
341
|
|
|
342
|
+
// TRIM THE TAIL BY BYTES, NOT MESSAGE COUNT.
|
|
343
|
+
//
|
|
344
|
+
// MEASURED on the live session: 9 kept turns of Claude Code tool output made
|
|
345
|
+
// promptTokens swamp the counterfactual and the call scored 1.00x, while the
|
|
346
|
+
// SAME bound context with a one-line ask scored 8.53x. Nine messages is a
|
|
347
|
+
// trivial number and an enormous payload — a single Read or grep result is
|
|
348
|
+
// tens of KB — so counting messages measures the wrong thing entirely.
|
|
349
|
+
//
|
|
350
|
+
// Walk backwards from the newest and stop at a byte budget. The newest turns
|
|
351
|
+
// are the ones the model actually needs verbatim; everything older is already
|
|
352
|
+
// in the bound corpus and comes back through recall.
|
|
353
|
+
let tailStart = cut;
|
|
354
|
+
{
|
|
355
|
+
const budget = Number(process.env.OPENZOO_TAIL_MAX_CHARS || 24000);
|
|
356
|
+
let used = 0;
|
|
357
|
+
for (let i = msgs.length - 1; i >= cut; i--) {
|
|
358
|
+
used += msgText(msgs[i]).length;
|
|
359
|
+
if (used > budget && severable(i)) { tailStart = i; break; }
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (tailStart > cut) cut = tailStart;
|
|
363
|
+
|
|
320
364
|
const head = msgs.slice(0, firstSpillable); // system block, always kept
|
|
321
365
|
const corpus = msgs.slice(firstSpillable, cut).map(msgText).filter(Boolean).join('\n\n');
|
|
322
366
|
if (corpus.length <= BIND_MIN_CHARS) return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.34",
|
|
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",
|