openzoo 0.48.62 → 0.48.64

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
@@ -19,24 +19,22 @@
19
19
  * not an API) because nothing in the conversation named the real one.
20
20
  */
21
21
  export const briefFor = (selfUrl) => [
22
- 'Environment notes for this endpoint (descriptive verify anything you rely on):',
23
- '',
24
- ...(selfUrl ? [
25
- `- Base URL: ${selfUrl}. It already ends in /v1, so routes are ${selfUrl}/chat/completions, ${selfUrl}/hrr/bind, ${selfUrl}/models. A doubled /v1 is repaired by the proxy but logs a warning. openzoo.fun is a website, not an API host.`,
26
- '',
27
- ] : []),
28
- '- Long bodies: requests over ~16KB are carved and bound to a holographic (HRR) memory before the model sees them, and the model answers from retrieval over that. So a large corpus can be sent whole; summarising or chunking it to fit is not required here, though nothing stops you. `context_length` in /v1/models reports the client-usable ceiling (128M tokens); the transformer window is `max_model_len`. A single POST over ~9.8M tokens (~32MiB) is rejected by the edge.',
29
- '',
30
- '- Repeat sends: a corpus placed first, followed by a blank line and then a question, binds once. Later questions reusing it ship only the question, which is cheaper and faster. Re-sending the same corpus each turn costs full price each time.',
31
- '',
32
- ' Explicit bind (unpaid): POST /v1/hrr/bind with {"corpus": "..."} returns {"context_id": "..."}. Passing that id as the X-HRR-Context header on later /v1/chat/completions calls lets the body stay small. Passing an existing context_id alongside a new corpus appends to it, which is how a corpus larger than one request gets bound in parts.',
33
- ' Request size: single requests over ~8MB are dropped by the network hop before reaching the proxy (opaque 413 or dead connection). That is a request limit, not a context limit.',
34
- '',
35
- '- Payment: calls are settled per request from the operator\'s own wallet via x402 (Solana / Base / Robinhood Chain, whichever is funded). There is no account to create and no key for you to supply or handle. GET / on this proxy returns the same description. /hrr/bind and GET /models are unpaid; /chat/completions is paid and uses the bearer key the client is already configured with, which is not readable from inside the conversation.',
36
- '',
37
- '- Model ids: unknown ids are matched to the nearest served model rather than erroring. /v1/models lists what is actually served, and each alias row carries `served_by`.',
22
+ // SMALL ON PURPOSE. This used to be ~2.2KB of prose on EVERY request —
23
+ // corpus binding, request-size limits, payment rails, model-id matching —
24
+ // most of which a given call never needs, all of which the caller pays for in
25
+ // tokens and latency. An agent that needs the detail can ask; what it cannot
26
+ // work out for itself is the base URL and the one behaviour that changes how
27
+ // it should send a big body. Everything else was documentation shipped as
28
+ // overhead.
29
+ ...(selfUrl ? [`Endpoint: ${selfUrl} (already ends in /v1). Routes: /chat/completions, /hrr/bind, /models.`] : []),
30
+ 'Bodies over ~16KB are bound to holographic memory and answered by retrieval, so a large corpus can be sent whole rather than summarised or chunked. A corpus sent once is not re-uploaded.',
31
+ 'Calls are paid per request from the operator\'s wallet; there is no key to supply. Unknown model ids match the nearest served model.',
38
32
  ].join('\n');
39
33
 
34
+ /** Stable substring used to detect an already-injected brief. Must appear in
35
+ * briefFor() output verbatim — see injectBrief(). */
36
+ export const BRIEF_MARK = 'bound to holographic memory and answered by retrieval';
37
+
40
38
  /** Back-compat: the briefing with no endpoint line. */
41
39
  export const BRIEF = briefFor(null);
42
40
 
