openzoo 0.48.34 → 0.48.36
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/hrr.js +6 -2
- package/lib/proxy.js +47 -5
- package/package.json +1 -1
package/lib/hrr.js
CHANGED
|
@@ -26,7 +26,7 @@ export const contextCacheDisabled = () => process.env.OPENZOO_NO_CONTEXT_CACHE =
|
|
|
26
26
|
* Ensure `corpus` is bound on the zoo. Returns
|
|
27
27
|
* { contextId, hash, reused, bytes } — reused=true means zero bytes shipped.
|
|
28
28
|
*/
|
|
29
|
-
export async function bindCorpus(corpus, { onStage, force = false } = {}) {
|
|
29
|
+
export async function bindCorpus(corpus, { onStage, force = false, appendTo = null } = {}) {
|
|
30
30
|
const hash = corpusHash(corpus);
|
|
31
31
|
const bytes = Buffer.byteLength(corpus);
|
|
32
32
|
if (!force) {
|
|
@@ -37,7 +37,11 @@ export async function bindCorpus(corpus, { onStage, force = false } = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
onStage?.('binding', { bytes });
|
|
40
|
-
|
|
40
|
+
// APPEND, don't re-upload. The gateway takes context_id on /v1/hrr/bind and
|
|
41
|
+
// adds to that context — which is the difference between paying for the
|
|
42
|
+
// delta and paying for the whole transcript again on every single turn.
|
|
43
|
+
const r = await postWithUploadSignal(`${config.apiBase}/v1/hrr/bind`,
|
|
44
|
+
JSON.stringify(appendTo ? { corpus, context_id: appendTo } : { corpus }), {
|
|
41
45
|
headers: withNamespace(),
|
|
42
46
|
onUploaded: () => onStage?.('bound-uploading-done', { bytes }),
|
|
43
47
|
});
|
package/lib/proxy.js
CHANGED
|
@@ -90,6 +90,9 @@ function jsonErr(res, status, message, extraFields = {}) {
|
|
|
90
90
|
|
|
91
91
|
const mb = (n) => (n / 1048576).toFixed(1);
|
|
92
92
|
|
|
93
|
+
// anchor -> { corpus, contextId, hash } for append-only transcript spills
|
|
94
|
+
const spillMemo = new Map();
|
|
95
|
+
|
|
93
96
|
/**
|
|
94
97
|
* Every fundable balance across all three chains, for the startup line and
|
|
95
98
|
* the live refresh. Each read is independent and advisory — one lagging RPC
|
|
@@ -365,11 +368,50 @@ async function spillTranscript(body, log) {
|
|
|
365
368
|
const corpus = msgs.slice(firstSpillable, cut).map(msgText).filter(Boolean).join('\n\n');
|
|
366
369
|
if (corpus.length <= BIND_MIN_CHARS) return null;
|
|
367
370
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
// CONTINUE THE CONTEXT, BIND ONLY THE DELTA.
|
|
372
|
+
//
|
|
373
|
+
// A transcript grows by one message per turn, so the whole-corpus hash misses
|
|
374
|
+
// every time and the old code re-uploaded the ENTIRE prefix on every single
|
|
375
|
+
// turn — OBSERVED live: 0.4MB bound three turns running, a fresh context id
|
|
376
|
+
// each time, while only a few KB was actually new. Bind cost grew with
|
|
377
|
+
// conversation length and was re-paid per message.
|
|
378
|
+
//
|
|
379
|
+
// The corpus is append-only: each turn's corpus starts with the previous
|
|
380
|
+
// one. So when it does, send just the tail and keep the same context_id.
|
|
381
|
+
// Anchored on the FIRST 2KB, which is stable for the life of a conversation
|
|
382
|
+
// and distinguishes concurrent ones.
|
|
383
|
+
const anchor = corpus.slice(0, 2048);
|
|
384
|
+
const prior = spillMemo.get(anchor);
|
|
385
|
+
let bind;
|
|
386
|
+
if (prior && corpus.startsWith(prior.corpus) && corpus.length > prior.corpus.length) {
|
|
387
|
+
const delta = corpus.slice(prior.corpus.length);
|
|
388
|
+
// FIRE AND FORGET. This delta is history for FUTURE turns — the answer
|
|
389
|
+
// being generated right now is served from the tail plus what is already
|
|
390
|
+
// bound, so waiting on the upload buys nothing and costs the user the
|
|
391
|
+
// round trip on every single turn. The context id is already known, so
|
|
392
|
+
// nothing is lost by not waiting for it.
|
|
393
|
+
//
|
|
394
|
+
// The FIRST bind is deliberately NOT async: the request must carry
|
|
395
|
+
// x-hrr-context, and that id does not exist until the bind returns. Firing
|
|
396
|
+
// that one off would send the opening turn with no context at all — a
|
|
397
|
+
// silently worse answer traded for a shorter pause, which is the wrong way
|
|
398
|
+
// round.
|
|
399
|
+
void bindCorpus(delta, {
|
|
400
|
+
appendTo: prior.contextId,
|
|
401
|
+
onStage: (stage, info) => {
|
|
402
|
+
if (stage === 'binding') log(`appending ${mb(info.bytes)}MB to ${prior.contextId} (delta, background)`);
|
|
403
|
+
},
|
|
404
|
+
}).catch((e) => log(`append failed (history may lag one turn): ${e.message}`));
|
|
405
|
+
bind = { contextId: prior.contextId, hash: prior.hash, reused: true, bytes: delta.length };
|
|
406
|
+
} else {
|
|
407
|
+
bind = await bindCorpus(corpus, {
|
|
408
|
+
onStage: (stage, info) => {
|
|
409
|
+
if (stage === 'binding') log(`binding ${mb(info.bytes)}MB of transcript to holographic memory...`);
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
spillMemo.set(anchor, { corpus, contextId: bind.contextId, hash: bind.hash });
|
|
414
|
+
if (spillMemo.size > 32) spillMemo.delete(spillMemo.keys().next().value);
|
|
373
415
|
const sent = msgs.length - cut;
|
|
374
416
|
log(bind.reused
|
|
375
417
|
? `transcript prefix already bound (${bind.contextId}) — sending ${sent}/${msgs.length} turns`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.36",
|
|
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",
|