openzoo 0.13.2 → 0.14.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/bin/openzoo.js +10 -2
- package/lib/anthropic.js +8 -1
- package/lib/proxy.js +7 -0
- package/lib/setup.js +59 -0
- package/package.json +1 -1
package/bin/openzoo.js
CHANGED
|
@@ -6,8 +6,11 @@ const HELP = `openzoo — local x402-paying proxy + MCP server for openzoo.fun
|
|
|
6
6
|
usage:
|
|
7
7
|
npx openzoo start the proxy: http://localhost:8402/v1 (keyless) PLUS a
|
|
8
8
|
public HTTPS url for cloud IDEs (key required, printed at start)
|
|
9
|
-
npx openzoo
|
|
10
|
-
|
|
9
|
+
npx openzoo cursor wire Cursor to the zoo (writes MCP config, prints the
|
|
10
|
+
base_url/key to paste into its AI settings)
|
|
11
|
+
npx openzoo vscode same, for VS Code
|
|
12
|
+
npx openzoo launch <cmd> [args] launch a TERMINAL Messages API client
|
|
13
|
+
(claude, aider...) already pointed at the zoo
|
|
11
14
|
npx openzoo mcp stdio MCP server (tools: zoo_ask, zoo_bind, zoo_models, zoo_wallet, zoo_contexts)
|
|
12
15
|
npx openzoo tunnel public-url-only mode (everything key-gated, no keyless localhost)
|
|
13
16
|
npx openzoo demo ~1M-token needle demo: direct refuses, the zoo answers
|
|
@@ -46,6 +49,11 @@ async function main() {
|
|
|
46
49
|
case 'start':
|
|
47
50
|
await (await import('../lib/proxy.js')).startProxy({ autoTunnel: true });
|
|
48
51
|
break;
|
|
52
|
+
case 'cursor':
|
|
53
|
+
case 'vscode':
|
|
54
|
+
// GUI editors read config files, not env vars — see lib/setup.js.
|
|
55
|
+
(await import('../lib/setup.js')).setupEditor(cmd);
|
|
56
|
+
break;
|
|
49
57
|
case 'mcp':
|
|
50
58
|
await (await import('../lib/mcp.js')).startMcp();
|
|
51
59
|
break;
|
package/lib/anthropic.js
CHANGED
|
@@ -144,7 +144,14 @@ export function openAIToAnthropic(data, requestedModel) {
|
|
|
144
144
|
* dead and RETRIES, and every retry is another payment.
|
|
145
145
|
*/
|
|
146
146
|
export function writeAnthropicSse(res, message, upstream) {
|
|
147
|
-
|
|
147
|
+
// x-accel-buffering: no keeps the cloudflare tunnel / nginx from buffering
|
|
148
|
+
// the stream and merging the tool_use frames a client parses incrementally.
|
|
149
|
+
const headers = {
|
|
150
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
151
|
+
'cache-control': 'no-cache',
|
|
152
|
+
'x-accel-buffering': 'no',
|
|
153
|
+
connection: 'keep-alive',
|
|
154
|
+
};
|
|
148
155
|
const settle = upstream?.headers?.get?.('x-payment-response');
|
|
149
156
|
if (settle) headers['x-payment-response'] = settle;
|
|
150
157
|
res.writeHead(200, headers);
|
package/lib/proxy.js
CHANGED
|
@@ -135,6 +135,13 @@ function serveAsSse(res, data, upstream) {
|
|
|
135
135
|
const headers = {
|
|
136
136
|
'content-type': 'text/event-stream; charset=utf-8',
|
|
137
137
|
'cache-control': 'no-cache',
|
|
138
|
+
// Tell any proxy in front of us (cloudflare quick tunnel, nginx) NOT to
|
|
139
|
+
// buffer the stream. Without this the tunnel accumulates the whole SSE
|
|
140
|
+
// body and releases it at once, which reorders/merges the tool_call frames
|
|
141
|
+
// an agent parses incrementally — observed as "provider-side tool-call
|
|
142
|
+
// protocol error" over the tunnel while localhost (no proxy) is fine.
|
|
143
|
+
'x-accel-buffering': 'no',
|
|
144
|
+
connection: 'keep-alive',
|
|
138
145
|
};
|
|
139
146
|
const settle = upstream?.headers?.get?.('x-payment-response');
|
|
140
147
|
if (settle) headers['x-payment-response'] = settle;
|
package/lib/setup.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npx openzoo cursor` / `npx openzoo vscode` — wire a GUI editor to the zoo.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS NOT `launch`: Cursor and VS Code are GUI apps, not processes you
|
|
5
|
+
* hand env vars to. `openzoo launch <cmd>` works for terminal harnesses (claude,
|
|
6
|
+
* aider) because they read ANTHROPIC_BASE_URL/OPENAI_BASE_URL at startup. An
|
|
7
|
+
* editor reads its own config instead, and Cursor keeps the provider base-URL +
|
|
8
|
+
* key in an ENCRYPTED internal store that no CLI can write — that half stays a
|
|
9
|
+
* GUI toggle. So this command does the half that IS automatable (MCP server
|
|
10
|
+
* registration, which is plain JSON) and prints the exact values to paste for
|
|
11
|
+
* the half that is not, instead of pretending it did everything.
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { config } from './config.js';
|
|
17
|
+
|
|
18
|
+
const CURSOR_MCP = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
19
|
+
const VSCODE_MCP = path.join(os.homedir(), '.vscode', 'mcp.json');
|
|
20
|
+
|
|
21
|
+
/** Merge our server into an existing mcp.json without clobbering the user's. */
|
|
22
|
+
function addMcpServer(file) {
|
|
23
|
+
let doc = {};
|
|
24
|
+
try { doc = JSON.parse(fs.readFileSync(file, 'utf8')) || {}; } catch { doc = {}; }
|
|
25
|
+
const key = doc.mcpServers ? 'mcpServers' : (doc.servers ? 'servers' : 'mcpServers');
|
|
26
|
+
doc[key] = doc[key] || {};
|
|
27
|
+
const existed = Boolean(doc[key].openzoo);
|
|
28
|
+
doc[key].openzoo = { command: 'npx', args: ['-y', 'openzoo@latest', 'mcp'] };
|
|
29
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
30
|
+
fs.writeFileSync(file, `${JSON.stringify(doc, null, 2)}\n`);
|
|
31
|
+
return { file, existed };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function setupEditor(which = 'cursor') {
|
|
35
|
+
const base = `http://localhost:${config.port}/v1`;
|
|
36
|
+
const file = which === 'vscode' ? VSCODE_MCP : CURSOR_MCP;
|
|
37
|
+
const { existed } = addMcpServer(file);
|
|
38
|
+
|
|
39
|
+
console.log(`openzoo → ${which}`);
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(`1. MCP: ${existed ? 'updated' : 'added'} "openzoo" in ${file}`);
|
|
42
|
+
console.log(' tools: zoo_bind, zoo_ask, zoo_models, zoo_wallet, zoo_contexts');
|
|
43
|
+
console.log(' (restart the editor to pick it up)');
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log('2. Model routing — paste these into the editor\'s AI settings.');
|
|
46
|
+
console.log(' Cursor keeps provider settings in an encrypted store, so this');
|
|
47
|
+
console.log(' part cannot be scripted; it is two fields:');
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(` Override OpenAI Base URL : ${base}`);
|
|
50
|
+
console.log(' OpenAI API Key : sk-openzoo (any value; x402 pays, not keys)');
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(' Then add a model the zoo serves (Cursor validates the name):');
|
|
53
|
+
console.log(' deepseek/deepseek-v4-pro-0813 ← cheap, stateless tools, recommended');
|
|
54
|
+
console.log(' openai/gpt-5.6-sol-pro ← flagship, ~34x the output cost');
|
|
55
|
+
console.log(' Turn OFF the built-in models (Composer/GPT/Cursor-Grok) while the');
|
|
56
|
+
console.log(' override is on — those only resolve against Cursor\'s own backend.');
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(`3. Make sure the proxy is running: npx openzoo (${base})`);
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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",
|