@@ -51,7 +49,11 @@ export function injectBrief(body, selfUrl = null) {
51
49
  if (process.env.OPENZOO_NO_BRIEF === '1') return null;
52
50
  const msgs = body?.messages;
53
51
  if (!Array.isArray(msgs) || !msgs.length) return null;
54
- if (msgs.some((m) => typeof m?.content === 'string' && m.content.includes('connected through an openzoo proxy'))) return null;
52
+ // THE SENTINEL MUST BE TEXT THE BRIEF ACTUALLY CONTAINS. This checked for
53
+ // 'connected through an openzoo proxy' — the old opening line — so shrinking
54
+ // the brief would have silently broken idempotency and stacked a fresh copy
55
+ // onto every single turn, growing the system block without bound.
56
+ if (msgs.some((m) => typeof m?.content === 'string' && m.content.includes(BRIEF_MARK))) return null;
55
57
 
56
58
  const brief = { role: 'system', content: briefFor(selfUrl) };
57
59
  // THE LEADING SYSTEM RUN ONLY — NOT THE LAST SYSTEM ANYWHERE.
package/lib/proxy.js CHANGED
@@ -829,6 +829,9 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
829
829
  // cost can.
830
830
  let sessionActual = 0;
831
831
  let actualCalls = 0;
832
+ // billed for ONLY those calls whose real cost we learned — the honest
833
+ // numerator for markupX. See the comment at the usage.cost site.
834
+ let billedWithActual = 0;
832
835
  // CREDIT, CACHED. Users cannot tell prepaid credit from wallet balance and
833
836
  // have to guess whether a call was even paid for ("I don't think x402 made me
834
837
  // pay this at all"). The status line runs EVERY turn, so this is refreshed at
@@ -980,9 +983,9 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
980
983
  actual: {
981
984
  calls: actualCalls,
982
985
  upstreamUsd: Number(sessionActual.toFixed(6)),
983
- billedUsd: Number(sessionSpent.toFixed(6)),
984
- marginUsd: Number((sessionSpent - sessionActual).toFixed(6)),
985
- markupX: sessionActual > 0 ? Number((sessionSpent / sessionActual).toFixed(2)) : null,
986
+ billedUsd: Number(billedWithActual.toFixed(6)),
987
+ marginUsd: Number((billedWithActual - sessionActual).toFixed(6)),
988
+ markupX: sessionActual > 0 ? Number((billedWithActual / sessionActual).toFixed(2)) : null,
986
989
  },
987
990
  mcp: `${self.replace(/\/v1$/, '')}/mcp`,
988
991
  upstream: config.apiBase,
@@ -1213,7 +1216,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
1213
1216
  bodyBuf = rw.body;
1214
1217
  }
1215
1218
  try {
1216
- const parsed = JSON.parse(bodyBuf.toString('utf8'));
1219
+ let parsed = JSON.parse(bodyBuf.toString('utf8'));
1217
1220
  wantsStream = parsed?.stream === true || clientWantsStream;
1218
1221
  // REASONING MODELS SPEND max_tokens ON THINKING FIRST.
1219
1222
  //
@@ -1259,8 +1262,56 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
1259
1262
  const selfUrl = viaTunnel && tunnelGate?.publicUrl
1260
1263
  ? `${tunnelGate.publicUrl}/v1`
1261
1264
  : `http://localhost:${config.port}/v1`;
1262
- const briefed = injectBrief(parsed, selfUrl);
1263
- if (briefed) bodyBuf = Buffer.from(JSON.stringify(briefed));
1265
+ // NOT ON A YES/NO. The brief is ~2.2KB describing corpus binding,
1266
+ // request-size limits and payment — none of which a tiny call can
1267
+ // use. Claude Code's auto-mode safety classifier asks a 16-token
1268
+ // question before it will run Bash, and it has a short timeout:
1269
+ // MEASURED, that call takes 3.5s cold through here against a 0.09s
1270
+ // gateway 402, and it times out on a machine paying on-chain. Adding
1271
+ // 2.2KB of prose to a body that small is latency and tokens spent on
1272
+ // advice nobody will read.
1273
+ //
1274
+ // Threshold is the same one the spill uses: below it there is no
1275
+ // corpus and nothing the brief could help with.
1276
+ const tiny = bodyBuf.length < BIND_MIN_CHARS
1277
+ && Number(parsed?.max_tokens ?? 0) > 0
1278
+ && Number(parsed?.max_tokens) <= 64;
1279
+ const briefed = tiny ? null : injectBrief(parsed, selfUrl);
1280
+ if (briefed) parsed = briefed;
1281
+ // SYSTEM MESSAGES BELONG AT THE FRONT, OR GOOGLE 400s.
1282
+ //
1283
+ // Claude Code emits <system-reminder> blocks mid-conversation, which
1284
+ // is legal for Anthropic natively. Several upstreams behind OpenRouter
1285
+ // are not: fable-5 is served by GOOGLE, whose API takes a system
1286
+ // instruction only before the conversation starts and rejects one
1287
+ // after. CAPTURED live — provider_error code 400,
1288
+ // roles="sssusatatus", 311KB body: two system messages sitting after
1289
+ // user turns, on a model that answers a simple call fine.
1290
+ //
1291
+ // So fold every later system message into the leading block, in
1292
+ // order. The content survives and its position moves; the alternative
1293
+ // is a 400 that ends the turn and tells the caller nothing.
1294
+ const nm = Array.isArray(parsed?.messages) ? parsed.messages : null;
1295
+ if (nm && nm.length > 1) {
1296
+ let lead = 0;
1297
+ while (lead < nm.length && nm[lead]?.role === 'system') lead += 1;
1298
+ const strays = [];
1299
+ const kept = [];
1300
+ nm.forEach((m, i) => {
1301
+ if (i >= lead && m?.role === 'system') strays.push(m);
1302
+ else kept.push(m);
1303
+ });
1304
+ if (strays.length) {
1305
+ const merged = strays.map((m) => (typeof m.content === 'string' ? m.content : msgText(m))).filter(Boolean).join('\n\n');
1306
+ const head = kept.slice(0, lead);
1307
+ const tailMsgs = kept.slice(lead);
1308
+ if (head.length) head[head.length - 1] = { ...head[head.length - 1], content: `${typeof head[head.length - 1].content === 'string' ? head[head.length - 1].content : msgText(head[head.length - 1])}\n\n${merged}` };
1309
+ else head.push({ role: 'system', content: merged });
1310
+ parsed = { ...parsed, messages: [...head, ...tailMsgs] };
1311
+ log(`hoisted ${strays.length} interleaved system message(s) to the leading block (some providers 400 otherwise)`);
1312
+ }
1313
+ }
1314
+ bodyBuf = Buffer.from(JSON.stringify(parsed));
1264
1315
  }
