openzoo 0.50.7 → 0.50.9
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/models.js +33 -1
- package/lib/proxy.js +46 -5
- package/package.json +1 -1
package/lib/models.js
CHANGED
|
@@ -366,9 +366,41 @@ export function augmentModelList(payload, { aliases: withAliases = true } = {})
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
// FAMILY SHORTCUTS — `/model grok`, `/model deepseek`, `/model gemini`.
|
|
370
|
+
//
|
|
371
|
+
// PROVEN NECESSARY on a live VM: the proxy's fuzzy /v1/models/<id> probe
|
|
372
|
+
// answered `grok -> x-ai/grok-4.6` with a 200, and Claude Code v2.1.246
|
|
373
|
+
// REFUSED THE ID ANYWAY — this client validates /model purely against the
|
|
374
|
+
// list it fetched at startup and never probes per-id. So the only fuzzy a
|
|
375
|
+
// list-validating harness can have is fuzzy that is already IN the list.
|
|
376
|
+
//
|
|
377
|
+
// One row per vendor family, resolved through the same scorer as the POST
|
|
378
|
+
// path, so `grok` lands on the family flagship rather than a hardcoded pick.
|
|
379
|
+
const FAMILY_TOKENS = ['gpt', 'claude', 'gemini', 'grok', 'deepseek', 'qwen',
|
|
380
|
+
'mistral', 'llama', 'glm', 'kimi', 'minimax', 'command', 'nova', 'sonar'];
|
|
381
|
+
const family = [];
|
|
382
|
+
if (withAliases) {
|
|
383
|
+
const ids = data.map((m) => m.id);
|
|
384
|
+
const taken2 = new Set([...have, ...aliases.map((a) => a.id), ...bare.map((b) => b.id)]);
|
|
385
|
+
for (const tok of FAMILY_TOKENS) {
|
|
386
|
+
if (taken2.has(tok)) continue;
|
|
387
|
+
const target = data.find((m) => m.id === resolveModel(tok, ids));
|
|
388
|
+
if (!target) continue;
|
|
389
|
+
taken2.add(tok);
|
|
390
|
+
family.push({
|
|
391
|
+
id: tok,
|
|
392
|
+
object: 'model',
|
|
393
|
+
owned_by: 'openzoo-alias',
|
|
394
|
+
...(target.context_length ? { context_length: target.context_length, context_window: target.context_window ?? target.context_length } : {}),
|
|
395
|
+
...(target.pricing ? { pricing: target.pricing } : {}),
|
|
396
|
+
served_by: target.id,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
369
401
|
// Auto is no longer published: routing lives on the backend, and a shim
|
|
370
402
|
// that advertises openzoo/auto would have to route it.
|
|
371
|
-
return { ...payload, object: payload?.object || 'list', data: [...data, ...aliases, ...bare] };
|
|
403
|
+
return { ...payload, object: payload?.object || 'list', data: [...data, ...aliases, ...bare, ...family] };
|
|
372
404
|
}
|
|
373
405
|
|
|
374
406
|
/**
|
package/lib/proxy.js
CHANGED
|
@@ -11,7 +11,23 @@ import { PayClient, QuoteTooHighError, UnderfundedError } from './pay.js';
|
|
|
11
11
|
import { tokenBalance } from './x402.js';
|
|
12
12
|
import { evmTokenBalance } from './evm.js';
|
|
13
13
|
import { autoContext } from './autobind.js';
|
|
14
|
-
import { modelsListForRequest, isHarnessAliasId } from './models.js';
|
|
14
|
+
import { modelsListForRequest, isHarnessAliasId, resolveModel, quoteableRows } from './models.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Quoteable catalog ids, cached 5 minutes, for the fuzzy /v1/models/<id> probe.
|
|
18
|
+
* Free read, but harnesses probe on every /model keystroke confirmation —
|
|
19
|
+
* without a cache each probe is an upstream round trip.
|
|
20
|
+
*/
|
|
21
|
+
let catalogIdsMemo = { at: 0, ids: [] };
|
|
22
|
+
async function catalogIdsCached(modelsUrl, headers) {
|
|
23
|
+
if (Date.now() - catalogIdsMemo.at < 5 * 60_000 && catalogIdsMemo.ids.length) return catalogIdsMemo.ids;
|
|
24
|
+
const r = await fetch(modelsUrl, { headers, signal: AbortSignal.timeout(8000) });
|
|
25
|
+
if (!r.ok) throw new Error(`catalog ${r.status}`);
|
|
26
|
+
const payload = await r.json();
|
|
27
|
+
const ids = quoteableRows(payload?.data).map((m) => m.id);
|
|
28
|
+
if (ids.length) catalogIdsMemo = { at: Date.now(), ids };
|
|
29
|
+
return ids;
|
|
30
|
+
}
|
|
15
31
|
import { withNamespace } from './namespace.js';
|
|
16
32
|
import { loadSessionSpend, saveSessionSpend } from './session.js';
|
|
17
33
|
import { creditBalance, quotedPrices } from './info.js';
|
|
@@ -618,10 +634,35 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
618
634
|
return;
|
|
619
635
|
}
|
|
620
636
|
const probe = req.method === 'GET' && /^\/v1\/models\/(.+)$/.exec(path);
|
|
621
|
-
if (probe
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
637
|
+
if (probe) {
|
|
638
|
+
const wanted = decodeURIComponent(probe[1]);
|
|
639
|
+
if (isHarnessAliasId(wanted)) {
|
|
640
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
641
|
+
res.end(JSON.stringify({ id: wanted, object: 'model', owned_by: 'openzoo-alias' }));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
// FUZZY, LIKE THE REST OF THE PIPELINE. The gateway already
|
|
645
|
+
// similarity-rewrites whatever model id lands in a POST body — but a
|
|
646
|
+
// harness never gets that far, because it validates the id CLIENT-SIDE
|
|
647
|
+
// with this exact probe first. Answering 200 only for a hand-typed
|
|
648
|
+
// alias list meant `/model deepseek pro` and `/model
|
|
649
|
+
// deepseek-ai/deepseek-v4-pro` were both refused at the door while the
|
|
650
|
+
// backend would have served either happily. The door must be exactly as
|
|
651
|
+
// forgiving as the room behind it.
|
|
652
|
+
try {
|
|
653
|
+
const ids = await catalogIdsCached(url.replace(/\/v1\/models\/.*$/, '/v1/models'), init.headers);
|
|
654
|
+
if (ids.includes(wanted)) {
|
|
655
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
656
|
+
res.end(JSON.stringify({ id: wanted, object: 'model' }));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const resolved = resolveModel(wanted, ids);
|
|
660
|
+
if (resolved) {
|
|
661
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
662
|
+
res.end(JSON.stringify({ id: wanted, object: 'model', owned_by: 'openzoo-alias', served_by: resolved }));
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
} catch { /* catalog unreachable — fall through to the paid path below */ }
|
|
625
666
|
}
|
|
626
667
|
|
|
627
668
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.9",
|
|
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",
|