openzoo 0.9.4 → 0.9.6
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 +2 -2
- package/lib/config.js +3 -1
- package/lib/proxy.js +33 -8
- package/lib/tunnel.js +7 -4
- package/package.json +1 -1
package/bin/openzoo.js
CHANGED
|
@@ -30,12 +30,12 @@ env:
|
|
|
30
30
|
OPENZOO_RPC (mainnet-beta) OPENZOO_TOKEN (402 rail preference) OPENZOO_WALLET (~/.openzoo/wallet.json)
|
|
31
31
|
OPENZOO_RAIL (unset — force a rail: solana | base | robinhood)
|
|
32
32
|
OPENZOO_BASE_RPC (https://mainnet.base.org) OPENZOO_RH_RPC (rpc.mainnet.chain.robinhood.com)
|
|
33
|
-
OPENZOO_MAX_USD_PER_CALL (
|
|
33
|
+
OPENZOO_MAX_USD_PER_CALL (unset — NO per-call ceiling; set to add one) OPENZOO_DEMO_MAX_USD (0.01)
|
|
34
34
|
OPENZOO_CONTEXT_MIN_CHARS (16384 — bodies bigger than this bind once + reuse)
|
|
35
35
|
OPENZOO_NO_CONTEXT_CACHE (0 — set 1 to always ship the full body)
|
|
36
36
|
OPENZOO_ENABLE_RH (0 — let DEFAULT selection fall through to the Robinhood rail;
|
|
37
37
|
OPENZOO_RAIL=robinhood forces it without this)
|
|
38
|
-
OPENZOO_TUNNEL_MAX_USD (
|
|
38
|
+
OPENZOO_TUNNEL_MAX_USD (unset — NO public-url session ceiling; set to add one) OPENZOO_TUNNEL_TOKEN (pin the api key)
|
|
39
39
|
OPENZOO_NO_TUNNEL (0 — set 1 for localhost-only, no public url)`;
|
|
40
40
|
|
|
41
41
|
async function main() {
|
package/lib/config.js
CHANGED
|
@@ -15,7 +15,9 @@ export const config = {
|
|
|
15
15
|
rhRpcUrl: process.env.OPENZOO_RH_RPC || 'https://rpc.mainnet.chain.robinhood.com',
|
|
16
16
|
walletPath: process.env.OPENZOO_WALLET || path.join(os.homedir(), '.openzoo', 'wallet.json'),
|
|
17
17
|
// Refuse to auto-pay any single 402 quote above this many USD (extra.billedUsd).
|
|
18
|
-
|
|
18
|
+
// UNCAPPED by default (user directive: no maximums — a giant bound-corpus
|
|
19
|
+
// call is a legitimate call). Set OPENZOO_MAX_USD_PER_CALL to add a ceiling.
|
|
20
|
+
maxUsdPerCall: process.env.OPENZOO_MAX_USD_PER_CALL ? Number(process.env.OPENZOO_MAX_USD_PER_CALL) : Infinity,
|
|
19
21
|
// Demo will only actually pay if the quote is at or below this.
|
|
20
22
|
demoMaxUsd: Number(process.env.OPENZOO_DEMO_MAX_USD || 0.01),
|
|
21
23
|
};
|
package/lib/proxy.js
CHANGED
|
@@ -271,12 +271,37 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
271
271
|
}
|
|
272
272
|
if (viaTunnel) {
|
|
273
273
|
const got = (req.headers.authorization || '').replace(/^Bearer\s+/i, '').trim();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
const authed = got === tunnelGate.token;
|
|
275
|
+
const p = (req.url || '').split('?')[0];
|
|
276
|
+
// DISCOVERY IS FREE. An agent probing this URL cold should be able to
|
|
277
|
+
// work out what it is and what to ask its operator for — a bare 401 on
|
|
278
|
+
// every path just sends it spelunking through the operator's machine.
|
|
279
|
+
// Reads that cost nothing and leak nothing (the catalog is public on
|
|
280
|
+
// the zoo anyway) go through without the key; money paths stay gated.
|
|
281
|
+
if (!authed) {
|
|
282
|
+
if (req.method === 'GET' && (p === '/' || p === '/v1' || p === '/v1/info')) {
|
|
283
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
284
|
+
res.end(JSON.stringify({
|
|
285
|
+
name: 'openzoo proxy (public tunnel)',
|
|
286
|
+
what: 'OpenAI-compatible pay-per-call proxy — the operator\'s wallet pays per request via x402; no account, no signup.',
|
|
287
|
+
authentication: 'All POST endpoints require `Authorization: Bearer <api key>`. The key is printed in the operator\'s terminal at proxy startup — ask them for it. It is NOT guessable.',
|
|
288
|
+
endpoints: {
|
|
289
|
+
'GET /v1/models': 'model catalog with pricing + context_length — no key needed',
|
|
290
|
+
'GET /v1/models/{id}': 'single-model probe — no key needed',
|
|
291
|
+
'POST /v1/chat/completions': 'chat (streaming supported, any model id — unknown ids are matched to the nearest served model) — key required',
|
|
292
|
+
},
|
|
293
|
+
docs: 'https://openzoo.fun · https://www.npmjs.com/package/openzoo',
|
|
294
|
+
}, null, 2));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const freeRead = req.method === 'GET' && (p === '/v1/models' || p.startsWith('/v1/models/'));
|
|
298
|
+
if (!freeRead) {
|
|
299
|
+
log(`public url: 401 ${req.method} ${req.url}`);
|
|
300
|
+
jsonErr(res, 401, 'unauthorized: this openzoo public URL requires the api key printed at the operator\'s proxy startup — GET / for discovery, GET /v1/models is open');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
278
303
|
}
|
|
279
|
-
if (tunnelSpent >= tunnelGate.sessionMaxUsd) {
|
|
304
|
+
if (authed && tunnelSpent >= tunnelGate.sessionMaxUsd) {
|
|
280
305
|
jsonErr(res, 402, `openzoo public-URL session cap reached ($${tunnelGate.sessionMaxUsd}) — restart the proxy or raise OPENZOO_TUNNEL_MAX_USD`);
|
|
281
306
|
return;
|
|
282
307
|
}
|
|
@@ -515,7 +540,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
515
540
|
try {
|
|
516
541
|
const { ensureCloudflared, startCloudflared, mintToken } = await import('./tunnel.js');
|
|
517
542
|
const token = mintToken();
|
|
518
|
-
const cap = Number(process.env.OPENZOO_TUNNEL_MAX_USD
|
|
543
|
+
const cap = process.env.OPENZOO_TUNNEL_MAX_USD ? Number(process.env.OPENZOO_TUNNEL_MAX_USD) : Infinity;
|
|
519
544
|
const bin = await ensureCloudflared((m) => log(m));
|
|
520
545
|
const { url, proc } = await startCloudflared(bin, config.port, log);
|
|
521
546
|
tunnelGate = { token, sessionMaxUsd: cap };
|
|
@@ -527,8 +552,8 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
527
552
|
log('cloud IDE / remote harness? use the public URL (they cannot reach localhost):');
|
|
528
553
|
log(` base_url = ${url}/v1`);
|
|
529
554
|
log(` api_key = ${token}`);
|
|
530
|
-
log(` (key REQUIRED on the public URL — it spends this wallet; capped at $${cap.toFixed(2)}/session,`);
|
|
531
|
-
log('
|
|
555
|
+
log(` (key REQUIRED on the public URL — it spends this wallet; ${Number.isFinite(cap) ? `capped at $${cap.toFixed(2)}/session` : 'NO session cap — OPENZOO_TUNNEL_MAX_USD adds one'},`);
|
|
556
|
+
log(' OPENZOO_NO_TUNNEL=1 for localhost-only)');
|
|
532
557
|
} catch (err) {
|
|
533
558
|
log(`public URL unavailable (${err.message}) — localhost still works; OPENZOO_NO_TUNNEL=1 hides this line`);
|
|
534
559
|
}
|
package/lib/tunnel.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* front of your money, so tunnel mode is the ONE mode where the api key is real:
|
|
17
17
|
* - a random bearer token is minted per session and REQUIRED (401 otherwise),
|
|
18
18
|
* checked before anything is forwarded, quoted, or paid;
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
19
|
+
* - caps are OPT-IN (user directive: no maximums by default) — set
|
|
20
|
+
* OPENZOO_MAX_USD_PER_CALL and/or OPENZOO_TUNNEL_MAX_USD to add them;
|
|
21
21
|
* - every tunnel-served request is logged with its running spend.
|
|
22
22
|
*/
|
|
23
23
|
import { spawn } from 'node:child_process';
|
|
@@ -119,7 +119,8 @@ export async function runTunnel() {
|
|
|
119
119
|
const { config } = await import('./config.js');
|
|
120
120
|
|
|
121
121
|
const token = mintToken();
|
|
122
|
-
|
|
122
|
+
// Uncapped unless the user sets a ceiling — the required token is the gate.
|
|
123
|
+
const sessionCap = process.env.OPENZOO_TUNNEL_MAX_USD ? Number(process.env.OPENZOO_TUNNEL_MAX_USD) : null;
|
|
123
124
|
|
|
124
125
|
// The proxy enforces the gate itself: nothing is forwarded, quoted or paid
|
|
125
126
|
// without the bearer token, and the session ceiling stops spend cold.
|
|
@@ -136,7 +137,9 @@ export async function runTunnel() {
|
|
|
136
137
|
console.log(' ┌─────────────────────────────────────────────────────────────');
|
|
137
138
|
console.log(' │ THIS URL SPENDS REAL MONEY FROM YOUR WALLET.');
|
|
138
139
|
console.log(' │ The token below is the only thing protecting it.');
|
|
139
|
-
console.log(
|
|
140
|
+
console.log(sessionCap != null
|
|
141
|
+
? ` │ Session ceiling: $${sessionCap.toFixed(2)} — then it stops paying.`
|
|
142
|
+
: ' │ NO session ceiling — OPENZOO_TUNNEL_MAX_USD adds one.');
|
|
140
143
|
console.log(' │ Ctrl-C when you are done; the URL dies with this process.');
|
|
141
144
|
console.log(' └─────────────────────────────────────────────────────────────');
|
|
142
145
|
console.log('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
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",
|