1265
1316
  } catch { /* not JSON */ }
1266
1317
  }
@@ -1268,6 +1319,21 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
1268
1319
  // Retry of a body we answered seconds ago? Serve the cached completion —
1269
1320
  // never pay twice for a harness's reconnect loop.
1270
1321
  const isChat = req.method === 'POST' && (req.url || '').includes('/chat/completions');
1322
+ // GROUND TRUTH ON THE OUTGOING BODY. Three sessions have now reported "no
1323
+ // actual question or task from you" while the proxy log showed a healthy
1324
+ // forward, and two rounds of reasoning about the cut were wrong. Log what
1325
+ // is actually in messages[] on the way out — roles, and the tail of the
1326
+ // last user turn — so the question stops being a matter of opinion.
1327
+ if (process.env.OPENZOO_LOG_BODY === '1' && isChat) {
1328
+ try {
1329
+ const b = JSON.parse(bodyBuf.toString('utf8'));
1330
+ const ms = Array.isArray(b?.messages) ? b.messages : [];
1331
+ const roles = ms.map((m) => (m.role || '?')[0]).join('');
1332
+ const lastUser = [...ms].reverse().find((m) => m.role === 'user');
1333
+ const txt = lastUser ? String(msgText(lastUser)).slice(-160).replace(/\s+/g, ' ') : '(NO USER MESSAGE)';
1334
+ log(` OUT roles=${roles} n=${ms.length} lastUser="${txt}"`);
1335
+ } catch { /* not json */ }
1336
+ }
1271
1337
  const rKey = isChat ? replayKey(bodyBuf, req.headers) : null;
1272
1338
  if (rKey) {
1273
1339
  const hit = replayGet(rKey);
@@ -1436,6 +1502,15 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
1436
1502
  if (typeof data?.usage?.cost === 'number' && data.usage.cost >= 0) {
1437
1503
  sessionActual += data.usage.cost;
1438
1504
  actualCalls += 1;
1505
+ // PAIR THE NUMERATOR WITH THE DENOMINATOR. sessionSpent is summed on
1506
+ // three paths and sessionActual on two, so markupX divided ALL billed
1507
+ // by the SUBSET that reported a real cost — a 402-receipt call added
1508
+ // to billed and nothing to real, and the ratio read 12.55x on a stack
1509
+ // running at ~1.0x. Track the billed side of exactly the calls whose
1510
+ // cost we actually learned.
1511
+ // Both figures ride the SAME response object, so read them together
1512
+ // rather than carrying one across sites and hoping the order holds.
1513
+ billedWithActual += Number(data?.x402?.billedUsd) || 0;
1439
1514
  }
1440
1515
  // PREPAID CALLS STILL COST MONEY. The block above only meters calls
1441
1516
  // where THIS proxy answered a 402 and paid. When prepaid credit covers
@@ -1513,7 +1588,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
1513
1588
  sessionSpent += x.billedUsd;
1514
1589
  sessionCogs += typeof x.cogsUsd === 'number' ? x.cogsUsd : x.billedUsd / MARKUP;
1515
1590
  sessionDirect += typeof x.directUsd === 'number' ? x.directUsd : x.billedUsd;
1516
- if (typeof x.actualUsd === 'number' && x.actualUsd >= 0) { sessionActual += x.actualUsd; actualCalls += 1; }
1591
+ if (typeof x.actualUsd === 'number' && x.actualUsd >= 0) { sessionActual += x.actualUsd; actualCalls += 1; billedWithActual += x.billedUsd || 0; }
1517
1592
  if (didSpill) {
1518
1593
  const lc = x.lecore || {};
1519
1594
  log(`spill priced (streamed): ${x.pricing} · basis ${x.counterfactualTokensUsed ?? '?'} tok vs sent ${lc.tokensBefore ?? '?'} -> ${lc.tokensAfter ?? '?'} · billed ${(x.billedUsd ?? 0).toFixed(5)} direct ${(x.directUsd ?? 0).toFixed(5)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.62",
3
+ "version": "0.48.64",
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",