openzoo 0.50.76 → 0.50.77

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/bin/openzoo.js CHANGED
@@ -284,6 +284,9 @@ async function main() {
284
284
  ? process.argv[ei + 1].split(',').map((e) => (e.startsWith('.') ? e : `.${e}`))
285
285
  : undefined;
286
286
  const mb = (n) => (n / 1048576).toFixed(1);
287
+ // Delta uploads are routinely a few KB against a multi-MB corpus; "0.0MB
288
+ // of 3.2MB" reads as a bug. Pick the unit per number.
289
+ const hb = (n) => (n >= 1048576 ? `${(n / 1048576).toFixed(1)}MB` : n >= 1024 ? `${(n / 1024).toFixed(0)}KB` : `${n}B`);
287
290
  const out = await bindPath(target, {
288
291
  exts,
289
292
  force: process.argv.includes('--force'),
@@ -291,9 +294,23 @@ async function main() {
291
294
  if (p.stage === 'reused') console.log(`already bound — ${mb(p.bytes)}MB across ${p.files} file(s) reused, nothing uploaded`);
292
295
  if (p.stage === 'start') console.log(`binding ${mb(p.bytes)}MB from ${p.files} file(s) in ${p.parts} part(s)...`);
293
296
  if (p.stage === 'part') console.log(` part ${p.index}/${p.of} bound (${mb(p.bytes)}MB)`);
297
+ // DELTA: the server already holds most of a re-bound repo, so say
298
+ // how much is actually crossing the wire — that number is the reason
299
+ // re-binding after an edit is cheap, and hiding it makes the feature
300
+ // look like a no-op.
301
+ if (p.stage === 'delta') {
302
+ console.log(p.missing === 0
303
+ ? `delta: server already holds all ${p.chunks} chunk(s) of ${hb(p.bytes)} — nothing to upload`
304
+ : `delta: ${p.missing} of ${p.chunks} chunk(s) missing on the server, uploading only those...`);
305
+ }
306
+ if (p.stage === 'delta-fill') console.log(` shipped ${hb(p.shipped)} of ${hb(p.of)}${p.remaining ? ` (${p.remaining} still missing)` : ''}`);
294
307
  },
295
308
  });
296
309
  console.log('');
310
+ if (out.delta) {
311
+ const pct = out.bytes ? ((100 * out.shipped) / out.bytes).toFixed(1) : '0.0';
312
+ console.log(`bound via delta — uploaded ${hb(out.shipped)} of ${hb(out.bytes)} (${pct}%)`);
313
+ }
297
314
  console.log(`context: ${out.contextId}`);
298
315
  console.log(`ask it: npx openzoo ask "your question" --context ${out.contextId}`);
299
316
  console.log('or send X-HRR-Context: <id> with a small body to /v1/chat/completions');
package/lib/bindpath.js CHANGED
@@ -144,14 +144,17 @@ export function splitIntoParts(text, maxBytes = MAX_PART_BYTES) {
144
144
  */
145
145
  const sha256 = (t) => createHash('sha256').update(t, 'utf8').digest('hex');
146
146
 
147
+ const ddbg = (...a) => { if (process.env.OPENZOO_BIND_DEBUG) console.error('[delta]', ...a); };
148
+
147
149
  async function postDelta(payload) {
148
150
  const r = await fetch(`${config.apiBase}/v1/hrr/delta`, {
149
151
  method: 'POST',
150
152
  headers: withNamespace({ 'content-type': 'application/json' }),
151
153
  body: JSON.stringify(payload),
152
154
  });
153
- if (r.status !== 200) return null;
155
+ if (r.status !== 200) { ddbg('HTTP', r.status, (await r.text().catch(() => '')).slice(0, 200)); return null; }
154
156
  const j = await r.json().catch(() => null);
157
+ if (!(j && Array.isArray(j.missing))) ddbg('bad shape', JSON.stringify(j).slice(0, 200));
155
158
  return j && Array.isArray(j.missing) ? j : null;
156
159
  }
157
160
 
@@ -175,19 +178,19 @@ async function bindDelta(fileTexts, shardKey, onProgress) {
175
178
  let size = 0;
176
179
  for (const h of missing) {
177
180
  const t = byHash.get(h);
178
- if (t === undefined) return null;
181
+ if (t === undefined) { ddbg('missing hash not in byHash', h); return null; }
179
182
  if (size && size + Buffer.byteLength(t) > MAX_PART_BYTES) break;
180
183
  batch[h] = t; size += Buffer.byteLength(t);
181
184
  }
182
- if (!Object.keys(batch).length) return null;
185
+ if (!Object.keys(batch).length) { ddbg('empty batch'); return null; }
183
186
  last = await postDelta({ chunk_hashes: hashes, chunks: batch, shard_key: shardKey });
184
187
  if (!last) return null;
185
188
  shipped += size;
186
189
  onProgress?.({ stage: 'delta-fill', shipped, of: total, remaining: last.missing.length });
187
- if (last.missing.length >= missing.length) return null; // no progress: refused chunks
190
+ if (last.missing.length >= missing.length) { ddbg('no progress', last.missing.length, missing.length, JSON.stringify(last.refused)); return null; } // no progress: refused chunks
188
191
  missing = last.missing;
189
192
  }
190
- if (!last.complete || !last.context_id) return null;
193
+ if (!last.complete || !last.context_id) { ddbg('incomplete', JSON.stringify(last).slice(0, 200)); return null; }
191
194
  return { contextId: last.context_id, chunks: chunks.length, shipped, bytes: total };
192
195
  }
193
196
 
@@ -259,7 +262,7 @@ export async function bindPath(target, { exts, onProgress, force = false } = {})
259
262
  rememberContext(config.apiBase, hash, d.contextId);
260
263
  return { contextId: d.contextId, files, parts: d.chunks, bytes, reused: false, delta: true, shipped: d.shipped };
261
264
  }
262
- } catch { /* fall through to the whole bind */ }
265
+ } catch (e) { ddbg('threw', e?.message); /* fall through to the whole bind */ }
263
266
  }
264
267
 
265
268
  const parts = splitIntoParts(text);
package/lib/mcp.js CHANGED
@@ -413,6 +413,10 @@ export function buildMcpServer() {
413
413
  files: out.files.length,
414
414
  parts: out.parts,
415
415
  reused: out.reused,
416
+ // A delta bind uploaded only the chunks the server lacked. `uploaded_bytes`
417
+ // against `bound_bytes` is the saving; an agent re-binding a repo after
418
+ // one edit should see a small number here, not the whole tree.
419
+ ...(out.delta ? { delta: true, uploaded_bytes: out.shipped } : {}),
416
420
  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.`,
417
421
  });
418
422
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.76",
3
+ "version": "0.50.77",
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",