openzoo 0.50.7 → 0.50.8

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.
Files changed (2) hide show
  1. package/lib/proxy.js +46 -5
  2. package/package.json +1 -1
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 && isHarnessAliasId(decodeURIComponent(probe[1]))) {
622
- res.writeHead(200, { 'content-type': 'application/json' });
623
- res.end(JSON.stringify({ id: decodeURIComponent(probe[1]), object: 'model', owned_by: 'openzoo-alias' }));
624
- return;
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.7",
3
+ "version": "0.50.8",
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",