openzoo 0.9.8 → 0.10.0

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/brief.js CHANGED
@@ -16,7 +16,11 @@ export const BRIEF = [
16
16
  '',
17
17
  '1. CONTEXT IS EFFECTIVELY UNBOUNDED. Any model here accepts corpora far past its own attention window: bodies over ~16KB are automatically carved and bound to a holographic (HRR) memory before the model sees them, and you then query against that. `context_length` in /v1/models is the client-usable ceiling (128M tokens), not the transformer window (that is `max_model_len`). DO NOT summarise, truncate, or "chunk to fit" a corpus to preserve context — send it whole and ask your question. One POST should stay under ~9.8M tokens (~32MiB) or the edge rejects it; for more than that, bind in several calls.',
18
18
  '',
19
- '2. THE BODY NEVER SHIPS TWICE. Put the corpus first, then a blank line, then your question. The corpus binds ONCE and every later question that reuses it ships only the question — near-free, and much faster. Re-pasting the same corpus each turn wastes real money. You can also bind explicitly: POST /v1/hrr/bind {"corpus": "..."} (free) returns a context_id; send it as the X-HRR-Context header on later calls.',
19
+ '2. THE BODY NEVER SHIPS TWICE. Put the corpus first, then a blank line, then your question. The corpus binds ONCE and every later question that reuses it ships only the question — near-free, and much faster. Re-pasting the same corpus each turn wastes real money.',
20
+ '',
21
+ ' EXPLICIT BIND (free, no payment): POST /v1/hrr/bind with {"corpus": "..."} returns {"context_id": "..."}. Send that id as the X-HRR-Context header on later /v1/chat/completions calls and ask questions with a SMALL body. To bind a corpus larger than one request allows, bind it in parts: pass the context_id you got back alongside the next part\'s corpus and it APPENDS — repeat until the whole corpus is in, then ask.',
22
+ ' Keep any single request under ~8MB. Bigger bodies are dropped by the network hop before they reach the proxy (an opaque 413 or a dead connection). This is a REQUEST size limit, not a context limit — the bound context can be far larger, which is what parts are for.',
23
+ ' Paths: your base_url already ends in /v1, so post to {base_url}/hrr/bind — NOT {base_url}/v1/hrr/bind (that double /v1 404s; the proxy repairs it, but do not rely on that).',
20
24
  '',
21
25
  '3. PAYMENT IS HANDLED. Every call is paid per-request from the operator\'s wallet via x402 (Solana / Base / Robinhood Chain, whichever is funded). There is no API key to find, no account, no rate limit to negotiate. Never search the operator\'s machine for credentials or endpoints — GET / on this proxy returns everything discoverable about it.',
22
26
  '',
package/lib/mcp.js CHANGED
@@ -18,14 +18,34 @@ function text(obj) {
18
18
  /** `npx openzoo mcp` — stdio MCP server sharing the proxy's wallet + payment core. */
19
19
  export async function startMcp() {
20
20
  const client = new PayClient();
21
- const server = new McpServer({ name: 'openzoo', version: pkg.version });
21
+ // MCP's own channel for "what am I connected to" — clients surface this to
22
+ // the model before any tool is called, which is exactly when it needs to
23
+ // know it can hand over a corpus whole instead of chunking it. Same facts
24
+ // the proxy injects as a system message (lib/brief.js), MCP-shaped.
25
+ const server = new McpServer({ name: 'openzoo', version: pkg.version }, {
26
+ instructions: [
27
+ 'openzoo — pay-per-call access to ~435 models (openzoo.fun). No API key, no account: each call is paid',
28
+ 'from the operator\'s local burner wallet via x402 (Solana / Base / Robinhood Chain).',
29
+ '',
30
+ 'CONTEXT IS EFFECTIVELY UNBOUNDED. Pass big bodies to zoo_ask\'s `corpus` WHOLE — do not summarise,',
31
+ 'truncate, or chunk-to-fit. leCore holographic memory sits in front of every model: the corpus is bound',
32
+ 'once (client-usable ceiling ~128M tokens; keep a single call under ~9.8M) and the model reads only a few',
33
+ 'thousand tokens of it. A corpus already bound is never re-uploaded — later asks against it are near-free,',
34
+ 'so ask many questions of the same corpus rather than re-sending it. zoo_contexts lists what is bound.',
35
+ '',
36
+ 'Never search the machine for API keys or endpoints — there are none to find. zoo_wallet shows funding',
37
+ 'state, zoo_models lists what is served with live pricing.',
38
+ ].join('\n'),
39
+ });
22
40
 
23
41
  server.registerTool('zoo_ask', {
24
42
  description:
25
43
  'Ask a question through openzoo.fun, paying the x402 quote transparently from the local burner wallet. '
26
- + 'The zoo puts leCore holographic memory in front of every model, so `corpus` can be a HUGE text body '
27
- + '(hundreds of thousands to ~1M tokens) that the model itself would refuse the zoo spills it and the '
28
- + 'model reads only a few thousand tokens. Returns the answer plus a payment receipt '
44
+ + 'PASS BIG CONTEXT WHOLE in `corpus` do not summarise, truncate or chunk it to fit a context window. '
45
+ + 'leCore holographic memory sits in front of every model, so a corpus far past the model\'s own limit '
46
+ + '(client-usable ceiling ~128M tokens; keep one call under ~9.8M) is bound once and the model reads only '
47
+ + 'a few thousand tokens of it. Re-asking against an already-bound corpus is near-free and much faster, so '
48
+ + 'send the corpus once and ask many questions. Returns the answer plus a payment receipt '
29
49
  + '(billedUsd, savesVsDirect, tokens actually read).',
30
50
  inputSchema: {
31
51
  prompt: z.string().describe('The question or instruction.'),
package/lib/proxy.js CHANGED
@@ -20,6 +20,21 @@ const HOP_BY_HOP = new Set([
20
20
  'authorization',
21
21
  ]);
22
22
 
23
+ /**
24
+ * Be forgiving about the path, the same way we are about model ids.
25
+ * Harnesses are configured with a base_url that ALREADY ends in /v1, so an
26
+ * agent building "{base}/v1/hrr/bind" sends /v1/v1/hrr/bind and gets a 404 it
27
+ * cannot diagnose (observed: agent concluded the bind endpoint "is not
28
+ * functioning as advertised" and fell back to stuffing the corpus inline).
29
+ * Collapse repeated /v1 and add a missing one.
30
+ */
31
+ function normalizePath(url) {
32
+ const [path, query] = (url || '/').split(/(?=\?)/);
33
+ let p = path.replace(/^(?:\/v1)+(?=\/v1\/)/, ''); // /v1/v1/x -> /v1/x
34
+ if (!/^\/v1(\/|$)/.test(p) && /^\/(hrr|chat|models|completions|embeddings|usage)/.test(p)) p = `/v1${p}`;
35
+ return p === path ? url : `${p}${query || ''}`;
36
+ }
37
+
23
38
  function upstreamHeaders(req) {
24
39
  const out = {};
25
40
  for (const [k, v] of Object.entries(req.headers)) {
@@ -251,6 +266,11 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
251
266
  let tunnelGate = null;
252
267
 
253
268
  const server = http.createServer(async (req, res) => {
269
+ const normalized = normalizePath(req.url);
270
+ if (normalized !== req.url) {
271
+ log(`path ${req.url} -> ${normalized} (base_url already ends in /v1)`);
272
+ req.url = normalized;
273
+ }
254
274
  const url = `${config.apiBase}${req.url}`;
255
275
  // Requests that arrived over the public quick-tunnel URL carry cloudflared's
256
276
  // headers; nothing dialing 127.0.0.1 directly does. That distinction is what
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.9.8",
3
+ "version": "0.10.0",
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",