openzoo 0.48.76 → 0.48.78
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/spill.js +71 -7
- package/package.json +2 -2
package/lib/spill.js
CHANGED
|
@@ -495,6 +495,58 @@ export function looksLikeFileView(command) {
|
|
|
495
495
|
/** Tool result larger than this is "fat" — stub it in the forwarded tail. */
|
|
496
496
|
export const FAT_TOOL_CHARS = 400;
|
|
497
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Bound-file matching used to stub any size, including a 461-byte Read the
|
|
500
|
+
* model had just fetched. Stay under this floor unless we are over budget
|
|
501
|
+
* *and* the result is not this turn's current tool round.
|
|
502
|
+
*/
|
|
503
|
+
export const KEEP_TOOL_CHARS = 4096;
|
|
504
|
+
|
|
505
|
+
function toolCallIds(m) {
|
|
506
|
+
if (!m || typeof m !== 'object') return [];
|
|
507
|
+
const ids = [];
|
|
508
|
+
if (Array.isArray(m.tool_calls)) {
|
|
509
|
+
for (const c of m.tool_calls) {
|
|
510
|
+
const id = c?.id || c?.tool_call_id;
|
|
511
|
+
if (id) ids.push(id);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (m.function_call) {
|
|
515
|
+
const id = m.function_call.id || m.function_call.tool_call_id;
|
|
516
|
+
if (id) ids.push(id);
|
|
517
|
+
}
|
|
518
|
+
if (Array.isArray(m.content)) {
|
|
519
|
+
for (const b of m.content) {
|
|
520
|
+
if (b?.type === 'tool_use' && b.id) ids.push(b.id);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return ids;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Last assistant that issued tool_calls / tool_use, plus those ids.
|
|
528
|
+
*
|
|
529
|
+
* Claude Code / OpenAI put *this turn's* Read/Bash/WebSearch results in
|
|
530
|
+
* `role:tool` (or tool_result blocks) AFTER the last user ask. Those are
|
|
531
|
+
* in-flight — the model's current eyes — and must never be stubbed, even
|
|
532
|
+
* when large. A completed round (tools, then a follow-up ask) may still
|
|
533
|
+
* be stubbed; that is the 0.48.76 storm savings. The last round's small
|
|
534
|
+
* bodies still use KEEP_TOOL_CHARS so a 461-byte Read survives.
|
|
535
|
+
*/
|
|
536
|
+
export function currentToolRound(msgs, { lastUser = -1 } = {}) {
|
|
537
|
+
let index = -1;
|
|
538
|
+
const ids = new Set();
|
|
539
|
+
if (!Array.isArray(msgs)) return { index, ids, inFlight: false };
|
|
540
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
541
|
+
const here = toolCallIds(msgs[i]);
|
|
542
|
+
if (!here.length) continue;
|
|
543
|
+
index = i;
|
|
544
|
+
ids.clear();
|
|
545
|
+
for (const id of here) ids.add(id);
|
|
546
|
+
}
|
|
547
|
+
return { index, ids, inFlight: index >= 0 && index > lastUser };
|
|
548
|
+
}
|
|
549
|
+
|
|
498
550
|
export function fileBoundStub(paths, n) {
|
|
499
551
|
const list = [...new Set((paths || []).filter(Boolean))].join(' ');
|
|
500
552
|
const mark = Number.isFinite(n) && n > 0 ? `[bound, ${n} chars]` : '[bound]';
|
|
@@ -586,7 +638,12 @@ function resolveBoundPath(raw, cwd, boundAbs) {
|
|
|
586
638
|
*
|
|
587
639
|
* `fromIndex` limits the rewrite to the forwarded tail so the spilled prefix
|
|
588
640
|
* that becomes the conversation corpus is unchanged. The last user ask is
|
|
589
|
-
* never rewritten.
|
|
641
|
+
* never rewritten. The current tool round — last assistant with tool_calls
|
|
642
|
+
* / tool_use and every matching role:tool / tool_result — is never rewritten
|
|
643
|
+
* when that assistant sits after the last user ask (this turn's eyes).
|
|
644
|
+
* Bodies under KEEP_TOOL_CHARS on that last round also stay, even when the
|
|
645
|
+
* ask follows the results, so a 461-byte Read is not replaced with
|
|
646
|
+
* `[bound, 461 chars]`.
|
|
590
647
|
*/
|
|
591
648
|
export function stubBoundFileResults(msgs, {
|
|
592
649
|
boundFiles,
|
|
@@ -659,15 +716,19 @@ export function stubBoundFileResults(msgs, {
|
|
|
659
716
|
const firstSpillable = firstSpillableIndex(msgs);
|
|
660
717
|
let lastUser = lastUserAskIndex(msgs, firstSpillable);
|
|
661
718
|
const fatLimit = Number.isFinite(Number(fatChars)) ? Number(fatChars) : FAT_TOOL_CHARS;
|
|
719
|
+
const keepFloor = KEEP_TOOL_CHARS;
|
|
662
720
|
const slimArgs = aggressive || wantBudget;
|
|
721
|
+
let round = currentToolRound(msgs, { lastUser });
|
|
663
722
|
|
|
664
723
|
const stubFor = (id, n) => {
|
|
665
724
|
const paths = idPaths.get(id);
|
|
666
725
|
return paths?.length ? fileBoundStub(paths, n) : toolResultStub(n);
|
|
667
726
|
};
|
|
668
727
|
|
|
669
|
-
const shouldStubBody = (id, n) => {
|
|
728
|
+
const shouldStubBody = (id, n, { overBudget = false } = {}) => {
|
|
670
729
|
if (!n || isStubText(typeof n === 'number' ? '' : n)) return false;
|
|
730
|
+
if (id && round.inFlight && round.ids.has(id)) return false;
|
|
731
|
+
if (n < keepFloor && (!overBudget || (id && round.ids.has(id)))) return false;
|
|
671
732
|
if (stubIds.has(id)) return true;
|
|
672
733
|
if (slimArgs && n >= fatLimit) return true;
|
|
673
734
|
return false;
|
|
@@ -686,8 +747,11 @@ export function stubBoundFileResults(msgs, {
|
|
|
686
747
|
firstSpillable,
|
|
687
748
|
});
|
|
688
749
|
lastUser = lastUserAskIndex(messages, firstSpillableIndex(messages));
|
|
750
|
+
round = currentToolRound(messages, { lastUser });
|
|
689
751
|
}
|
|
690
752
|
|
|
753
|
+
const skipSlimCalls = (i) => round.inFlight && i === round.index;
|
|
754
|
+
|
|
691
755
|
messages = messages.map((m, i) => {
|
|
692
756
|
if (i < fromIndex || !m || i === lastUser) return m;
|
|
693
757
|
if (m.role === 'tool') {
|
|
@@ -698,7 +762,7 @@ export function stubBoundFileResults(msgs, {
|
|
|
698
762
|
return { ...m, content: stubFor(m.tool_call_id, n) };
|
|
699
763
|
}
|
|
700
764
|
let next = m;
|
|
701
|
-
if (Array.isArray(m.tool_calls) && slimArgs) {
|
|
765
|
+
if (Array.isArray(m.tool_calls) && slimArgs && !skipSlimCalls(i)) {
|
|
702
766
|
let changed = false;
|
|
703
767
|
const calls = m.tool_calls.map((tc) => {
|
|
704
768
|
const n = toolCallArgLength(tc);
|
|
@@ -711,7 +775,7 @@ export function stubBoundFileResults(msgs, {
|
|
|
711
775
|
});
|
|
712
776
|
if (changed) next = { ...next, tool_calls: calls };
|
|
713
777
|
}
|
|
714
|
-
if (next.function_call && slimArgs) {
|
|
778
|
+
if (next.function_call && slimArgs && !skipSlimCalls(i)) {
|
|
715
779
|
const n = toolCallArgLength(next.function_call);
|
|
716
780
|
const raw = next.function_call.arguments;
|
|
717
781
|
if (n >= fatLimit && !isStubText(typeof raw === 'string' ? raw : '')) {
|
|
@@ -751,7 +815,7 @@ export function stubBoundFileResults(msgs, {
|
|
|
751
815
|
if (m.role === 'tool') {
|
|
752
816
|
if (isStubText(m.content)) continue;
|
|
753
817
|
const n = toolContentLength(m.content);
|
|
754
|
-
if (!n) continue;
|
|
818
|
+
if (!shouldStubBody(m.tool_call_id, n, { overBudget: true })) continue;
|
|
755
819
|
const stub = stubFor(m.tool_call_id, n);
|
|
756
820
|
if (stub.length >= n) continue;
|
|
757
821
|
used = used - n + stub.length;
|
|
@@ -760,7 +824,7 @@ export function stubBoundFileResults(msgs, {
|
|
|
760
824
|
dropped += n;
|
|
761
825
|
continue;
|
|
762
826
|
}
|
|
763
|
-
if (Array.isArray(m.tool_calls)) {
|
|
827
|
+
if (Array.isArray(m.tool_calls) && !skipSlimCalls(i)) {
|
|
764
828
|
let changed = false;
|
|
765
829
|
const calls = m.tool_calls.map((tc) => {
|
|
766
830
|
if (used <= cap) return tc;
|
|
@@ -784,7 +848,7 @@ export function stubBoundFileResults(msgs, {
|
|
|
784
848
|
const blocks = cur.content.map((b) => {
|
|
785
849
|
if (b?.type !== 'tool_result' || used <= cap || isStubText(b.content)) return b;
|
|
786
850
|
const n = toolContentLength(b.content);
|
|
787
|
-
if (!n) return b;
|
|
851
|
+
if (!shouldStubBody(b.tool_use_id, n, { overBudget: true })) return b;
|
|
788
852
|
const stub = stubFor(b.tool_use_id, n);
|
|
789
853
|
if (stub.length >= n) return b;
|
|
790
854
|
used = used - n + stub.length;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
4
|
-
"description": "Local x402-paying proxy + MCP server for openzoo.fun
|
|
3
|
+
"version": "0.48.78",
|
|
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",
|
|
7
7
|
"bin": {
|