openzoo 0.49.18 → 0.50.1
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 +0 -9
- package/lib/models.js +27 -211
- package/lib/proxy.js +112 -1377
- package/lib/xbot.js +1569 -0
- package/lib/xburner.js +80 -0
- package/package.json +4 -5
- package/lib/anthropic.js +0 -369
- package/lib/boxes.js +0 -299
- package/lib/brief.js +0 -76
- package/lib/grokbot.js +0 -120
- package/lib/grokui.mjs +0 -6295
- package/lib/modelroute/README.md +0 -1
- package/lib/modelroute/catalog.json +0 -1
- package/lib/modelroute/outcomes.json +0 -1566
- package/lib/modelroute/router.json +0 -1
- package/lib/modelroute.js +0 -737
- package/lib/podagent.mjs +0 -1414
- package/lib/responses-stream.js +0 -176
- package/lib/responses.js +0 -425
- package/lib/runguard.js +0 -31
- package/lib/spill.js +0 -2031
- package/lib/worktree.mjs +0 -424
- package/vendor/modelroute/CURRENT_STATE.md +0 -132
- package/vendor/modelroute/FOR_MOOSE.md +0 -110
- package/vendor/modelroute/HANDOFF.md +0 -159
- package/vendor/modelroute/catalog.json +0 -1
- package/vendor/modelroute/holographic_modelroute.py +0 -809
- package/vendor/modelroute/outcomes.json +0 -1566
- package/vendor/modelroute/router.json +0 -1
package/bin/openzoo.js
CHANGED
|
@@ -106,10 +106,6 @@ usage:
|
|
|
106
106
|
by default, --terminal for the Claude Code CLI
|
|
107
107
|
npx openzoo launch <cmd> [args] launch a TERMINAL Messages API client
|
|
108
108
|
(claude, aider...) already pointed at the zoo
|
|
109
|
-
npx openzoo grokbot KEEP Grok Bot's UI, serve YOUR RunPod box under it:
|
|
110
|
-
spawns a CPU box, MITMs api2.cursor.sh, and answers
|
|
111
|
-
EnsureSandBox with your box instead of a cursorvm pod.
|
|
112
|
-
Inference x402-paid; needs RUNPOD_API_KEY + sudo.
|
|
113
109
|
npx openzoo grok-cli point the grok CLI at the zoo — GROK MODELS ONLY,
|
|
114
110
|
paid per call by x402 instead of xAI first-party billing,
|
|
115
111
|
then TAKE OVER the app's backend: pins api2.cursor.sh in
|
|
@@ -148,8 +144,6 @@ env:
|
|
|
148
144
|
OPENZOO_RAIL (unset — force a rail: solana | base | robinhood)
|
|
149
145
|
OPENZOO_BASE_RPC (https://mainnet.base.org) OPENZOO_RH_RPC (rpc.mainnet.chain.robinhood.com)
|
|
150
146
|
OPENZOO_MAX_USD_PER_CALL (unset — NO per-call ceiling; set to add one) OPENZOO_DEMO_MAX_USD (0.01)
|
|
151
|
-
OPENZOO_CONTEXT_MIN_CHARS (16384 — bodies bigger than this bind once + reuse)
|
|
152
|
-
OPENZOO_NO_CONTEXT_CACHE (0 — set 1 to always ship the full body)
|
|
153
147
|
OPENZOO_ENABLE_RH (0 — let DEFAULT selection fall through to the Robinhood rail;
|
|
154
148
|
OPENZOO_RAIL=robinhood forces it without this)
|
|
155
149
|
OPENZOO_TUNNEL_MAX_USD (unset — NO public-url session ceiling; set to add one) OPENZOO_TUNNEL_TOKEN (pin the api key)
|
|
@@ -167,9 +161,6 @@ async function main() {
|
|
|
167
161
|
// GUI editors read config files, not env vars — see lib/setup.js.
|
|
168
162
|
await (await import('../lib/setup.js')).setupEditor(cmd === 'editor' ? undefined : cmd, process.argv[3]);
|
|
169
163
|
break;
|
|
170
|
-
case 'grokbot':
|
|
171
|
-
await (await import('../lib/grokbot.js')).runGrokBot(process.argv.slice(3));
|
|
172
|
-
break;
|
|
173
164
|
case 'grok-cli':
|
|
174
165
|
case 'grok':
|
|
175
166
|
// Grok Bot (com.anysphere.sand) fronts the `grok` CLI, and the CLI reads
|
package/lib/models.js
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { config } from './config.js';
|
|
2
2
|
import { fetchHeaders } from './fetch.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// AUTO routing lives on the backend now; these helpers survive only so the
|
|
4
|
+
// published catalog can EXCLUDE ids the gateway cannot serve (auto aliases,
|
|
5
|
+
// :batch twins, $0 rows). The shim never routes.
|
|
6
|
+
export const AUTO_MODEL_ID = 'openzoo/auto';
|
|
7
|
+
export const AUTO_MODEL_ALIASES = new Set(['openzoo/auto', 'openzoo-auto', 'auto']);
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
+
export function isAutoModel(id) {
|
|
10
|
+
return AUTO_MODEL_ALIASES.has(String(id || '').trim().toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Ids the gateway cannot quote for a chat turn (:batch twins, ~latest
|
|
14
|
+
* pointers, openzoo-* aliases). The published catalog must never list them. */
|
|
15
|
+
export function isUnservableRouteId(id) {
|
|
16
|
+
const s = String(id || '').trim();
|
|
17
|
+
if (!s) return true;
|
|
18
|
+
if (isAutoModel(s)) return false;
|
|
19
|
+
return s.includes(':batch') || s.startsWith('~') || s.startsWith('openzoo-');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Both sides must be a finite positive token price. $0 / NaN / missing 500. */
|
|
23
|
+
export function isPricedTokenPair(priceIn, priceOut) {
|
|
24
|
+
const pin = Number(priceIn);
|
|
25
|
+
const pout = Number(priceOut);
|
|
26
|
+
return Number.isFinite(pin) && Number.isFinite(pout) && pin > 0 && pout > 0;
|
|
27
|
+
}
|
|
9
28
|
|
|
10
29
|
/** Same threshold as BIND_MIN_CHARS in hrr.js — kept local so this
|
|
11
30
|
* module stays importable without the wallet/rpc stack. */
|
|
@@ -313,14 +332,9 @@ export function augmentModelList(payload, { aliases: withAliases = true } = {})
|
|
|
313
332
|
};
|
|
314
333
|
})
|
|
315
334
|
: [];
|
|
316
|
-
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
if (!have.has(AUTO_MODEL_ID) && autoHasPricedModels(undefined, ids.length ? ids : null)) {
|
|
320
|
-
virtual.push(autoModelListEntry());
|
|
321
|
-
have.add(AUTO_MODEL_ID);
|
|
322
|
-
}
|
|
323
|
-
return { ...payload, object: payload?.object || 'list', data: [...virtual, ...data, ...aliases] };
|
|
335
|
+
// Auto is no longer published: routing lives on the backend, and a shim
|
|
336
|
+
// that advertises openzoo/auto would have to route it.
|
|
337
|
+
return { ...payload, object: payload?.object || 'list', data: [...data, ...aliases] };
|
|
324
338
|
}
|
|
325
339
|
|
|
326
340
|
/**
|
|
@@ -415,7 +429,6 @@ export function pickClaudePickerRows(rows) {
|
|
|
415
429
|
for (const { prefer, re } of CLAUDE_PICKER_GATEWAY) {
|
|
416
430
|
add(ids.includes(prefer) ? prefer : newestMatching(ids, (id) => re.test(id)));
|
|
417
431
|
}
|
|
418
|
-
add(AUTO_MODEL_ID);
|
|
419
432
|
return picked.map((id) => byId.get(id)).filter(Boolean);
|
|
420
433
|
}
|
|
421
434
|
|
|
@@ -462,200 +475,3 @@ export function wantsAnthropicModelList(headers = {}) {
|
|
|
462
475
|
export function modelsListForRequest(payload, headers) {
|
|
463
476
|
return wantsAnthropicModelList(headers) ? anthropicModelList(payload) : publishModelList(payload);
|
|
464
477
|
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Which request paths carry a rewritable model field. POST-only; embeddings /
|
|
468
|
-
* audio / image / moderation models are DIFFERENT model families — rewriting
|
|
469
|
-
* a chat model into those would corrupt the call, so they pass untouched.
|
|
470
|
-
*/
|
|
471
|
-
export function rewritablePath(method, url) {
|
|
472
|
-
if (method !== 'POST') return false;
|
|
473
|
-
const p = (url || '').split('?')[0];
|
|
474
|
-
return !/embed|audio|image|moderation/.test(p);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Families that spend max_tokens on hidden thinking first. A 16/40/160
|
|
479
|
-
* budget on these returns an empty visible completion — measured on Grok
|
|
480
|
-
* and DeepSeek. The raise in raiseReasoningMaxTokens exists for that.
|
|
481
|
-
* It must NEVER fire on Claude Code's 16-token auto-mode classifier.
|
|
482
|
-
*/
|
|
483
|
-
export const REASONING_MODEL_RE = /(deepseek|grok|o[134](-|$)|reasoner|thinking|-pro\b|sol-pro|qwq)/i;
|
|
484
|
-
|
|
485
|
-
/** Claude Code auto-mode classify is max_tokens=16. 0.48.75 used 64 and
|
|
486
|
-
* missed grok nubs at 128 / 2000 on a 1–2 message body (dead-steady ~3¢
|
|
487
|
-
* from the reasoning floor). Anything in (0, 256] on a short transcript
|
|
488
|
-
* is a nub; a fat grok chat still uses the 4000 floor. */
|
|
489
|
-
export const CLASSIFY_MAX_TOKENS = 256;
|
|
490
|
-
export const CLASSIFY_MAX_MSGS = 3;
|
|
491
|
-
export const CLASSIFY_MAX_BODY = 65_536;
|
|
492
|
-
|
|
493
|
-
const CLASSIFIER_PREFS = ['google/gemini-3.7-flash', 'anthropic/claude-haiku-4.5'];
|
|
494
|
-
|
|
495
|
-
function messageHasToolCalls(m) {
|
|
496
|
-
return Boolean(
|
|
497
|
-
(Array.isArray(m?.tool_calls) && m.tool_calls.length)
|
|
498
|
-
|| m?.function_call
|
|
499
|
-
|| m?.role === 'tool',
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Tiny classify / grok nub: pin to flash, never apply the reasoning floor.
|
|
505
|
-
*
|
|
506
|
-
* 0.48.75 required max_tokens ≤ 64 AND body < BIND_MIN (16k). Live 3¢
|
|
507
|
-
* asks missed that (max_tokens 128 or 2000, and/or body ≥ 16k from a
|
|
508
|
-
* tools schema) and then ate the 4000 grok floor. Widen:
|
|
509
|
-
* - max_tokens ≤ 256 on a short transcript (≤ 6 msgs, no tool_calls)
|
|
510
|
-
* - OR few messages, no tool_calls, body under a few tens of KB
|
|
511
|
-
* (even when the caller asked for 2000 tokens)
|
|
512
|
-
* A real grok chat (max_tokens 2000+ AND a long / tool-using transcript)
|
|
513
|
-
* still returns false so the floor can fire.
|
|
514
|
-
*
|
|
515
|
-
* `body` may be the raw Buffer/string or a parsed object. An optional
|
|
516
|
-
* `bodyLen` overrides stringify length when the caller still has the wire
|
|
517
|
-
* bytes (the proxy does).
|
|
518
|
-
*/
|
|
519
|
-
export function isTinyClassify(body, bodyLen) {
|
|
520
|
-
let parsed = body;
|
|
521
|
-
let len = bodyLen;
|
|
522
|
-
if (body == null) return false;
|
|
523
|
-
if (typeof body === 'string' || Buffer.isBuffer(body)) {
|
|
524
|
-
const buf = Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
525
|
-
len = buf.length;
|
|
526
|
-
try { parsed = JSON.parse(buf.toString('utf8')); } catch { return false; }
|
|
527
|
-
} else if (len == null) {
|
|
528
|
-
try { len = Buffer.byteLength(JSON.stringify(body)); } catch { return false; }
|
|
529
|
-
}
|
|
530
|
-
const mt = Number(parsed?.max_tokens);
|
|
531
|
-
if (!Number.isFinite(mt) || mt <= 0) return false;
|
|
532
|
-
const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
|
|
533
|
-
if (messages.some(messageHasToolCalls)) return false;
|
|
534
|
-
if (mt <= CLASSIFY_MAX_TOKENS && messages.length <= 6) return true;
|
|
535
|
-
return messages.length <= CLASSIFY_MAX_MSGS && len < CLASSIFY_MAX_BODY;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Ids that must never serve Claude Code / grokui AUTO's tiny yes/no classify.
|
|
540
|
-
* REASONING_MODEL_RE is the thinking floor; HEAVY_RE is the flagship set
|
|
541
|
-
* (opus/pro/max/…). opus-5 is openzoo's default session model and does NOT
|
|
542
|
-
* match the reasoning regex, so a catalog that lists opus before flash — or
|
|
543
|
-
* lists only opus + grok — used to pick opus as "first non-reasoner". That
|
|
544
|
-
* classify is a 402 handshake on a big model; AUTO's timeout then hard-blocks
|
|
545
|
-
* Bash instead of prompting.
|
|
546
|
-
*/
|
|
547
|
-
function isSlowClassifier(id) {
|
|
548
|
-
const s = String(id || '');
|
|
549
|
-
return REASONING_MODEL_RE.test(s) || HEAVY_RE.test(s);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Fast non-reasoning id that is actually on the zoo. Prefer an explicit
|
|
554
|
-
* OPENZOO_CLASSIFIER_MODEL, then flash, then haiku, then the first catalog
|
|
555
|
-
* id that is neither a reasoner nor a heavy/flagship (opus/pro/max/…).
|
|
556
|
-
*/
|
|
557
|
-
export function pickClassifierModel(ids, preferred = process.env.OPENZOO_CLASSIFIER_MODEL) {
|
|
558
|
-
if (!Array.isArray(ids) || !ids.length) return null;
|
|
559
|
-
if (preferred && ids.includes(preferred)) return preferred;
|
|
560
|
-
for (const id of CLASSIFIER_PREFS) {
|
|
561
|
-
if (ids.includes(id)) return id;
|
|
562
|
-
}
|
|
563
|
-
return ids.find((id) => !isSlowClassifier(id)) || null;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Raise max_tokens for a reasoning model. Returns { parsed, raised, from, to }.
|
|
568
|
-
* Does not itself decide whether a request is a classify — callers skip this
|
|
569
|
-
* when isTinyClassify is true. Real Grok/DeepSeek chats still need the floor:
|
|
570
|
-
* 4× a caller's 40 is 160, and those still come back blank.
|
|
571
|
-
*/
|
|
572
|
-
export function raiseReasoningMaxTokens(parsed, env = process.env) {
|
|
573
|
-
const mult = Number(env.OPENZOO_REASONING_MAX_TOKENS_X || 4);
|
|
574
|
-
const cap = Number(env.OPENZOO_REASONING_MAX_TOKENS_CAP || 32000);
|
|
575
|
-
const floor = Number(env.OPENZOO_REASONING_MIN_TOKENS || 4000);
|
|
576
|
-
const mdl = String(parsed?.model || '');
|
|
577
|
-
const mt = Number(parsed?.max_tokens);
|
|
578
|
-
if (mult > 1 && REASONING_MODEL_RE.test(mdl) && Number.isFinite(mt) && mt > 0 && mt < cap) {
|
|
579
|
-
const raised = Math.min(cap, Math.max(floor, Math.round(mt * mult)));
|
|
580
|
-
if (raised > mt) {
|
|
581
|
-
return { parsed: { ...parsed, max_tokens: raised }, raised: true, from: mt, to: raised };
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return { parsed, raised: false, from: mt, to: mt };
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Model + max_tokens policy for one chat body.
|
|
589
|
-
*
|
|
590
|
-
* Tiny classify: pin to a fast non-reasoning catalog id, leave max_tokens
|
|
591
|
-
* alone, ignore OPENZOO_DEFAULT_MODEL. Never fall back to `from` when that
|
|
592
|
-
* id is a reasoner or a heavy/flagship (the zoo default is opus-5). A
|
|
593
|
-
* catalog miss or an opus-only list used to keep the classify on opus-5
|
|
594
|
-
* and AUTO hard-blocked Bash. Everything else: resolveModel (which honours
|
|
595
|
-
* the default) then the reasoning floor.
|
|
596
|
-
*/
|
|
597
|
-
export function rewriteChatModel(parsed, ids, { bodyLen } = {}) {
|
|
598
|
-
const from = parsed?.model;
|
|
599
|
-
const len = bodyLen ?? (parsed == null ? 0 : Buffer.byteLength(JSON.stringify(parsed)));
|
|
600
|
-
if (isAutoModel(from) && !isTinyClassify(parsed, len)) {
|
|
601
|
-
return { parsed, tiny: false, auto: true, from, to: AUTO_MODEL_ID, raised: false };
|
|
602
|
-
}
|
|
603
|
-
if (isTinyClassify(parsed, len)) {
|
|
604
|
-
const picked = pickClassifierModel(ids);
|
|
605
|
-
// pickClassifierModel returns null on an empty catalog or a zoo that
|
|
606
|
-
// only lists reasoners/heavies. `(picked) || from` left those on
|
|
607
|
-
// anthropic/claude-opus-5 (openzoo's default). Pin to flash instead —
|
|
608
|
-
// never ship a classify body AUTO would time out and hard-block on.
|
|
609
|
-
const to = picked || (typeof from === 'string' && !isSlowClassifier(from) ? from : CLASSIFIER_PREFS[0]);
|
|
610
|
-
return {
|
|
611
|
-
parsed: (to && to !== from) ? { ...parsed, model: to } : parsed,
|
|
612
|
-
tiny: true,
|
|
613
|
-
from,
|
|
614
|
-
to,
|
|
615
|
-
raised: false,
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
if (typeof from !== 'string') {
|
|
619
|
-
return { parsed, tiny: false, from, to: from, raised: false };
|
|
620
|
-
}
|
|
621
|
-
const resolved = resolveModel(from, ids) || anthropicNativeAlias(from);
|
|
622
|
-
const next = resolved ? { ...parsed, model: resolved } : parsed;
|
|
623
|
-
const bump = raiseReasoningMaxTokens(next);
|
|
624
|
-
return {
|
|
625
|
-
parsed: bump.parsed,
|
|
626
|
-
tiny: false,
|
|
627
|
-
from,
|
|
628
|
-
to: next.model,
|
|
629
|
-
raised: bump.raised,
|
|
630
|
-
raisedFrom: bump.from,
|
|
631
|
-
raisedTo: bump.to,
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Rewrite the model field of any request body that has one.
|
|
637
|
-
* Returns null (send as-is) or { body, from, to, tiny?, raised? }. Any
|
|
638
|
-
* failure — bad JSON, unreachable catalog — returns null: this layer must
|
|
639
|
-
* never break a call that would have worked without it.
|
|
640
|
-
*
|
|
641
|
-
* Tiny classify is pinned here too, so OPENZOO_DEFAULT_MODEL cannot capture
|
|
642
|
-
* a 16-token yes/no even if a caller only goes through this helper.
|
|
643
|
-
*/
|
|
644
|
-
export async function maybeRewriteModel(bodyBuf) {
|
|
645
|
-
let body;
|
|
646
|
-
try { body = JSON.parse(bodyBuf.toString('utf8')); } catch { return null; }
|
|
647
|
-
if (typeof body?.model !== 'string') return null;
|
|
648
|
-
let ids;
|
|
649
|
-
try { ids = await zooModelIds(); } catch { return null; }
|
|
650
|
-
const policy = rewriteChatModel(body, ids, { bodyLen: bodyBuf.length });
|
|
651
|
-
if (!policy.tiny && !policy.raised && policy.to === body.model) return null;
|
|
652
|
-
return {
|
|
653
|
-
body: Buffer.from(JSON.stringify(policy.parsed)),
|
|
654
|
-
from: body.model,
|
|
655
|
-
to: policy.parsed.model,
|
|
656
|
-
tiny: policy.tiny,
|
|
657
|
-
raised: policy.raised,
|
|
658
|
-
raisedFrom: policy.raisedFrom,
|
|
659
|
-
raisedTo: policy.raisedTo,
|
|
660
|
-
};
|
|
661
|
-
}
|