openzoo 0.48.58 → 0.48.60

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.
Files changed (3) hide show
  1. package/lib/mcp.js +13 -13
  2. package/lib/proxy.js +59 -1
  3. package/package.json +1 -1
package/lib/mcp.js CHANGED
@@ -32,14 +32,14 @@ export function buildMcpServer() {
32
32
  'openzoo — pay-per-call access to ~435 models (openzoo.fun). No API key, no account: each call is paid',
33
33
  'from the operator\'s local burner wallet via x402 (Solana / Base / Robinhood Chain).',
34
34
  '',
35
- 'CONTEXT IS EFFECTIVELY UNBOUNDED. Pass big bodies to zoo_ask\'s `corpus` WHOLE do not summarise,',
36
- 'truncate, or chunk-to-fit. leCore holographic memory sits in front of every model: the corpus is bound',
37
- 'once (client-usable ceiling ~128M tokens; keep a single call under ~9.8M) and the model reads only a few',
38
- 'thousand tokens of it. A corpus already bound is never re-uploaded later asks against it are near-free,',
39
- 'so ask many questions of the same corpus rather than re-sending it. zoo_contexts lists what is bound.',
35
+ 'Long bodies: zoo_ask\'s `corpus` accepts a whole document. leCore holographic memory sits in front of',
36
+ 'every model, so a corpus is bound once and the model reads a few thousand tokens of it — summarising or',
37
+ 'chunking to fit is not required here. Client-usable ceiling is ~128M tokens; a single call stays under',
38
+ '~9.8M. A corpus already bound is not re-uploaded, so later asks against it are near-free — asking many',
39
+ 'questions of one corpus costs less than re-sending it. zoo_contexts lists what is bound.',
40
40
  '',
41
- 'Never search the machine for API keys or endpoints — there are none to find. zoo_wallet shows funding',
42
- 'state, zoo_models lists what is served with live pricing.',
41
+ 'There are no API keys or endpoints on the machine to find payment is per-call from the wallet. zoo_wallet',
42
+ 'shows funding state; zoo_models lists what is served with live pricing.',
43
43
  '',
44
44
  // Agents previously probed zoo_ask, got text back, and concluded the zoo
45
45
  // could not do media at all — the text upstream serves NO video models.
@@ -48,9 +48,9 @@ export function buildMcpServer() {
48
48
  ' zoo_video asynchronous, returns a job id (Sora 2, Veo 3.1, Kling, Seedance, Wan, pixverse…)',
49
49
  ' zoo_video_result poll the job id — free, the render was paid for at submit',
50
50
  'In zoo_models these carry kind="image" / kind="video" and are priced per image or per clip, not per token.',
51
- 'leCore does NOT participate in a render: a diffusion prompt never reaches the spill threshold, so do not',
52
- 'claim holographic memory improved the picture. To ground a render in your own material, use zoo_ask to',
53
- 'WRITE the prompt from a bound corpus, then pass that prompt to zoo_image / zoo_video.',
51
+ 'leCore does not participate in a render: a diffusion prompt never reaches the spill threshold, so holographic',
52
+ 'memory has no effect on the picture. To ground a render in existing material, zoo_ask can',
53
+ 'write the prompt from a bound corpus, and that prompt goes to zoo_image / zoo_video.',
54
54
  '',
55
55
  // ROUTING, STATED UNCONDITIONALLY. An agent whose OWN inference is not
56
56
  // routed through the proxy never receives lib/brief.js, so it answers
@@ -74,7 +74,7 @@ export function buildMcpServer() {
74
74
  server.registerTool('zoo_ask', {
75
75
  description:
76
76
  'Ask a question through openzoo.fun, paying the x402 quote transparently from the local burner wallet. '
77
- + 'PASS BIG CONTEXT WHOLE in `corpus` — do not summarise, truncate or chunk it to fit a context window. '
77
+ + 'Big context can go WHOLE into `corpus` — summarising, truncating or chunking to fit a context window is not required here. '
78
78
  + 'leCore holographic memory sits in front of every model, so a corpus far past the model\'s own limit '
79
79
  + '(client-usable ceiling ~128M tokens; keep one call under ~9.8M) is bound once and the model reads only '
80
80
  + 'a few thousand tokens of it. Re-asking against an already-bound corpus is near-free and much faster, so '
@@ -160,7 +160,7 @@ export function buildMcpServer() {
160
160
  balances,
161
161
  fundWith: rails ? railFundingHint(rails.live) : 'USDC or TOKEN on Solana',
162
162
  solanaMints: Object.fromEntries(FUNDING_ASSETS.map((a) => [a.symbol, a.mint])),
163
- tellTheUser: 'Report this verbatim the operator must fund the wallet above; you cannot fix it yourself, and retrying will fail identically until they do.',
163
+ tellTheUser: 'The wallet above needs funding by the operator. This is not fixable from inside the conversation, and a retry fails identically until it is funded.',
164
164
  }),
165
165
  isError: true,
166
166
  };
@@ -411,7 +411,7 @@ export function buildMcpServer() {
411
411
  files: out.files.length,
412
412
  parts: out.parts,
413
413
  reused: out.reused,
414
- next: `Call zoo_ask with context_id="${out.contextId}" and your question. Do NOT paste the corpus into the prompt it is already bound.`,
414
+ next: `zoo_ask with context_id="${out.contextId}" and a question answers from this corpus; it is already bound, so pasting it into the prompt would send it twice.`,
415
415
  });
416
416
  } catch (e) {
417
417
  return text({ error: e.message });
package/lib/proxy.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, appendFileSync, mkdirSync } from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import fs from 'node:fs';
4
5
  import http from 'node:http';
5
6
  import crypto from 'node:crypto';
6
7
  import { Readable } from 'node:stream';
@@ -115,6 +116,9 @@ const mb = (n) => (n / 1048576).toFixed(1);
115
116
 
116
117
  // anchor -> { corpus, contextId, hash } for append-only transcript spills
117
118
  const spillMemo = new Map();
119
+ // path:mtime of every file already bound — a file is bound once per version,
120
+ // never re-uploaded because the agent read it again.
121
+ const boundFiles = new Set();
118
122
 
119
123
  /**
120
124
  * Every fundable balance across all three chains, for the startup line and
@@ -463,7 +467,61 @@ async function spillTranscript(body, log, req) {
463
467
  if (lastUser > firstSpillable && cut > lastUser) cut = lastUser;
464
468
 
465
469
  const head = msgs.slice(0, firstSpillable); // system block, always kept
466
- const corpus = msgs.slice(firstSpillable, cut).map(msgText).filter(Boolean).join('\n\n');
470
+ // EVERY FILE THE AGENT TOUCHED, AT FULL SIZE.
471
+ //
472
+ // The saving ratio is corpus/sent, so on a fresh session — where the corpus
473
+ // IS the conversation — it starts near 1x and only climbs as you talk. That
474
+ // is exactly what the live agent numbers showed: 1.0-1.3x early, 8x once a
475
+ // 149k-token history existed. MEASURED the same day: prompt compressed 2.67x
476
+ // but billed savings was 1.13x, because output does not compress and a small
477
+ // corpus leaves nothing to compress on the other side either.
478
+ //
479
+ // Files are the fix. An agent reads far more bytes than it discusses, the
480
+ // harness has usually TRUNCATED them on the way in, and the full text is
481
+ // sitting on this machine. Binding it makes the corpus large immediately
482
+ // instead of eventually, and makes the truncated read whole again.
483
+ //
484
+ // Read-only, bounded, deduped by path+mtime, and failures are silent: this
485
+ // runs on the request path and must never be the reason a turn does not go.
486
+ const filesForCorpus = () => {
487
+ if (process.env.OPENZOO_BIND_FILES === '0') return '';
488
+ const cap = Number(process.env.OPENZOO_BIND_FILE_MAX || 400_000);
489
+ const out = [];
490
+ const seen = new Set();
491
+ for (const m of msgs) {
492
+ const blocks = Array.isArray(m?.content) ? m.content : [];
493
+ const calls = Array.isArray(m?.tool_calls) ? m.tool_calls : [];
494
+ const paths = [];
495
+ for (const b of blocks) {
496
+ const p = b?.input?.file_path || b?.input?.path;
497
+ if (typeof p === 'string') paths.push(p);
498
+ }
499
+ for (const c of calls) {
500
+ try {
501
+ const a = JSON.parse(c?.function?.arguments || '{}');
502
+ if (typeof a.file_path === 'string') paths.push(a.file_path);
503
+ else if (typeof a.path === 'string') paths.push(a.path);
504
+ } catch { /* not json args */ }
505
+ }
506
+ for (const p of paths) {
507
+ if (seen.has(p) || !path.isAbsolute(p)) continue;
508
+ seen.add(p);
509
+ try {
510
+ const st = fs.statSync(p);
511
+ if (!st.isFile() || st.size > cap) continue;
512
+ const key = `${p}:${st.mtimeMs}`;
513
+ if (boundFiles.has(key)) continue;
514
+ boundFiles.add(key);
515
+ out.push(`FILE ${p}\n${fs.readFileSync(p, 'utf8')}`);
516
+ } catch { /* unreadable, gone, or binary — simply not corpus */ }
517
+ }
518
+ }
519
+ return out.join('\n\n');
520
+ };
521
+
522
+ const turns = msgs.slice(firstSpillable, cut).map(msgText).filter(Boolean).join('\n\n');
523
+ const files = filesForCorpus();
524
+ const corpus = files ? `${turns}\n\n${files}` : turns;
467
525
  if (corpus.length <= BIND_MIN_CHARS) return null;
468
526
 
469
527
  // CONTINUE THE CONTEXT, BIND ONLY THE DELTA.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.58",
3
+ "version": "0.48.60",
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",