openzoo 0.23.1 → 0.25.0
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/proxy.js +6 -0
- package/lib/setup.js +80 -0
- package/package.json +1 -1
package/lib/proxy.js
CHANGED
|
@@ -285,6 +285,10 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
285
285
|
// origin, not off whether the URL exists yet, so there is no startup window
|
|
286
286
|
// where public traffic slips through ungated.
|
|
287
287
|
let tunnelGate = null;
|
|
288
|
+
// How many chat requests actually ARRIVED. The single number that answers
|
|
289
|
+
// "is the editor really routing through us?" — an editor that silently keeps
|
|
290
|
+
// using its own backend leaves this at 0 while looking perfectly healthy.
|
|
291
|
+
let servedRequests = 0;
|
|
288
292
|
let tunnelError = null;
|
|
289
293
|
|
|
290
294
|
const server = http.createServer(async (req, res) => {
|
|
@@ -343,6 +347,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
343
347
|
yourEndpoint: self,
|
|
344
348
|
reachedVia: viaTunnel ? 'public tunnel' : 'localhost',
|
|
345
349
|
publicTunnel: tunnelGate?.publicUrl ? `${tunnelGate.publicUrl}/v1` : null,
|
|
350
|
+
servedRequests,
|
|
346
351
|
mcp: `${self.replace(/\/v1$/, '')}/mcp`,
|
|
347
352
|
upstream: config.apiBase,
|
|
348
353
|
payment: 'x402 per request from the operator\'s local burner wallet — no API key, no account',
|
|
@@ -467,6 +472,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
467
472
|
// carries a model field, not just chat/completions, so /completions,
|
|
468
473
|
// /responses and future shapes all work. Never silent.
|
|
469
474
|
let wantsStream = false;
|
|
475
|
+
if ((req.url || '').includes('/chat/completions') && req.method === 'POST') servedRequests += 1;
|
|
470
476
|
if (rewritablePath(req.method, req.url)) {
|
|
471
477
|
const rw = await maybeRewriteModel(bodyBuf);
|
|
472
478
|
if (rw) {
|
package/lib/setup.js
CHANGED
|
@@ -193,8 +193,48 @@ async function proxyUp(base) {
|
|
|
193
193
|
} catch { return false; }
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
|
|
197
|
+
/** Facts about THIS run, before anything can go wrong silently. */
|
|
198
|
+
async function printStartupDiagnostic(base, which) {
|
|
199
|
+
const q = (fn, dflt = '?') => { try { return fn(); } catch { return dflt; } };
|
|
200
|
+
let version = '?';
|
|
201
|
+
try {
|
|
202
|
+
const here = path.dirname(new URL(import.meta.url).pathname);
|
|
203
|
+
version = JSON.parse(fs.readFileSync(path.join(here, '..', 'package.json'), 'utf8')).version;
|
|
204
|
+
} catch { /* keep ? */ }
|
|
205
|
+
|
|
206
|
+
const picked = q(() => pickEditor(which), null);
|
|
207
|
+
let hosts = 'n/a';
|
|
208
|
+
try {
|
|
209
|
+
const { isBlocked, BACKEND_HOSTS } = await import('./hosts.js');
|
|
210
|
+
hosts = isBlocked() ? `blocked (${BACKEND_HOSTS.join(', ')})` : 'NOT blocked';
|
|
211
|
+
} catch { /* n/a */ }
|
|
212
|
+
|
|
213
|
+
const portBusy = await proxyUp(base);
|
|
214
|
+
|
|
215
|
+
console.log('openzoo diagnostic');
|
|
216
|
+
console.log(` version : ${version} node ${process.version} ${process.platform}/${process.arch}`);
|
|
217
|
+
console.log(` port ${config.port} : ${portBusy ? 'ALREADY SERVING (an older proxy may still be running — kill it if this build is newer)' : 'free'}`);
|
|
218
|
+
console.log(` editor : ${picked ? `${picked.which} @ ${picked.cmd}` : 'NONE FOUND'}`);
|
|
219
|
+
console.log(` running : ${picked ? (q(() => editorRunning(picked.which), false) ? 'yes — will be quit so settings stick' : 'no') : '-'}`);
|
|
220
|
+
console.log(` backend : ${hosts}`);
|
|
221
|
+
console.log(` upstream : ${config.apiBase}`);
|
|
222
|
+
const envs = ['OPENZOO_DEFAULT_MODEL', 'OPENZOO_EDITOR_MODELS', 'OPENZOO_EDITOR_MAP', 'OPENZOO_NO_LABELS', 'OPENZOO_NO_TUNNEL']
|
|
223
|
+
.filter((k) => process.env[k]).map((k) => `${k}=${process.env[k]}`);
|
|
224
|
+
if (envs.length) console.log(` env : ${envs.join(' ')}`);
|
|
225
|
+
console.log('');
|
|
226
|
+
}
|
|
227
|
+
|
|
196
228
|
export async function setupEditor(which, target) {
|
|
197
229
|
const base = `http://localhost:${config.port}/v1`;
|
|
230
|
+
|
|
231
|
+
// STARTUP DIAGNOSTIC. Printed unconditionally because every hard bug tonight
|
|
232
|
+
// was invisible from the outside: a STALE BUILD writing old config (npm caches
|
|
233
|
+
// metadata and silently serves an older version), a hosts block that never
|
|
234
|
+
// applied because the sudo prompt was skipped, an editor that was still running
|
|
235
|
+
// so it clobbered the write, a tunnel that printed a URL and never served.
|
|
236
|
+
// One block up front turns each of those from a guess into a fact.
|
|
237
|
+
await printStartupDiagnostic(base, which);
|
|
198
238
|
const mcpFile = addMcpServer(MCP_FILES[which] || MCP_FILES.cursor);
|
|
199
239
|
|
|
200
240
|
// 1. PROXY + TUNNEL. Start in-process if nothing is listening, so the user
|
|
@@ -360,6 +400,18 @@ export async function setupEditor(which, target) {
|
|
|
360
400
|
? ' pinned -> yes (the editor re-syncs these from its account; a DB trigger re-applies them)'
|
|
361
401
|
: ` pinned -> NO (${pin?.error || 'unavailable'}) — the editor may revert these on launch`);
|
|
362
402
|
console.log(' verify: send one message, watch for "paid $0.0… · rail solana · tx …" here.');
|
|
403
|
+
// READ-BACK, not what we intended to write. A pin trigger or a running editor
|
|
404
|
+
// can rewrite this between the write and the launch, and that silent revert
|
|
405
|
+
// is exactly what made the editor point at a dead tunnel for hours.
|
|
406
|
+
try {
|
|
407
|
+
const back = writeEditorProviderConfig(target0, { baseUrl: editorBase, models, apiKey: tunnelKey })?.verified;
|
|
408
|
+
console.log(' db now : baseUrl=' + (back?.openAIBaseUrl || '?'));
|
|
409
|
+
console.log(' models =' + (Array.isArray(back?.availableAPIKeyModels)
|
|
410
|
+
? back.availableAPIKeyModels.map((m) => m.name).join(', ') : '?'));
|
|
411
|
+
if (back?.openAIBaseUrl && back.openAIBaseUrl !== editorBase) {
|
|
412
|
+
console.log(' WARNING : the database does NOT match what we wrote — something is reverting it.');
|
|
413
|
+
}
|
|
414
|
+
} catch (e) { console.log(' db now : could not read back (' + e.message + ')'); }
|
|
363
415
|
}
|
|
364
416
|
|
|
365
417
|
// 2b. THE BACKEND BLOCK. Pinning the database is not sufficient on its own:
|
|
@@ -416,6 +468,34 @@ export async function setupEditor(which, target) {
|
|
|
416
468
|
console.error(`could not launch ${picked.which}: ${e.message}`);
|
|
417
469
|
});
|
|
418
470
|
child.unref();
|
|
471
|
+
|
|
472
|
+
// DID IT ACTUALLY ROUTE? The editor can look perfectly configured and still
|
|
473
|
+
// serve every message from its OWN backend — it answers normally, so a reply
|
|
474
|
+
// proves nothing. This watches the only number that does: chat requests that
|
|
475
|
+
// actually ARRIVED here. Costs nothing and ends the guessing.
|
|
476
|
+
(async () => {
|
|
477
|
+
const seen = async () => {
|
|
478
|
+
try {
|
|
479
|
+
const r = await fetch(`${base}/info`, { signal: AbortSignal.timeout(3000) });
|
|
480
|
+
return (await r.json())?.servedRequests ?? 0;
|
|
481
|
+
} catch { return 0; }
|
|
482
|
+
};
|
|
483
|
+
const before = await seen();
|
|
484
|
+
for (let i = 0; i < 40; i++) { // ~2 min
|
|
485
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
486
|
+
const now = await seen();
|
|
487
|
+
if (now > before) {
|
|
488
|
+
console.log(`\nROUTING CONFIRMED — ${now - before} request(s) reached the zoo.`);
|
|
489
|
+
console.log(' (a receipt line above shows what each one paid)');
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
console.log('\nNOT ROUTING — 0 requests reached the zoo in 2 minutes.');
|
|
494
|
+
console.log(' The editor is answering from its OWN backend. Check, in order:');
|
|
495
|
+
console.log(' 1. did you QUIT the editor fully before this ran? it caches config at launch');
|
|
496
|
+
console.log(' 2. Settings -> Models -> "Override OpenAI Base URL" toggle is ON');
|
|
497
|
+
console.log(' 3. the model you picked is one of the ids written above');
|
|
498
|
+
})();
|
|
419
499
|
// Keep this process alive when we own the proxy — killing it would kill the
|
|
420
500
|
// zoo the editor was just pointed at.
|
|
421
501
|
if (publicUrl || !(await proxyUp(base))) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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",
|