openzoo 0.49.8 → 0.49.10
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/README.md +40 -7
- package/lib/claudecode.js +231 -0
- package/lib/grokui.mjs +850 -175
- package/lib/launch.js +72 -73
- package/lib/livestatus.js +4 -2
- package/lib/modelroute/README.md +1 -0
- package/lib/modelroute/catalog.json +1 -0
- package/lib/modelroute/outcomes.json +1566 -0
- package/lib/modelroute/router.json +1 -0
- package/lib/modelroute.js +737 -0
- package/lib/models.js +191 -62
- package/lib/package.json +3 -0
- package/lib/podagent.mjs +73 -27
- package/lib/proxy.js +141 -7
- package/lib/racesettle.js +23 -2
- package/lib/responses-stream.js +176 -0
- package/lib/runguard.js +31 -0
- package/lib/spill.js +9 -1
- package/lib/think.js +126 -0
- package/package.json +4 -3
- package/vendor/modelroute/CURRENT_STATE.md +132 -0
- package/vendor/modelroute/HANDOFF.md +159 -0
- package/vendor/modelroute/catalog.json +1 -0
- package/vendor/modelroute/holographic_modelroute.py +809 -0
- package/vendor/modelroute/outcomes.json +1566 -0
- package/vendor/modelroute/router.json +1 -0
package/README.md
CHANGED
|
@@ -23,23 +23,56 @@ paid $0.002137 (9.5× cheaper than direct) · rail solana · tx 5Kd…
|
|
|
23
23
|
|
|
24
24
|
**Cursor** (Settings → Models → OpenAI API): set *Override OpenAI Base URL* to `http://localhost:8402/v1`, API key `sk-openzoo`. (Cursor Hobby can't BYOK; Pro can.)
|
|
25
25
|
|
|
26
|
-
**Claude Code (
|
|
26
|
+
**Claude Code / grokui Auto** — `openzoo claude` sets the Anthropic API key + base URL to the local OpenZoo proxy (x402 pay-per-call). You do **not** need to authenticate Claude first. grokui orange Auto is this harness, not a `RUN:` text parser. PATH `~/.local/bin` is required on Mac so `claude` is found.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
`GET /v1/models` is the live OpenRouter catalog **after** dropping `:batch`, `$0` / missing prices, and `openzoo-*` twins. Claude Code's `/model` picker (Anthropic-shaped GET) is a short list: current Opus/Sonnet/Haiku-class + a few real gateway models + `openzoo/auto`.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
Mac:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
curl -fsSL https://claude.ai/install.sh | bash
|
|
34
|
+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash
|
|
35
|
+
. "$HOME/.nvm/nvm.sh"
|
|
36
|
+
nvm install 24
|
|
37
|
+
npm i -g openzoo
|
|
38
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
|
|
39
|
+
openzoo claude
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Windows — official Claude install, then nvm-windows (https://github.com/coreybutler/nvm-windows — `nvm-setup.exe`). Do not use the unix nvm curl on Windows. Do not source `~/.zshrc`.
|
|
43
|
+
|
|
44
|
+
PowerShell:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
irm https://claude.ai/install.ps1 | iex
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
CMD:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
curl -fsSL https://downloads.claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then nvm-windows:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
nvm install 24
|
|
60
|
+
nvm use 24
|
|
61
|
+
npm i -g openzoo
|
|
62
|
+
openzoo claude
|
|
32
63
|
```
|
|
33
64
|
|
|
34
|
-
Leave a healthy `:8402` sidecar alone. Manual equivalent:
|
|
65
|
+
Leave a healthy `:8402` sidecar alone. Manual equivalent / check that the list is quoteable:
|
|
35
66
|
|
|
36
67
|
```bash
|
|
37
68
|
unset ANTHROPIC_API_KEY
|
|
38
69
|
export ANTHROPIC_BASE_URL=http://localhost:8402/v1
|
|
39
70
|
export ANTHROPIC_AUTH_TOKEN=sk-openzoo
|
|
40
71
|
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
|
|
41
|
-
curl -s "$ANTHROPIC_BASE_URL/models"
|
|
42
|
-
|
|
72
|
+
curl -s "$ANTHROPIC_BASE_URL/models" | jq '[.data[].id] | map(select(test(":batch") or startswith("openzoo-")))'
|
|
73
|
+
# -> [] (no :batch, no openzoo-* clones)
|
|
74
|
+
curl -s -H 'anthropic-version: 2023-06-01' "$ANTHROPIC_BASE_URL/models" | jq '[.data[].id]'
|
|
75
|
+
# -> short picker (opus/sonnet/haiku + a few gateway ids + openzoo/auto)
|
|
43
76
|
```
|
|
44
77
|
|
|
45
78
|
**Any OpenAI-env tool:**
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive Claude Code (`claude --print --output-format stream-json`) as grokui
|
|
3
|
+
* Auto. This is the same harness `openzoo claude` launches: env from
|
|
4
|
+
* claudeZooEnv (ANTHROPIC_BASE_URL + AUTH_TOKEN at :8402, no Anthropic
|
|
5
|
+
* API key). Tools are Claude Code's own (Bash, Read, Write, Edit, Glob, Grep).
|
|
6
|
+
*
|
|
7
|
+
* Do not re-parse RUN:/WRITE:/DONE: here. Stream-json is Claude Code's
|
|
8
|
+
* official print protocol; we only fold those events onto the canvas.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { claudeZooEnv, resolveClaudeCli } from './launch.js';
|
|
12
|
+
|
|
13
|
+
export const AUTO_CLAUDE_SYSTEM = 'You are grokui Auto, paid per call through the local OpenZoo proxy (x402 on :8402). '
|
|
14
|
+
+ 'Use your native tools (Bash, Read, Write, Edit, Glob, Grep) to do the work in this working directory. '
|
|
15
|
+
+ 'Do not curl localhost:8402/v1/chat/completions and do not emit RUN:/WRITE:/DONE: text directives — you already have real tools. '
|
|
16
|
+
+ 'Do not ask the user to type continue.';
|
|
17
|
+
|
|
18
|
+
export const CLAUDE_MISSING = 'claude CLI not found. Auto is the Claude Code harness via OpenZoo — install it with: '
|
|
19
|
+
+ 'curl -fsSL https://claude.ai/install.sh | bash (then ensure ~/.local/bin is on PATH). '
|
|
20
|
+
+ 'No Anthropic login: `openzoo claude` already points ANTHROPIC_BASE_URL at the local proxy.';
|
|
21
|
+
|
|
22
|
+
let runnerOverride = null;
|
|
23
|
+
export function setClaudeRunnerForTest(fn) {
|
|
24
|
+
runnerOverride = typeof fn === 'function' ? fn : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function claudePrintArgs({ prompt, sessionId, model, system } = {}) {
|
|
28
|
+
const args = [
|
|
29
|
+
'--print',
|
|
30
|
+
'--verbose',
|
|
31
|
+
'--output-format', 'stream-json',
|
|
32
|
+
'--permission-mode', 'bypassPermissions',
|
|
33
|
+
];
|
|
34
|
+
const sys = system === undefined ? AUTO_CLAUDE_SYSTEM : system;
|
|
35
|
+
if (sys) args.push('--append-system-prompt', sys);
|
|
36
|
+
if (sessionId) args.push('--resume', String(sessionId));
|
|
37
|
+
if (model) args.push('--model', String(model));
|
|
38
|
+
if (prompt != null && prompt !== '') args.push(String(prompt));
|
|
39
|
+
return args;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function toolStatusLine(name, input) {
|
|
43
|
+
const n = String(name || 'tool');
|
|
44
|
+
const i = input && typeof input === 'object' ? input : {};
|
|
45
|
+
if (n === 'Write' || n === 'Edit' || n === 'Read' || n === 'NotebookEdit') {
|
|
46
|
+
return `${n} ${i.file_path || i.path || ''}`.trim();
|
|
47
|
+
}
|
|
48
|
+
if (n === 'Bash') return `Bash ${String(i.command || i.cmd || '').slice(0, 80)}`.trim();
|
|
49
|
+
if (n === 'Glob') return `Glob ${i.pattern || ''}`.trim();
|
|
50
|
+
if (n === 'Grep') return `Grep ${i.pattern || ''}`.trim();
|
|
51
|
+
if (n === 'Task') return `Task ${i.description || i.prompt || ''}`.trim();
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function paymentFailText(text) {
|
|
56
|
+
const s = String(text || '');
|
|
57
|
+
if (/\b(?:wallet is empty|empty wallet|wallet underfunded|underfunded)\b/i.test(s)) {
|
|
58
|
+
return s.includes('HTTP 402') || /payment/i.test(s)
|
|
59
|
+
? s
|
|
60
|
+
: `(payment required — HTTP 402, the wallet is empty.) ${s}`.trim();
|
|
61
|
+
}
|
|
62
|
+
if (/\b(?:payment failed|HTTP 402|payment required)\b/i.test(s)) return s;
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fold one Claude Code stream-json object into a grokui-sized event.
|
|
68
|
+
* Not a RUN: parser — just the official print protocol.
|
|
69
|
+
*/
|
|
70
|
+
export function foldClaudeEvent(ev) {
|
|
71
|
+
if (!ev || typeof ev !== 'object') return null;
|
|
72
|
+
const sessionId = ev.session_id || ev.sessionId;
|
|
73
|
+
if (ev.type === 'system' && ev.subtype === 'init') {
|
|
74
|
+
return { kind: 'init', sessionId, model: ev.model, tools: ev.tools || [] };
|
|
75
|
+
}
|
|
76
|
+
if (ev.type === 'stream_event') {
|
|
77
|
+
const delta = ev.event?.delta;
|
|
78
|
+
if (delta?.type === 'thinking_delta' && delta.thinking) {
|
|
79
|
+
return { kind: 'think', text: delta.thinking, sessionId };
|
|
80
|
+
}
|
|
81
|
+
if (delta?.type === 'text_delta' && delta.text) {
|
|
82
|
+
return { kind: 'text', text: delta.text, sessionId };
|
|
83
|
+
}
|
|
84
|
+
return { kind: 'partial', sessionId };
|
|
85
|
+
}
|
|
86
|
+
if (ev.type === 'assistant' && ev.message) {
|
|
87
|
+
const blocks = Array.isArray(ev.message.content) ? ev.message.content : [];
|
|
88
|
+
const thinking = [];
|
|
89
|
+
const text = [];
|
|
90
|
+
const tools = [];
|
|
91
|
+
for (const b of blocks) {
|
|
92
|
+
if (!b || typeof b !== 'object') continue;
|
|
93
|
+
if (b.type === 'thinking' && b.thinking) thinking.push(b.thinking);
|
|
94
|
+
else if (b.type === 'text' && b.text) text.push(b.text);
|
|
95
|
+
else if (b.type === 'tool_use') tools.push({ name: b.name, input: b.input || {} });
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
kind: 'assistant',
|
|
99
|
+
thinking: thinking.join('\n'),
|
|
100
|
+
text: text.join(''),
|
|
101
|
+
tools,
|
|
102
|
+
sessionId,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (ev.type === 'result') {
|
|
106
|
+
const raw = ev.result != null ? String(ev.result) : (ev.error != null ? String(ev.error) : '');
|
|
107
|
+
const pay = paymentFailText(raw);
|
|
108
|
+
return {
|
|
109
|
+
kind: 'result',
|
|
110
|
+
text: raw,
|
|
111
|
+
error: Boolean(ev.is_error || ev.subtype === 'error' || ev.subtype === 'error_during_execution'),
|
|
112
|
+
paymentFailed: pay,
|
|
113
|
+
sessionId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { kind: ev.type || 'other', sessionId };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseNdjsonLine(line) {
|
|
120
|
+
const s = String(line || '').trim();
|
|
121
|
+
if (!s) return null;
|
|
122
|
+
try { return JSON.parse(s); } catch { return null; }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function runClaudeCode({
|
|
126
|
+
prompt,
|
|
127
|
+
cwd,
|
|
128
|
+
sessionId,
|
|
129
|
+
model,
|
|
130
|
+
system,
|
|
131
|
+
env = process.env,
|
|
132
|
+
port,
|
|
133
|
+
onEvent,
|
|
134
|
+
signal,
|
|
135
|
+
} = {}) {
|
|
136
|
+
if (runnerOverride) {
|
|
137
|
+
return runnerOverride({ prompt, cwd, sessionId, model, system, env, port, onEvent, signal });
|
|
138
|
+
}
|
|
139
|
+
const zooEnv = claudeZooEnv(env, { port });
|
|
140
|
+
const cli = resolveClaudeCli(zooEnv);
|
|
141
|
+
if (!cli) {
|
|
142
|
+
return { text: CLAUDE_MISSING, error: true, paymentFailed: '', sessionId: sessionId || '', missing: true };
|
|
143
|
+
}
|
|
144
|
+
const args = claudePrintArgs({ prompt, sessionId, model, system });
|
|
145
|
+
return spawnClaudePrint({ cli, args, cwd, env: zooEnv, onEvent, signal, sessionId });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function spawnClaudePrint({ cli, args, cwd, env, onEvent, signal, sessionId }) {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
let child;
|
|
151
|
+
try {
|
|
152
|
+
child = spawn(cli, args, {
|
|
153
|
+
cwd: cwd || process.cwd(),
|
|
154
|
+
env,
|
|
155
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
156
|
+
});
|
|
157
|
+
} catch (e) {
|
|
158
|
+
resolve({
|
|
159
|
+
text: `could not launch claude: ${e.message}`,
|
|
160
|
+
error: true,
|
|
161
|
+
paymentFailed: '',
|
|
162
|
+
sessionId: sessionId || '',
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let stdout = '';
|
|
167
|
+
let stderr = '';
|
|
168
|
+
let sid = sessionId || '';
|
|
169
|
+
let lastText = '';
|
|
170
|
+
let lastPay = '';
|
|
171
|
+
let sawError = false;
|
|
172
|
+
const onAbort = () => {
|
|
173
|
+
try { child.kill('SIGTERM'); } catch { /* gone */ }
|
|
174
|
+
};
|
|
175
|
+
if (signal) {
|
|
176
|
+
if (signal.aborted) onAbort();
|
|
177
|
+
else signal.addEventListener('abort', onAbort, { once: true });
|
|
178
|
+
}
|
|
179
|
+
const take = (chunk, which) => {
|
|
180
|
+
const s = String(chunk);
|
|
181
|
+
if (which === 'err') stderr += s;
|
|
182
|
+
else stdout += s;
|
|
183
|
+
if (which !== 'out') return;
|
|
184
|
+
const lines = stdout.split(/\r?\n/);
|
|
185
|
+
stdout = lines.pop() || '';
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
const raw = parseNdjsonLine(line);
|
|
188
|
+
if (!raw) continue;
|
|
189
|
+
const folded = foldClaudeEvent(raw);
|
|
190
|
+
if (!folded) continue;
|
|
191
|
+
if (folded.sessionId) sid = folded.sessionId;
|
|
192
|
+
if (folded.kind === 'result') {
|
|
193
|
+
lastText = folded.text || lastText;
|
|
194
|
+
lastPay = folded.paymentFailed || lastPay;
|
|
195
|
+
if (folded.error) sawError = true;
|
|
196
|
+
} else if (folded.kind === 'assistant' && folded.text) {
|
|
197
|
+
lastText = folded.text;
|
|
198
|
+
}
|
|
199
|
+
try { onEvent?.(folded); } catch { /* paint must not kill the child */ }
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
child.stdout.on('data', (d) => take(d, 'out'));
|
|
203
|
+
child.stderr.on('data', (d) => take(d, 'err'));
|
|
204
|
+
child.on('error', (e) => {
|
|
205
|
+
if (signal) signal.removeEventListener?.('abort', onAbort);
|
|
206
|
+
resolve({
|
|
207
|
+
text: `could not launch claude: ${e.message}`,
|
|
208
|
+
error: true,
|
|
209
|
+
paymentFailed: '',
|
|
210
|
+
sessionId: sid,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
child.on('close', (code) => {
|
|
214
|
+
if (signal) signal.removeEventListener?.('abort', onAbort);
|
|
215
|
+
if (stdout.trim()) take('\n', 'out');
|
|
216
|
+
const errPay = paymentFailText(stderr);
|
|
217
|
+
const outPay = lastPay || paymentFailText(lastText) || errPay;
|
|
218
|
+
let text = lastText;
|
|
219
|
+
if (!text && stderr.trim()) text = stderr.trim().slice(0, 4000);
|
|
220
|
+
if (!text && code && code !== 0) text = `claude exited ${code}`;
|
|
221
|
+
resolve({
|
|
222
|
+
text: text || '',
|
|
223
|
+
error: sawError || (code !== 0 && code != null),
|
|
224
|
+
paymentFailed: outPay,
|
|
225
|
+
sessionId: sid,
|
|
226
|
+
stderr,
|
|
227
|
+
exitCode: code,
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|