openzoo 0.48.78 → 0.48.80
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 +4 -1
- package/lib/spill.js +70 -23
- package/package.json +1 -1
package/lib/proxy.js
CHANGED
|
@@ -610,7 +610,10 @@ async function spillTranscript(body, log, req, stats) {
|
|
|
610
610
|
bindFilesInBackground('background', { appendTo: bind.contextId, asAppend: true });
|
|
611
611
|
spillMemo.set(anchor, { corpus, contextId: bind.contextId, hash: bind.hash });
|
|
612
612
|
if (spillMemo.size > 32) spillMemo.delete(spillMemo.keys().next().value);
|
|
613
|
-
|
|
613
|
+
// Count the tail that is actually forwarded after stub/trim � older
|
|
614
|
+
// continue-turn rounds after the ask may have been dropped, so
|
|
615
|
+
// msgs.length - cut would keep lastSend growing with the raw pile.
|
|
616
|
+
const sent = Math.max(0, stubbed.messages.length - cut);
|
|
614
617
|
// NAME THE KEY. A memo keyed on the wrong thing fails silently — it just
|
|
615
618
|
// re-binds forever and collides sessions — so the log says which key was used.
|
|
616
619
|
const keyKind = sessionId ? `sid ${String(sessionId).slice(0, 8)}` : 'content-anchor';
|
package/lib/spill.js
CHANGED
|
@@ -496,9 +496,10 @@ export function looksLikeFileView(command) {
|
|
|
496
496
|
export const FAT_TOOL_CHARS = 400;
|
|
497
497
|
|
|
498
498
|
/**
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
499
|
+
* Last-round bodies under this floor stay even after a follow-up ask
|
|
500
|
+
* (0.48.77: a 461-byte bound Read must remain visible). Older rounds use
|
|
501
|
+
* the 0.48.76 rules (bound / fat / over budget) whether or not they sit
|
|
502
|
+
* after the last user ask.
|
|
502
503
|
*/
|
|
503
504
|
export const KEEP_TOOL_CHARS = 4096;
|
|
504
505
|
|
|
@@ -524,20 +525,22 @@ function toolCallIds(m) {
|
|
|
524
525
|
}
|
|
525
526
|
|
|
526
527
|
/**
|
|
527
|
-
*
|
|
528
|
+
* Most recent assistant tool_calls / Anthropic tool_use in the forwarded
|
|
529
|
+
* tail, plus those ids. Not "everything after the last user ask".
|
|
528
530
|
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* bodies
|
|
531
|
+
* A long continue / ultracode turn parks the last user ask early and then
|
|
532
|
+
* stacks Read/Bash/WebSearch rounds after it. Only the latest batch is
|
|
533
|
+
* this turn's eyes — even a 461-byte Read, even a fat WebSearch. Older
|
|
534
|
+
* batches in that same tail stub/trim under the 0.48.76 rules. When the
|
|
535
|
+
* ask follows the last batch, KEEP_TOOL_CHARS still keeps that last
|
|
536
|
+
* round's small bodies (0.48.77) while fat completed rounds may stub.
|
|
535
537
|
*/
|
|
536
|
-
export function currentToolRound(msgs, { lastUser = -1 } = {}) {
|
|
538
|
+
export function currentToolRound(msgs, { lastUser = -1, fromIndex = 0 } = {}) {
|
|
537
539
|
let index = -1;
|
|
538
540
|
const ids = new Set();
|
|
539
541
|
if (!Array.isArray(msgs)) return { index, ids, inFlight: false };
|
|
540
|
-
|
|
542
|
+
const start = Math.max(0, Number(fromIndex) || 0);
|
|
543
|
+
for (let i = start; i < msgs.length; i++) {
|
|
541
544
|
const here = toolCallIds(msgs[i]);
|
|
542
545
|
if (!here.length) continue;
|
|
543
546
|
index = i;
|
|
@@ -638,12 +641,12 @@ function resolveBoundPath(raw, cwd, boundAbs) {
|
|
|
638
641
|
*
|
|
639
642
|
* `fromIndex` limits the rewrite to the forwarded tail so the spilled prefix
|
|
640
643
|
* that becomes the conversation corpus is unchanged. The last user ask is
|
|
641
|
-
* never rewritten. The current tool round
|
|
642
|
-
* / tool_use
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
644
|
+
* never rewritten. The current tool round is only the most recent assistant
|
|
645
|
+
* tool_calls / tool_use in that tail, plus tool results for those ids — not
|
|
646
|
+
* every Read/Bash/Search after the last user ask. In-flight latest bodies
|
|
647
|
+
* stay even when large. KEEP_TOOL_CHARS applies only to that last round so
|
|
648
|
+
* a 461-byte Read survives a follow-up ask. Older rounds after the same
|
|
649
|
+
* ask stub (bound / fat / over budget) and trim (keepTail) like 0.48.76.
|
|
647
650
|
*/
|
|
648
651
|
export function stubBoundFileResults(msgs, {
|
|
649
652
|
boundFiles,
|
|
@@ -718,7 +721,7 @@ export function stubBoundFileResults(msgs, {
|
|
|
718
721
|
const fatLimit = Number.isFinite(Number(fatChars)) ? Number(fatChars) : FAT_TOOL_CHARS;
|
|
719
722
|
const keepFloor = KEEP_TOOL_CHARS;
|
|
720
723
|
const slimArgs = aggressive || wantBudget;
|
|
721
|
-
let round = currentToolRound(msgs, { lastUser });
|
|
724
|
+
let round = currentToolRound(msgs, { lastUser, fromIndex });
|
|
722
725
|
|
|
723
726
|
const stubFor = (id, n) => {
|
|
724
727
|
const paths = idPaths.get(id);
|
|
@@ -727,10 +730,13 @@ export function stubBoundFileResults(msgs, {
|
|
|
727
730
|
|
|
728
731
|
const shouldStubBody = (id, n, { overBudget = false } = {}) => {
|
|
729
732
|
if (!n || isStubText(typeof n === 'number' ? '' : n)) return false;
|
|
733
|
+
// Latest in-flight batch (after the ask): never stub, even if fat.
|
|
730
734
|
if (id && round.inFlight && round.ids.has(id)) return false;
|
|
731
|
-
|
|
735
|
+
// Last completed batch (ask follows): keep small bodies only.
|
|
736
|
+
if (id && round.ids.has(id) && n < keepFloor) return false;
|
|
732
737
|
if (stubIds.has(id)) return true;
|
|
733
738
|
if (slimArgs && n >= fatLimit) return true;
|
|
739
|
+
if (overBudget && n > 0) return true;
|
|
734
740
|
return false;
|
|
735
741
|
};
|
|
736
742
|
|
|
@@ -738,16 +744,25 @@ export function stubBoundFileResults(msgs, {
|
|
|
738
744
|
let dropped = 0;
|
|
739
745
|
let messages = msgs;
|
|
740
746
|
|
|
741
|
-
// keepTail votes here because cutTranscript cannot move inside the chain
|
|
747
|
+
// keepTail votes here because cutTranscript cannot move inside the chain
|
|
748
|
+
// *or* past the last user ask. A continue turn stacks severable
|
|
749
|
+
// assistant+result rounds after that ask; trim those older rounds first
|
|
750
|
+
// so lastSend cannot grow to hundreds of fully-billed messages.
|
|
742
751
|
if (wantKeep) {
|
|
752
|
+
messages = trimRoundsAfterAsk(messages, {
|
|
753
|
+
fromIndex,
|
|
754
|
+
keepTail: Number(keepTail),
|
|
755
|
+
lastUser,
|
|
756
|
+
});
|
|
757
|
+
lastUser = lastUserAskIndex(messages, firstSpillableIndex(messages));
|
|
743
758
|
messages = trimUnseverablePairs(messages, {
|
|
744
759
|
fromIndex,
|
|
745
760
|
keepTail: Number(keepTail),
|
|
746
761
|
lastUser,
|
|
747
|
-
firstSpillable,
|
|
762
|
+
firstSpillable: firstSpillableIndex(messages),
|
|
748
763
|
});
|
|
749
764
|
lastUser = lastUserAskIndex(messages, firstSpillableIndex(messages));
|
|
750
|
-
round = currentToolRound(messages, { lastUser });
|
|
765
|
+
round = currentToolRound(messages, { lastUser, fromIndex });
|
|
751
766
|
}
|
|
752
767
|
|
|
753
768
|
const skipSlimCalls = (i) => round.inFlight && i === round.index;
|
|
@@ -862,6 +877,9 @@ export function stubBoundFileResults(msgs, {
|
|
|
862
877
|
messages = next;
|
|
863
878
|
used = sliceChars(messages, fromIndex);
|
|
864
879
|
if (used > cap) {
|
|
880
|
+
// Only the un-severable last chain can still be huge (one assistant
|
|
881
|
+
// with hundreds of calls). Do not drop already-stubbed older rounds
|
|
882
|
+
// after the ask — the overage is often the protected latest batch.
|
|
865
883
|
messages = trimUnseverablePairs(messages, {
|
|
866
884
|
fromIndex,
|
|
867
885
|
keepTail: 1,
|
|
@@ -1176,6 +1194,35 @@ function isSeverable(msgs, i, firstSpillable) {
|
|
|
1176
1194
|
return msgs[i].role !== 'tool';
|
|
1177
1195
|
}
|
|
1178
1196
|
|
|
1197
|
+
/**
|
|
1198
|
+
* cutTranscript pins `cut` at the last user ask, so every later tool round
|
|
1199
|
+
* on a continue / ultracode turn used to ride in the forwarded tail. Keep
|
|
1200
|
+
* the newest `keepTail` complete assistant(tool_calls)+results rounds after
|
|
1201
|
+
* that ask and drop the older ones. Pairing stays valid; the ask stays;
|
|
1202
|
+
* the latest round is never dropped.
|
|
1203
|
+
*/
|
|
1204
|
+
export function trimRoundsAfterAsk(msgs, {
|
|
1205
|
+
fromIndex = 0,
|
|
1206
|
+
keepTail = 1,
|
|
1207
|
+
lastUser = -1,
|
|
1208
|
+
} = {}) {
|
|
1209
|
+
if (!Array.isArray(msgs) || lastUser < 0) return msgs;
|
|
1210
|
+
const keep = Math.max(1, Math.round(Number(keepTail) || 1));
|
|
1211
|
+
const start = Math.max(fromIndex, lastUser + 1);
|
|
1212
|
+
const starts = [];
|
|
1213
|
+
for (let i = start; i < msgs.length; i++) {
|
|
1214
|
+
if (toolCallIds(msgs[i]).length) starts.push(i);
|
|
1215
|
+
}
|
|
1216
|
+
if (starts.length <= keep) return msgs;
|
|
1217
|
+
const keepFrom = starts[starts.length - keep];
|
|
1218
|
+
const out = [];
|
|
1219
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
1220
|
+
if (i > lastUser && i < keepFrom) continue;
|
|
1221
|
+
out.push(msgs[i]);
|
|
1222
|
+
}
|
|
1223
|
+
return out;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1179
1226
|
/**
|
|
1180
1227
|
* On an un-severable assistant(tool_calls)+results chain, keepTail cannot
|
|
1181
1228
|
* move `cut`. Drop older complete pairs (rewrite tool_calls, drop their
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.80",
|
|
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",
|