@bankr/cli 0.3.13 → 0.3.32
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 +34 -10
- package/dist/cli.js +34 -18
- package/dist/commands/balances.js +3 -3
- package/dist/commands/cancel.js +1 -3
- package/dist/commands/club.js +6 -16
- package/dist/commands/config.js +1 -2
- package/dist/commands/fees.js +79 -64
- package/dist/commands/files.js +17 -39
- package/dist/commands/launch.d.ts +4 -0
- package/dist/commands/launch.js +56 -18
- package/dist/commands/llm.d.ts +10 -0
- package/dist/commands/llm.js +510 -99
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.js +57 -57
- package/dist/commands/portfolio.js +4 -4
- package/dist/commands/profile.js +6 -5
- package/dist/commands/prompt.js +36 -26
- package/dist/commands/sign.js +9 -16
- package/dist/commands/sounds.js +6 -15
- package/dist/commands/status.js +2 -4
- package/dist/commands/submit.js +13 -24
- package/dist/commands/swap.js +11 -11
- package/dist/commands/tokens.js +4 -4
- package/dist/commands/transfer.js +10 -10
- package/dist/commands/update.js +2 -2
- package/dist/commands/webhooks.js +27 -67
- package/dist/commands/whoami.js +4 -4
- package/dist/commands/x402.d.ts +3 -0
- package/dist/commands/x402.js +95 -115
- package/dist/lib/api.d.ts +17 -3
- package/dist/lib/api.js +34 -43
- package/dist/lib/explorer.js +1 -0
- package/dist/lib/output.d.ts +5 -0
- package/dist/lib/output.js +18 -0
- package/dist/lib/passthrough.d.ts +21 -0
- package/dist/lib/passthrough.js +26 -0
- package/dist/lib/tokens.d.ts +0 -5
- package/dist/lib/tokens.js +0 -5
- package/package.json +1 -1
package/dist/commands/llm.js
CHANGED
|
@@ -5,7 +5,13 @@ import { spawn } from "child_process";
|
|
|
5
5
|
import { CLI_USER_AGENT, getApiUrl, getLlmKey, getLlmUrl, requireApiKey, } from "../lib/config.js";
|
|
6
6
|
import { getBalances } from "../lib/api.js";
|
|
7
7
|
import * as output from "../lib/output.js";
|
|
8
|
+
import { getErrorMessage } from "../lib/errors.js";
|
|
9
|
+
import { isHelpRequest } from "../lib/passthrough.js";
|
|
8
10
|
const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at bankr.bot/api-keys";
|
|
11
|
+
/** True when the model serves chat completions (image-only models don't). */
|
|
12
|
+
function supportsChat(m) {
|
|
13
|
+
return (m.output ?? ["text"]).includes("text");
|
|
14
|
+
}
|
|
9
15
|
/**
|
|
10
16
|
* Fallback model catalog — used when the gateway is unreachable or unauthenticated.
|
|
11
17
|
* resolveModels() fetches live data from GET /v1/models; this list is the offline safety net.
|
|
@@ -14,6 +20,15 @@ const IMAGE_INPUT = ["text", "image"];
|
|
|
14
20
|
const TEXT_INPUT = ["text"];
|
|
15
21
|
const GATEWAY_MODELS = [
|
|
16
22
|
// Claude
|
|
23
|
+
{
|
|
24
|
+
id: "claude-fable-5.1",
|
|
25
|
+
name: "Claude Fable 5.1",
|
|
26
|
+
owned_by: "anthropic",
|
|
27
|
+
contextWindow: 1000000,
|
|
28
|
+
maxTokens: 128000,
|
|
29
|
+
input: IMAGE_INPUT,
|
|
30
|
+
cost: { input: 10.0, output: 50.0, cacheRead: 0.25, cacheWrite: 12.5 },
|
|
31
|
+
},
|
|
17
32
|
{
|
|
18
33
|
id: "claude-fable-5",
|
|
19
34
|
name: "Claude Fable 5",
|
|
@@ -23,6 +38,15 @@ const GATEWAY_MODELS = [
|
|
|
23
38
|
input: IMAGE_INPUT,
|
|
24
39
|
cost: { input: 10.0, output: 50.0, cacheRead: 1.0, cacheWrite: 12.5 },
|
|
25
40
|
},
|
|
41
|
+
{
|
|
42
|
+
id: "claude-opus-5",
|
|
43
|
+
name: "Claude Opus 5",
|
|
44
|
+
owned_by: "anthropic",
|
|
45
|
+
contextWindow: 1000000,
|
|
46
|
+
maxTokens: 128000,
|
|
47
|
+
input: IMAGE_INPUT,
|
|
48
|
+
cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
49
|
+
},
|
|
26
50
|
{
|
|
27
51
|
id: "claude-opus-4.8",
|
|
28
52
|
name: "Claude Opus 4.8",
|
|
@@ -59,6 +83,15 @@ const GATEWAY_MODELS = [
|
|
|
59
83
|
input: IMAGE_INPUT,
|
|
60
84
|
cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
61
85
|
},
|
|
86
|
+
{
|
|
87
|
+
id: "claude-sonnet-5",
|
|
88
|
+
name: "Claude Sonnet 5",
|
|
89
|
+
owned_by: "anthropic",
|
|
90
|
+
contextWindow: 1000000,
|
|
91
|
+
maxTokens: 128000,
|
|
92
|
+
input: IMAGE_INPUT,
|
|
93
|
+
cost: { input: 2.0, output: 10.0, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
94
|
+
},
|
|
62
95
|
{
|
|
63
96
|
id: "claude-sonnet-4.6",
|
|
64
97
|
name: "Claude Sonnet 4.6",
|
|
@@ -88,22 +121,49 @@ const GATEWAY_MODELS = [
|
|
|
88
121
|
},
|
|
89
122
|
// Gemini
|
|
90
123
|
{
|
|
91
|
-
id: "gemini-3.
|
|
92
|
-
name: "Gemini 3.
|
|
124
|
+
id: "gemini-3.8-flash",
|
|
125
|
+
name: "Gemini 3.8 Flash",
|
|
93
126
|
owned_by: "google",
|
|
94
127
|
contextWindow: 1048576,
|
|
95
128
|
maxTokens: 65536,
|
|
96
129
|
input: IMAGE_INPUT,
|
|
97
|
-
cost: { input:
|
|
130
|
+
cost: { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0.04167 },
|
|
98
131
|
},
|
|
99
132
|
{
|
|
100
|
-
id: "gemini-3.
|
|
101
|
-
name: "Gemini 3.
|
|
133
|
+
id: "gemini-3.7-flash",
|
|
134
|
+
name: "Gemini 3.7 Flash",
|
|
102
135
|
owned_by: "google",
|
|
103
136
|
contextWindow: 1048576,
|
|
104
137
|
maxTokens: 65536,
|
|
105
138
|
input: IMAGE_INPUT,
|
|
106
|
-
cost: { input: 0.
|
|
139
|
+
cost: { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0.04167 },
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "gemini-3.6-flash",
|
|
143
|
+
name: "Gemini 3.6 Flash",
|
|
144
|
+
owned_by: "google",
|
|
145
|
+
contextWindow: 1048576,
|
|
146
|
+
maxTokens: 65536,
|
|
147
|
+
input: IMAGE_INPUT,
|
|
148
|
+
cost: { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0.04167 },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "gemini-3.5-flash",
|
|
152
|
+
name: "Gemini 3.5 Flash",
|
|
153
|
+
owned_by: "google",
|
|
154
|
+
contextWindow: 1048576,
|
|
155
|
+
maxTokens: 65536,
|
|
156
|
+
input: IMAGE_INPUT,
|
|
157
|
+
cost: { input: 1.5, output: 9.0, cacheRead: 0.15, cacheWrite: 0.08333 },
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "gemini-3.1-pro",
|
|
161
|
+
name: "Gemini 3.1 Pro",
|
|
162
|
+
owned_by: "google",
|
|
163
|
+
contextWindow: 1048576,
|
|
164
|
+
maxTokens: 65536,
|
|
165
|
+
input: IMAGE_INPUT,
|
|
166
|
+
cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 0.375 },
|
|
107
167
|
},
|
|
108
168
|
{
|
|
109
169
|
id: "gemini-3-flash",
|
|
@@ -132,7 +192,74 @@ const GATEWAY_MODELS = [
|
|
|
132
192
|
input: IMAGE_INPUT,
|
|
133
193
|
cost: { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0.0833 },
|
|
134
194
|
},
|
|
195
|
+
// Gemma
|
|
196
|
+
{
|
|
197
|
+
id: "gemma-4-31b-it",
|
|
198
|
+
name: "Gemma 4 31B IT",
|
|
199
|
+
owned_by: "google",
|
|
200
|
+
contextWindow: 262144,
|
|
201
|
+
maxTokens: 131072,
|
|
202
|
+
input: IMAGE_INPUT,
|
|
203
|
+
cost: { input: 0.14, output: 0.4, cacheRead: 0, cacheWrite: 0 },
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "gemma-4-26b-a4b-it",
|
|
207
|
+
name: "Gemma 4 26B A4B IT",
|
|
208
|
+
owned_by: "google",
|
|
209
|
+
contextWindow: 262144,
|
|
210
|
+
maxTokens: 262144,
|
|
211
|
+
input: IMAGE_INPUT,
|
|
212
|
+
cost: { input: 0.15, output: 0.6, cacheRead: 0, cacheWrite: 0 },
|
|
213
|
+
},
|
|
135
214
|
// OpenAI
|
|
215
|
+
{
|
|
216
|
+
id: "gpt-6-astra",
|
|
217
|
+
name: "GPT 6 Astra",
|
|
218
|
+
owned_by: "openai",
|
|
219
|
+
contextWindow: 1050000,
|
|
220
|
+
maxTokens: 128000,
|
|
221
|
+
input: IMAGE_INPUT,
|
|
222
|
+
cost: { input: 10.0, output: 50.0, cacheRead: 1.0, cacheWrite: 12.5 },
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "gpt-5.6-sol",
|
|
226
|
+
name: "GPT 5.6 Sol",
|
|
227
|
+
owned_by: "openai",
|
|
228
|
+
contextWindow: 1050000,
|
|
229
|
+
maxTokens: 128000,
|
|
230
|
+
input: IMAGE_INPUT,
|
|
231
|
+
cost: { input: 4.0, output: 20.0, cacheRead: 0.4, cacheWrite: 5.0 },
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: "gpt-5.6-terra",
|
|
235
|
+
name: "GPT 5.6 Terra",
|
|
236
|
+
owned_by: "openai",
|
|
237
|
+
contextWindow: 1050000,
|
|
238
|
+
maxTokens: 128000,
|
|
239
|
+
input: IMAGE_INPUT,
|
|
240
|
+
cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: "gpt-5.6-luna",
|
|
244
|
+
name: "GPT 5.6 Luna",
|
|
245
|
+
owned_by: "openai",
|
|
246
|
+
contextWindow: 1050000,
|
|
247
|
+
maxTokens: 128000,
|
|
248
|
+
input: IMAGE_INPUT,
|
|
249
|
+
cost: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25 },
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "gpt-image-2",
|
|
253
|
+
name: "GPT Image 2",
|
|
254
|
+
owned_by: "openai",
|
|
255
|
+
contextWindow: 32000,
|
|
256
|
+
maxTokens: 16384,
|
|
257
|
+
input: TEXT_INPUT,
|
|
258
|
+
output: ["image"],
|
|
259
|
+
// Image model — served via /v1/images/generations; output rate is per
|
|
260
|
+
// million image tokens.
|
|
261
|
+
cost: { input: 5.0, output: 30.0, cacheRead: 0, cacheWrite: 0 },
|
|
262
|
+
},
|
|
136
263
|
{
|
|
137
264
|
id: "gpt-5.5",
|
|
138
265
|
name: "GPT 5.5",
|
|
@@ -206,6 +333,33 @@ const GATEWAY_MODELS = [
|
|
|
206
333
|
cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
|
|
207
334
|
},
|
|
208
335
|
// Other providers
|
|
336
|
+
{
|
|
337
|
+
id: "grok-4.6",
|
|
338
|
+
name: "Grok 4.6",
|
|
339
|
+
owned_by: "x-ai",
|
|
340
|
+
contextWindow: 500000,
|
|
341
|
+
maxTokens: 500000,
|
|
342
|
+
input: IMAGE_INPUT,
|
|
343
|
+
cost: { input: 2.0, output: 6.0, cacheRead: 0.5, cacheWrite: 0 },
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "grok-4.5",
|
|
347
|
+
name: "Grok 4.5",
|
|
348
|
+
owned_by: "x-ai",
|
|
349
|
+
contextWindow: 500000,
|
|
350
|
+
maxTokens: 500000,
|
|
351
|
+
input: IMAGE_INPUT,
|
|
352
|
+
cost: { input: 2.0, output: 6.0, cacheRead: 0.3, cacheWrite: 0 },
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "grok-4.20",
|
|
356
|
+
name: "Grok 4.20",
|
|
357
|
+
owned_by: "x-ai",
|
|
358
|
+
contextWindow: 2000000,
|
|
359
|
+
maxTokens: 2000000,
|
|
360
|
+
input: IMAGE_INPUT,
|
|
361
|
+
cost: { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
|
|
362
|
+
},
|
|
209
363
|
{
|
|
210
364
|
id: "grok-4.3",
|
|
211
365
|
name: "Grok 4.3",
|
|
@@ -223,6 +377,39 @@ const GATEWAY_MODELS = [
|
|
|
223
377
|
maxTokens: 30000,
|
|
224
378
|
input: TEXT_INPUT,
|
|
225
379
|
cost: { input: 0.2, output: 0.5, cacheRead: 0.05, cacheWrite: 0 },
|
|
380
|
+
// Hard-deprecated in the seed — the gateway 410s it. Without this the
|
|
381
|
+
// offline catalog would advertise it as active.
|
|
382
|
+
deprecation: {
|
|
383
|
+
replaced_by: "grok-4.3",
|
|
384
|
+
removed_at: "2026-06-17T00:00:00.000Z",
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "deepseek-v4-pro-0813",
|
|
389
|
+
name: "DeepSeek V4 Pro 0813",
|
|
390
|
+
owned_by: "deepseek",
|
|
391
|
+
contextWindow: 1048576,
|
|
392
|
+
maxTokens: 384000,
|
|
393
|
+
input: TEXT_INPUT,
|
|
394
|
+
cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0 },
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: "deepseek-v4-pro",
|
|
398
|
+
name: "DeepSeek V4 Pro",
|
|
399
|
+
owned_by: "deepseek",
|
|
400
|
+
contextWindow: 1048576,
|
|
401
|
+
maxTokens: 384000,
|
|
402
|
+
input: TEXT_INPUT,
|
|
403
|
+
cost: { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 },
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "deepseek-v4-flash",
|
|
407
|
+
name: "DeepSeek V4 Flash",
|
|
408
|
+
owned_by: "deepseek",
|
|
409
|
+
contextWindow: 1048576,
|
|
410
|
+
maxTokens: 384000,
|
|
411
|
+
input: TEXT_INPUT,
|
|
412
|
+
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
|
226
413
|
},
|
|
227
414
|
{
|
|
228
415
|
id: "deepseek-v3.2",
|
|
@@ -233,6 +420,42 @@ const GATEWAY_MODELS = [
|
|
|
233
420
|
input: TEXT_INPUT,
|
|
234
421
|
cost: { input: 0.26, output: 0.38, cacheRead: 0.13, cacheWrite: 0 },
|
|
235
422
|
},
|
|
423
|
+
{
|
|
424
|
+
id: "qwen3.8-max",
|
|
425
|
+
name: "Qwen3.8 Max",
|
|
426
|
+
owned_by: "qwen",
|
|
427
|
+
contextWindow: 1000000,
|
|
428
|
+
maxTokens: 131072,
|
|
429
|
+
input: IMAGE_INPUT,
|
|
430
|
+
cost: { input: 2.0, output: 6.0, cacheRead: 0.25, cacheWrite: 2.5 },
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: "qwen3.8-flash",
|
|
434
|
+
name: "Qwen3.8 Flash",
|
|
435
|
+
owned_by: "qwen",
|
|
436
|
+
contextWindow: 1000000,
|
|
437
|
+
maxTokens: 131072,
|
|
438
|
+
input: IMAGE_INPUT,
|
|
439
|
+
cost: { input: 0.15, output: 0.47, cacheRead: 0.016, cacheWrite: 0.2 },
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "qwen3.7-max",
|
|
443
|
+
name: "Qwen3.7 Max",
|
|
444
|
+
owned_by: "qwen",
|
|
445
|
+
contextWindow: 1000000,
|
|
446
|
+
maxTokens: 65536,
|
|
447
|
+
input: TEXT_INPUT,
|
|
448
|
+
cost: { input: 2.5, output: 7.5, cacheRead: 0.25, cacheWrite: 0 },
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: "qwen3.7-flash",
|
|
452
|
+
name: "Qwen3.7 Flash",
|
|
453
|
+
owned_by: "qwen",
|
|
454
|
+
contextWindow: 1000000,
|
|
455
|
+
maxTokens: 65536,
|
|
456
|
+
input: IMAGE_INPUT,
|
|
457
|
+
cost: { input: 0.1, output: 0.4, cacheRead: 0.02, cacheWrite: 0.125 },
|
|
458
|
+
},
|
|
236
459
|
{
|
|
237
460
|
id: "qwen3-coder",
|
|
238
461
|
name: "Qwen3 Coder",
|
|
@@ -240,7 +463,7 @@ const GATEWAY_MODELS = [
|
|
|
240
463
|
contextWindow: 262144,
|
|
241
464
|
maxTokens: 65536,
|
|
242
465
|
input: TEXT_INPUT,
|
|
243
|
-
cost: { input: 0.
|
|
466
|
+
cost: { input: 0.3, output: 1.5, cacheRead: 0.06, cacheWrite: 0 },
|
|
244
467
|
},
|
|
245
468
|
{
|
|
246
469
|
id: "qwen3.7-plus",
|
|
@@ -258,7 +481,7 @@ const GATEWAY_MODELS = [
|
|
|
258
481
|
contextWindow: 1000000,
|
|
259
482
|
maxTokens: 65536,
|
|
260
483
|
input: TEXT_INPUT,
|
|
261
|
-
cost: { input: 0.
|
|
484
|
+
cost: { input: 0.1875, output: 1.125, cacheRead: 0, cacheWrite: 0.234375 },
|
|
262
485
|
},
|
|
263
486
|
{
|
|
264
487
|
id: "qwen3.5-plus",
|
|
@@ -276,7 +499,16 @@ const GATEWAY_MODELS = [
|
|
|
276
499
|
contextWindow: 1000000,
|
|
277
500
|
maxTokens: 65536,
|
|
278
501
|
input: TEXT_INPUT,
|
|
279
|
-
cost: { input: 0.
|
|
502
|
+
cost: { input: 0.065, output: 0.26, cacheRead: 0, cacheWrite: 0 },
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: "kimi-k3",
|
|
506
|
+
name: "Kimi K3",
|
|
507
|
+
owned_by: "moonshotai",
|
|
508
|
+
contextWindow: 1048576,
|
|
509
|
+
maxTokens: 131072,
|
|
510
|
+
input: IMAGE_INPUT,
|
|
511
|
+
cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 0 },
|
|
280
512
|
},
|
|
281
513
|
{
|
|
282
514
|
id: "kimi-k2.7-code",
|
|
@@ -303,7 +535,16 @@ const GATEWAY_MODELS = [
|
|
|
303
535
|
contextWindow: 262144,
|
|
304
536
|
maxTokens: 65535,
|
|
305
537
|
input: TEXT_INPUT,
|
|
306
|
-
cost: { input: 0.
|
|
538
|
+
cost: { input: 0.6, output: 3.0, cacheRead: 0.1, cacheWrite: 0 },
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
id: "minimax-m3",
|
|
542
|
+
name: "MiniMax M3",
|
|
543
|
+
owned_by: "minimax",
|
|
544
|
+
contextWindow: 524288,
|
|
545
|
+
maxTokens: 524288,
|
|
546
|
+
input: IMAGE_INPUT,
|
|
547
|
+
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
307
548
|
},
|
|
308
549
|
{
|
|
309
550
|
id: "minimax-m2.5",
|
|
@@ -332,6 +573,42 @@ const GATEWAY_MODELS = [
|
|
|
332
573
|
input: TEXT_INPUT,
|
|
333
574
|
cost: { input: 0.6, output: 2.4, cacheRead: 0.06, cacheWrite: 0 },
|
|
334
575
|
},
|
|
576
|
+
{
|
|
577
|
+
id: "glm-5.3-flash",
|
|
578
|
+
name: "GLM-5.3 Flash",
|
|
579
|
+
owned_by: "z-ai",
|
|
580
|
+
contextWindow: 1048576,
|
|
581
|
+
maxTokens: 131072,
|
|
582
|
+
input: IMAGE_INPUT,
|
|
583
|
+
cost: { input: 0.15, output: 0.5, cacheRead: 0.03, cacheWrite: 0 },
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
id: "glm-5.3",
|
|
587
|
+
name: "GLM-5.3",
|
|
588
|
+
owned_by: "z-ai",
|
|
589
|
+
contextWindow: 1048576,
|
|
590
|
+
maxTokens: 131072,
|
|
591
|
+
input: TEXT_INPUT,
|
|
592
|
+
cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
id: "glm-5.2",
|
|
596
|
+
name: "GLM-5.2",
|
|
597
|
+
owned_by: "z-ai",
|
|
598
|
+
contextWindow: 1048576,
|
|
599
|
+
maxTokens: 131072,
|
|
600
|
+
input: TEXT_INPUT,
|
|
601
|
+
cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
id: "glm-5.1",
|
|
605
|
+
name: "GLM-5.1",
|
|
606
|
+
owned_by: "z-ai",
|
|
607
|
+
contextWindow: 202800,
|
|
608
|
+
maxTokens: 202800,
|
|
609
|
+
input: TEXT_INPUT,
|
|
610
|
+
cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
611
|
+
},
|
|
335
612
|
{
|
|
336
613
|
id: "glm-5",
|
|
337
614
|
name: "GLM-5",
|
|
@@ -339,7 +616,7 @@ const GATEWAY_MODELS = [
|
|
|
339
616
|
contextWindow: 202752,
|
|
340
617
|
maxTokens: 131072,
|
|
341
618
|
input: TEXT_INPUT,
|
|
342
|
-
cost: { input: 0
|
|
619
|
+
cost: { input: 1.0, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
343
620
|
},
|
|
344
621
|
{
|
|
345
622
|
id: "glm-5-turbo",
|
|
@@ -373,6 +650,9 @@ async function resolveModels() {
|
|
|
373
650
|
contextWindow: m.context_window ?? fallback?.contextWindow ?? 128000,
|
|
374
651
|
maxTokens: m.max_output_tokens ?? fallback?.maxTokens ?? 32768,
|
|
375
652
|
input: m.input_modalities ?? fallback?.input ?? TEXT_INPUT,
|
|
653
|
+
...((m.output_modalities ?? fallback?.output) && {
|
|
654
|
+
output: m.output_modalities ?? fallback?.output,
|
|
655
|
+
}),
|
|
376
656
|
cost: m.pricing
|
|
377
657
|
? {
|
|
378
658
|
input: m.pricing.input,
|
|
@@ -388,6 +668,7 @@ async function resolveModels() {
|
|
|
388
668
|
}),
|
|
389
669
|
...(m.deprecation && { deprecation: m.deprecation }),
|
|
390
670
|
...(m.private && { private: true }),
|
|
671
|
+
...(m.zdr && { zdr: true }),
|
|
391
672
|
};
|
|
392
673
|
});
|
|
393
674
|
return { models, live: true };
|
|
@@ -426,53 +707,148 @@ function installConfigFile(configFile, toolName, merge) {
|
|
|
426
707
|
output.success(`Bankr provider installed to ${configFile}`);
|
|
427
708
|
}
|
|
428
709
|
/* ───────────────────────────── bankr llm models ───────────────────────────── */
|
|
710
|
+
/**
|
|
711
|
+
* The tier filters. Both flags read the gateway's own capability flags from
|
|
712
|
+
* /v1/models, so they track the live registry rather than a hardcoded list.
|
|
713
|
+
*/
|
|
714
|
+
const TIER_VIEWS = {
|
|
715
|
+
private: {
|
|
716
|
+
title: "Bankr LLM Gateway — Private Models",
|
|
717
|
+
match: (m) => m.private === true,
|
|
718
|
+
empty: "No models currently offer a private (TEE) compute environment.",
|
|
719
|
+
countSuffix: " support :private",
|
|
720
|
+
},
|
|
721
|
+
zdr: {
|
|
722
|
+
title: "Bankr LLM Gateway — Zero-Retention Models",
|
|
723
|
+
match: (m) => m.zdr === true,
|
|
724
|
+
empty: "No models currently guarantee zero data retention.",
|
|
725
|
+
countSuffix: " support :zdr",
|
|
726
|
+
},
|
|
727
|
+
};
|
|
429
728
|
export async function modelsCommand(opts = {}) {
|
|
430
729
|
const spin = output.spinner("Fetching models…");
|
|
431
730
|
const { models, live } = await resolveModels();
|
|
432
731
|
spin.stop();
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
732
|
+
// `--private` wins when both are passed: private is the narrower set (every
|
|
733
|
+
// private model is also ZDR), so it's the more specific of the two requests.
|
|
734
|
+
const view = opts.private
|
|
735
|
+
? TIER_VIEWS.private
|
|
736
|
+
: opts.zdr
|
|
737
|
+
? TIER_VIEWS.zdr
|
|
738
|
+
: undefined;
|
|
739
|
+
const shown = view ? models.filter(view.match) : models;
|
|
740
|
+
output.brandBold(view?.title ?? "Bankr LLM Gateway — Available Models");
|
|
440
741
|
console.log();
|
|
441
|
-
if (
|
|
742
|
+
if (view && shown.length === 0) {
|
|
442
743
|
output.warn(live
|
|
443
|
-
?
|
|
444
|
-
: "
|
|
744
|
+
? view.empty
|
|
745
|
+
: "Tier status needs a live gateway — set BANKR_LLM_KEY (and BANKR_LLM_URL).");
|
|
445
746
|
return;
|
|
446
747
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
748
|
+
// Widths are sized to the longest real value (`minimax-m2.7-highspeed` /
|
|
749
|
+
// `MiniMax M2.7 Highspeed` are both 22), keeping the table under ~90 columns
|
|
750
|
+
// once the tier list is included.
|
|
751
|
+
const COL = { id: 23, name: 23, provider: 11, privacy: 28 };
|
|
752
|
+
const isImageGen = (m) => m.output?.includes("image") === true;
|
|
753
|
+
/**
|
|
754
|
+
* Every tier this model can serve, not just the strongest.
|
|
755
|
+
*
|
|
756
|
+
* The tiers nest, so a private model is also reachable at zdr and standard —
|
|
757
|
+
* listing only the top one made the ladder look like three separate offers
|
|
758
|
+
* and hid that `glm-5.2` (no suffix) is a perfectly valid standard request.
|
|
759
|
+
* Read straight off the flags rather than inferring `zdr` from `private`, so
|
|
760
|
+
* the column reports what the gateway actually advertises.
|
|
761
|
+
*/
|
|
762
|
+
const supportedTiers = (m) => {
|
|
763
|
+
// Each tier is painted individually. Colouring the joined string instead
|
|
764
|
+
// made "standard" grey on a standard-only row and green on a ZDR one — the
|
|
765
|
+
// same word changing colour purely because of what followed it.
|
|
766
|
+
const tiers = [
|
|
767
|
+
// The baseline, on every row, so it recedes rather than competing.
|
|
768
|
+
{ label: "standard", paint: output.fmt.dim },
|
|
769
|
+
];
|
|
770
|
+
if (m.zdr)
|
|
771
|
+
tiers.push({ label: "zdr", paint: output.fmt.success });
|
|
772
|
+
if (m.private) {
|
|
773
|
+
// Distinct from zdr: they are two rungs, not one capability.
|
|
774
|
+
tiers.push({ label: "private (tee)", paint: output.fmt.magic });
|
|
775
|
+
}
|
|
776
|
+
return tiers;
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* Pad from the PLAIN text — ANSI escapes have zero display width but count
|
|
780
|
+
* toward `.length`, so padding the painted string misaligns every coloured
|
|
781
|
+
* row against the header.
|
|
782
|
+
*/
|
|
783
|
+
const tierCell = (m) => {
|
|
784
|
+
const tiers = supportedTiers(m);
|
|
785
|
+
const plain = tiers.map((t) => t.label).join(", ");
|
|
786
|
+
const painted = tiers
|
|
787
|
+
.map((t) => t.paint(t.label))
|
|
788
|
+
.join(output.fmt.dim(", "));
|
|
789
|
+
return painted + " ".repeat(Math.max(0, COL.privacy - plain.length));
|
|
790
|
+
};
|
|
791
|
+
/**
|
|
792
|
+
* Everything that is NOT a privacy tier. Kept in its own column because
|
|
793
|
+
* sharing one cell forced an `else if` chain where a deprecated ZDR model
|
|
794
|
+
* showed only "deprecated" and its tier silently disappeared.
|
|
795
|
+
*/
|
|
796
|
+
const modelNotes = (m) => {
|
|
797
|
+
const parts = [];
|
|
453
798
|
if (m.deprecation) {
|
|
454
|
-
|
|
799
|
+
parts.push(m.deprecation.replaced_by
|
|
455
800
|
? `deprecated → ${m.deprecation.replaced_by}`
|
|
456
|
-
: "deprecated";
|
|
801
|
+
: "deprecated");
|
|
457
802
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
803
|
+
if (isImageGen(m))
|
|
804
|
+
parts.push("Image gen (/v1/images/generations)");
|
|
805
|
+
return parts.join(" · ");
|
|
806
|
+
};
|
|
807
|
+
const showNotes = shown.some((m) => modelNotes(m) !== "");
|
|
808
|
+
const line = (cells) => console.log(` ${cells.join(" ")}`.trimEnd());
|
|
809
|
+
line([
|
|
810
|
+
output.fmt.brandBold("Model ID".padEnd(COL.id)),
|
|
811
|
+
output.fmt.brandBold("Name".padEnd(COL.name)),
|
|
812
|
+
output.fmt.brandBold("Provider".padEnd(COL.provider)),
|
|
813
|
+
output.fmt.brandBold("Privacy Tier".padEnd(COL.privacy)),
|
|
814
|
+
...(showNotes ? [output.fmt.brandBold("Notes")] : []),
|
|
815
|
+
]);
|
|
816
|
+
// Measured, not guessed: a fixed fudge factor for the last column left the
|
|
817
|
+
// rule hanging well past the header once the tier list widened it.
|
|
818
|
+
const notesWidth = showNotes
|
|
819
|
+
? Math.max(...shown.map((m) => modelNotes(m).length))
|
|
820
|
+
: 0;
|
|
821
|
+
const rule = COL.id +
|
|
822
|
+
COL.name +
|
|
823
|
+
COL.provider +
|
|
824
|
+
COL.privacy +
|
|
825
|
+
3 +
|
|
826
|
+
(showNotes ? notesWidth + 1 : 0);
|
|
827
|
+
console.log(output.fmt.dim(` ${"─".repeat(rule)}`));
|
|
828
|
+
for (const m of shown) {
|
|
829
|
+
line([
|
|
830
|
+
output.fmt.brand(m.id.padEnd(COL.id)),
|
|
831
|
+
m.name.padEnd(COL.name),
|
|
832
|
+
output.fmt.dim(m.owned_by.padEnd(COL.provider)),
|
|
833
|
+
tierCell(m),
|
|
834
|
+
...(showNotes ? [output.fmt.dim(modelNotes(m))] : []),
|
|
835
|
+
]);
|
|
462
836
|
}
|
|
463
837
|
console.log();
|
|
464
838
|
output.dim(` Gateway: ${getLlmUrl()}`);
|
|
465
|
-
output.dim(` ${shown.length} model${shown.length === 1 ? "" : "s"}${
|
|
466
|
-
if (!
|
|
839
|
+
output.dim(` ${shown.length} model${shown.length === 1 ? "" : "s"}${view?.countSuffix ?? " available"}${live ? "" : " (cached)"}`);
|
|
840
|
+
if (!view && shown.some((m) => m.private)) {
|
|
467
841
|
output.dim(" Private (TEE) — append :private to the model id (e.g. glm-5.2:private). List: bankr llm models --private");
|
|
468
842
|
}
|
|
843
|
+
if (!view && shown.some((m) => m.zdr)) {
|
|
844
|
+
output.dim(" Zero data retention — append :zdr to the model id (e.g. glm-5.2:zdr), or turn it on account-wide under Settings in the web terminal. List: bankr llm models --zdr");
|
|
845
|
+
}
|
|
469
846
|
}
|
|
470
847
|
/* ─────────────────────── bankr llm credits ─────────────────────────────────── */
|
|
471
848
|
export async function creditsCommand() {
|
|
472
849
|
const llmKey = getLlmKey();
|
|
473
850
|
if (!llmKey) {
|
|
474
|
-
output.
|
|
475
|
-
process.exit(1);
|
|
851
|
+
output.fatal("Not authenticated. Run `bankr login` first.");
|
|
476
852
|
}
|
|
477
853
|
const llmUrl = getLlmUrl();
|
|
478
854
|
const spin = output.spinner("Fetching credit balance…");
|
|
@@ -491,12 +867,10 @@ export async function creditsCommand() {
|
|
|
491
867
|
return;
|
|
492
868
|
}
|
|
493
869
|
if (res.status === 401 || res.status === 403) {
|
|
494
|
-
output.
|
|
495
|
-
process.exit(1);
|
|
870
|
+
output.fatal("Authentication failed. Check your API key or run: bankr login");
|
|
496
871
|
}
|
|
497
872
|
if (!res.ok) {
|
|
498
|
-
output.
|
|
499
|
-
process.exit(1);
|
|
873
|
+
output.fatal(`Failed to fetch credits (HTTP ${res.status})`);
|
|
500
874
|
}
|
|
501
875
|
const body = (await res.json());
|
|
502
876
|
output.brandBold("Bankr LLM Gateway — Credits");
|
|
@@ -507,8 +881,7 @@ export async function creditsCommand() {
|
|
|
507
881
|
}
|
|
508
882
|
catch (err) {
|
|
509
883
|
spin.stop();
|
|
510
|
-
output.
|
|
511
|
-
process.exit(1);
|
|
884
|
+
output.fatal(`Failed to fetch credits: ${getErrorMessage(err)}`);
|
|
512
885
|
}
|
|
513
886
|
}
|
|
514
887
|
/* ─────────────────────── bankr llm credits add ──────────────────────────── */
|
|
@@ -560,7 +933,7 @@ function resolveTokenAcrossChains(input, balances) {
|
|
|
560
933
|
symbol: nativeSym,
|
|
561
934
|
name: nativeSym,
|
|
562
935
|
decimals: 18,
|
|
563
|
-
balance:
|
|
936
|
+
balance: chainBal.nativeBalance || "0",
|
|
564
937
|
balanceUsd: nativeUsd,
|
|
565
938
|
});
|
|
566
939
|
}
|
|
@@ -603,8 +976,7 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
603
976
|
// Validate amount
|
|
604
977
|
const amountUsd = Number(amount);
|
|
605
978
|
if (Number.isNaN(amountUsd) || amountUsd < 1 || amountUsd > 1000) {
|
|
606
|
-
output.
|
|
607
|
-
process.exit(1);
|
|
979
|
+
output.fatal("Amount must be between $1 and $1,000");
|
|
608
980
|
}
|
|
609
981
|
// Fetch current balance for display (skip when --yes/--not-interactive since user won't see it)
|
|
610
982
|
let currentBalance = "unknown";
|
|
@@ -627,7 +999,7 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
627
999
|
catch (err) {
|
|
628
1000
|
balSpin.stop();
|
|
629
1001
|
// Non-fatal — proceed without balance display
|
|
630
|
-
output.dim(` (Could not fetch balance: ${err
|
|
1002
|
+
output.dim(` (Could not fetch balance: ${getErrorMessage(err)})`);
|
|
631
1003
|
}
|
|
632
1004
|
}
|
|
633
1005
|
// When --token is passed, the chain holding the highest USD balance of
|
|
@@ -642,11 +1014,10 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
642
1014
|
}
|
|
643
1015
|
catch (err) {
|
|
644
1016
|
resolveSpin.stop();
|
|
645
|
-
output.warn(`Could not fetch balances: ${err
|
|
1017
|
+
output.warn(`Could not fetch balances: ${getErrorMessage(err)}`);
|
|
646
1018
|
}
|
|
647
1019
|
if (!resolved) {
|
|
648
|
-
output.
|
|
649
|
-
process.exit(1);
|
|
1020
|
+
output.fatal(`Token "${opts.token}" not found in your wallet on any supported chain.`);
|
|
650
1021
|
}
|
|
651
1022
|
}
|
|
652
1023
|
const targetChain = resolved?.chain ?? "base";
|
|
@@ -693,22 +1064,18 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
693
1064
|
spin.stop();
|
|
694
1065
|
if (res.status === 402) {
|
|
695
1066
|
const errBody = (await res.json().catch(() => ({})));
|
|
696
|
-
output.
|
|
1067
|
+
output.fatal(errBody.error ??
|
|
697
1068
|
"Insufficient wallet balance. Add funds to your wallet first.");
|
|
698
|
-
process.exit(1);
|
|
699
1069
|
}
|
|
700
1070
|
if (res.status === 403) {
|
|
701
|
-
output.
|
|
702
|
-
process.exit(1);
|
|
1071
|
+
output.fatal(ERR_LLM_NOT_ENABLED);
|
|
703
1072
|
}
|
|
704
1073
|
if (res.status === 502) {
|
|
705
|
-
output.
|
|
706
|
-
process.exit(1);
|
|
1074
|
+
output.fatal("Token swap failed. Try USDC or a different token.");
|
|
707
1075
|
}
|
|
708
1076
|
if (!res.ok) {
|
|
709
1077
|
const errBody = (await res.json().catch(() => ({})));
|
|
710
|
-
output.
|
|
711
|
-
process.exit(1);
|
|
1078
|
+
output.fatal(errBody.error ?? `Failed to add credits (HTTP ${res.status})`);
|
|
712
1079
|
}
|
|
713
1080
|
const result = (await res.json());
|
|
714
1081
|
console.log();
|
|
@@ -720,8 +1087,7 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
720
1087
|
}
|
|
721
1088
|
catch (err) {
|
|
722
1089
|
spin.stop();
|
|
723
|
-
output.
|
|
724
|
-
process.exit(1);
|
|
1090
|
+
output.fatal(`Failed to add credits: ${getErrorMessage(err)}`);
|
|
725
1091
|
}
|
|
726
1092
|
}
|
|
727
1093
|
function formatTimeAgo(dateStr) {
|
|
@@ -762,12 +1128,10 @@ export async function creditsAutoCommand(opts) {
|
|
|
762
1128
|
});
|
|
763
1129
|
spin.stop();
|
|
764
1130
|
if (res.status === 403) {
|
|
765
|
-
output.
|
|
766
|
-
process.exit(1);
|
|
1131
|
+
output.fatal(ERR_LLM_NOT_ENABLED);
|
|
767
1132
|
}
|
|
768
1133
|
if (!res.ok) {
|
|
769
|
-
output.
|
|
770
|
-
process.exit(1);
|
|
1134
|
+
output.fatal(`Failed to fetch config (HTTP ${res.status})`);
|
|
771
1135
|
}
|
|
772
1136
|
const { config } = (await res.json());
|
|
773
1137
|
output.brandBold("Bankr LLM Gateway — Auto Top-Up");
|
|
@@ -782,15 +1146,13 @@ export async function creditsAutoCommand(opts) {
|
|
|
782
1146
|
}
|
|
783
1147
|
catch (err) {
|
|
784
1148
|
spin.stop();
|
|
785
|
-
output.
|
|
786
|
-
process.exit(1);
|
|
1149
|
+
output.fatal(`Failed to fetch config: ${getErrorMessage(err)}`);
|
|
787
1150
|
}
|
|
788
1151
|
return;
|
|
789
1152
|
}
|
|
790
1153
|
// POST update config
|
|
791
1154
|
if (opts.enable && opts.disable) {
|
|
792
|
-
output.
|
|
793
|
-
process.exit(1);
|
|
1155
|
+
output.fatal("Cannot use --enable and --disable together");
|
|
794
1156
|
}
|
|
795
1157
|
const body = {};
|
|
796
1158
|
if (opts.disable) {
|
|
@@ -806,16 +1168,14 @@ export async function creditsAutoCommand(opts) {
|
|
|
806
1168
|
if (opts.amount) {
|
|
807
1169
|
const amt = Number(opts.amount);
|
|
808
1170
|
if (Number.isNaN(amt) || amt < 1 || amt > 1000) {
|
|
809
|
-
output.
|
|
810
|
-
process.exit(1);
|
|
1171
|
+
output.fatal("Amount must be between $1 and $1,000");
|
|
811
1172
|
}
|
|
812
1173
|
body.amountUsd = amt;
|
|
813
1174
|
}
|
|
814
1175
|
if (opts.threshold) {
|
|
815
1176
|
const thr = Number(opts.threshold);
|
|
816
1177
|
if (Number.isNaN(thr) || thr < 1 || thr > 500) {
|
|
817
|
-
output.
|
|
818
|
-
process.exit(1);
|
|
1178
|
+
output.fatal("Threshold must be between $1 and $500");
|
|
819
1179
|
}
|
|
820
1180
|
body.thresholdUsd = thr;
|
|
821
1181
|
}
|
|
@@ -827,8 +1187,7 @@ export async function creditsAutoCommand(opts) {
|
|
|
827
1187
|
.map((s) => s.trim())
|
|
828
1188
|
.filter(Boolean);
|
|
829
1189
|
if (inputs.length === 0 || inputs.length > 3) {
|
|
830
|
-
output.
|
|
831
|
-
process.exit(1);
|
|
1190
|
+
output.fatal("Provide 1-3 comma-separated token symbols or addresses");
|
|
832
1191
|
}
|
|
833
1192
|
const resolved = [];
|
|
834
1193
|
// Highest USD balance wins when the same symbol exists on multiple chains.
|
|
@@ -859,8 +1218,7 @@ export async function creditsAutoCommand(opts) {
|
|
|
859
1218
|
}
|
|
860
1219
|
}
|
|
861
1220
|
if (resolved.length === 0) {
|
|
862
|
-
output.
|
|
863
|
-
process.exit(1);
|
|
1221
|
+
output.fatal("No valid tokens resolved. Check your token symbols.");
|
|
864
1222
|
}
|
|
865
1223
|
body.tokens = resolved;
|
|
866
1224
|
}
|
|
@@ -883,13 +1241,11 @@ export async function creditsAutoCommand(opts) {
|
|
|
883
1241
|
});
|
|
884
1242
|
spin.stop();
|
|
885
1243
|
if (res.status === 403) {
|
|
886
|
-
output.
|
|
887
|
-
process.exit(1);
|
|
1244
|
+
output.fatal(ERR_LLM_NOT_ENABLED);
|
|
888
1245
|
}
|
|
889
1246
|
if (!res.ok) {
|
|
890
1247
|
const errBody = (await res.json().catch(() => ({})));
|
|
891
|
-
output.
|
|
892
|
-
process.exit(1);
|
|
1248
|
+
output.fatal(errBody.error ?? `Failed to update config (HTTP ${res.status})`);
|
|
893
1249
|
}
|
|
894
1250
|
output.success(opts.disable ? "Auto top-up disabled" : "Auto top-up settings updated");
|
|
895
1251
|
// Show config from POST response (avoids extra GET round-trip)
|
|
@@ -907,8 +1263,7 @@ export async function creditsAutoCommand(opts) {
|
|
|
907
1263
|
}
|
|
908
1264
|
catch (err) {
|
|
909
1265
|
spin.stop();
|
|
910
|
-
output.
|
|
911
|
-
process.exit(1);
|
|
1266
|
+
output.fatal(`Failed to update config: ${getErrorMessage(err)}`);
|
|
912
1267
|
}
|
|
913
1268
|
}
|
|
914
1269
|
/* ──────────────────────── bankr llm setup openclaw ────────────────────────── */
|
|
@@ -916,13 +1271,31 @@ export async function setupOpenclawCommand(opts) {
|
|
|
916
1271
|
const llmKey = getLlmKey();
|
|
917
1272
|
const llmUrl = getLlmUrl();
|
|
918
1273
|
const { models } = await resolveModels();
|
|
919
|
-
|
|
1274
|
+
// Coding-tool config lists chat models only — image-only models can't chat.
|
|
1275
|
+
const activeModels = models.filter((m) => !m.deprecation && supportsChat(m));
|
|
1276
|
+
// --images routes OpenClaw's built-in image_generate tool through the gateway.
|
|
1277
|
+
// That tool only uses OpenClaw's known image providers (not custom ones like
|
|
1278
|
+
// `bankr`), so enabling it takes over the `openai` provider slot. OpenClaw's
|
|
1279
|
+
// OpenAI provider expects a `/v1`-suffixed base; normalize so a llmUrl that
|
|
1280
|
+
// already ends in /v1 (or a trailing slash) doesn't produce `/v1/v1`.
|
|
1281
|
+
const imageBaseUrl = `${llmUrl.replace(/\/+$/, "").replace(/\/v1$/, "")}/v1`;
|
|
1282
|
+
const imageProvider = opts.images
|
|
1283
|
+
? { baseUrl: imageBaseUrl, apiKey: llmKey ?? "${BANKR_API_KEY}" }
|
|
1284
|
+
: null;
|
|
1285
|
+
const imageGenerationModel = opts.images
|
|
1286
|
+
? { primary: "openai/gpt-image-2", timeoutMs: 180000 }
|
|
1287
|
+
: null;
|
|
1288
|
+
const warnImagesSlot = () => {
|
|
1289
|
+
if (opts.images)
|
|
1290
|
+
output.warn("--images points OpenClaw's `openai` provider at the gateway; image_generate (and any OpenAI-format request) now routes through Bankr. Skip --images if you use real OpenAI in OpenClaw.");
|
|
1291
|
+
};
|
|
920
1292
|
const providerConfig = {
|
|
921
1293
|
baseUrl: llmUrl,
|
|
922
1294
|
apiKey: llmKey ?? "${BANKR_API_KEY}",
|
|
923
1295
|
api: "openai-completions",
|
|
924
|
-
//
|
|
925
|
-
//
|
|
1296
|
+
// Models get a twin entry per privacy tier they support, so users can pick
|
|
1297
|
+
// one from the model dropdown in tools that expose no other configuration.
|
|
1298
|
+
// Base first, then weakest tier to strongest.
|
|
926
1299
|
models: activeModels.flatMap((m) => {
|
|
927
1300
|
const base = {
|
|
928
1301
|
id: m.id,
|
|
@@ -933,12 +1306,15 @@ export async function setupOpenclawCommand(opts) {
|
|
|
933
1306
|
maxTokens: m.maxTokens,
|
|
934
1307
|
cost: m.cost,
|
|
935
1308
|
};
|
|
936
|
-
return
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
{ ...base, id: `${m.id}:
|
|
940
|
-
|
|
941
|
-
|
|
1309
|
+
return [
|
|
1310
|
+
base,
|
|
1311
|
+
...(m.zdr
|
|
1312
|
+
? [{ ...base, id: `${m.id}:zdr`, name: `${m.name} (ZDR)` }]
|
|
1313
|
+
: []),
|
|
1314
|
+
...(m.private
|
|
1315
|
+
? [{ ...base, id: `${m.id}:private`, name: `${m.name} (Private)` }]
|
|
1316
|
+
: []),
|
|
1317
|
+
];
|
|
942
1318
|
}),
|
|
943
1319
|
};
|
|
944
1320
|
if (opts.install) {
|
|
@@ -947,17 +1323,43 @@ export async function setupOpenclawCommand(opts) {
|
|
|
947
1323
|
const models = existing.models ?? {};
|
|
948
1324
|
const providers = models.providers ?? {};
|
|
949
1325
|
providers.bankr = providerConfig;
|
|
1326
|
+
if (imageProvider) {
|
|
1327
|
+
providers.openai = {
|
|
1328
|
+
...(providers.openai ?? {}),
|
|
1329
|
+
...imageProvider,
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
950
1332
|
models.providers = providers;
|
|
951
1333
|
existing.models = models;
|
|
1334
|
+
if (imageGenerationModel) {
|
|
1335
|
+
const agents = existing.agents ?? {};
|
|
1336
|
+
const defaults = agents.defaults ?? {};
|
|
1337
|
+
defaults.imageGenerationModel = imageGenerationModel;
|
|
1338
|
+
agents.defaults = defaults;
|
|
1339
|
+
existing.agents = agents;
|
|
1340
|
+
}
|
|
952
1341
|
});
|
|
953
1342
|
warnIfNoKey(llmKey);
|
|
1343
|
+
warnImagesSlot();
|
|
954
1344
|
return;
|
|
955
1345
|
}
|
|
956
|
-
|
|
1346
|
+
const printed = {
|
|
1347
|
+
models: {
|
|
1348
|
+
providers: {
|
|
1349
|
+
bankr: providerConfig,
|
|
1350
|
+
...(imageProvider ? { openai: imageProvider } : {}),
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
...(imageGenerationModel
|
|
1354
|
+
? { agents: { defaults: { imageGenerationModel } } }
|
|
1355
|
+
: {}),
|
|
1356
|
+
};
|
|
1357
|
+
console.log(JSON.stringify(printed, null, 2));
|
|
957
1358
|
console.log();
|
|
958
1359
|
if (!llmKey) {
|
|
959
1360
|
output.warn("No API key set. Replace ${BANKR_API_KEY} with your key or run: bankr login");
|
|
960
1361
|
}
|
|
1362
|
+
warnImagesSlot();
|
|
961
1363
|
output.dim("Add to ~/.openclaw/openclaw.json or run: bankr llm setup openclaw --install");
|
|
962
1364
|
}
|
|
963
1365
|
/* ──────────────────────── bankr llm setup opencode ─────────────────────────── */
|
|
@@ -965,11 +1367,15 @@ export async function setupOpenCodeCommand(opts) {
|
|
|
965
1367
|
const llmKey = getLlmKey();
|
|
966
1368
|
const llmUrl = getLlmUrl();
|
|
967
1369
|
const { models } = await resolveModels();
|
|
968
|
-
|
|
1370
|
+
// Coding-tool config lists chat models only — image-only models can't chat.
|
|
1371
|
+
const activeModels = models.filter((m) => !m.deprecation && supportsChat(m));
|
|
969
1372
|
const modelsObj = {};
|
|
970
1373
|
for (const m of activeModels) {
|
|
971
1374
|
modelsObj[m.id] = { name: m.name };
|
|
972
|
-
//
|
|
1375
|
+
// One variant per privacy tier the model supports, weakest to strongest.
|
|
1376
|
+
if (m.zdr) {
|
|
1377
|
+
modelsObj[`${m.id}:zdr`] = { name: `${m.name} (ZDR)` };
|
|
1378
|
+
}
|
|
973
1379
|
if (m.private) {
|
|
974
1380
|
modelsObj[`${m.id}:private`] = { name: `${m.name} (Private)` };
|
|
975
1381
|
}
|
|
@@ -1015,7 +1421,7 @@ export async function setupCursorCommand() {
|
|
|
1015
1421
|
const { models } = await resolveModels();
|
|
1016
1422
|
const activeModels = models.filter((m) => !m.deprecation);
|
|
1017
1423
|
// Pick one model per provider as recommended examples
|
|
1018
|
-
const recommendedIds = ["claude-fable-5", "gemini-3.1-pro", "gpt-5.5"];
|
|
1424
|
+
const recommendedIds = ["claude-fable-5.1", "gemini-3.1-pro", "gpt-5.5"];
|
|
1019
1425
|
const recommended = recommendedIds.filter((id) => activeModels.some((m) => m.id === id));
|
|
1020
1426
|
output.brandBold("Cursor — Bankr LLM Gateway");
|
|
1021
1427
|
console.log();
|
|
@@ -1061,8 +1467,7 @@ export async function setupClaudeCodeCommand() {
|
|
|
1061
1467
|
function requireAuth() {
|
|
1062
1468
|
const llmKey = getLlmKey();
|
|
1063
1469
|
if (!llmKey) {
|
|
1064
|
-
output.
|
|
1065
|
-
process.exit(1);
|
|
1470
|
+
output.fatal("Not authenticated. Run `bankr login` first.");
|
|
1066
1471
|
}
|
|
1067
1472
|
return llmKey;
|
|
1068
1473
|
}
|
|
@@ -1102,8 +1507,8 @@ function fileContains(filePath, search) {
|
|
|
1102
1507
|
* `--model` flag appears honored but the real request is a different
|
|
1103
1508
|
* model. Any suffix (e.g. `[1m]` context tier) is preserved.
|
|
1104
1509
|
*/
|
|
1105
|
-
function toAnthropicModelId(model) {
|
|
1106
|
-
return model.replace(/^(claude-(?:opus|sonnet|haiku)-)(\d+)\.(\d+)(.*)$/, "$1$2-$3$4");
|
|
1510
|
+
export function toAnthropicModelId(model) {
|
|
1511
|
+
return model.replace(/^(claude-(?:opus|sonnet|haiku|fable)-)(\d+)\.(\d+)(.*)$/, "$1$2-$3$4");
|
|
1107
1512
|
}
|
|
1108
1513
|
function translateClaudeModelArgs(args) {
|
|
1109
1514
|
const out = [...args];
|
|
@@ -1122,6 +1527,9 @@ function translateClaudeModelArgs(args) {
|
|
|
1122
1527
|
return out;
|
|
1123
1528
|
}
|
|
1124
1529
|
export async function claudeCommand(args) {
|
|
1530
|
+
if (isHelpRequest(args)) {
|
|
1531
|
+
return launchTool("claude", args, {}, "https://docs.anthropic.com/en/docs/claude-code");
|
|
1532
|
+
}
|
|
1125
1533
|
const llmKey = requireAuth();
|
|
1126
1534
|
const llmUrl = getLlmUrl();
|
|
1127
1535
|
const translated = translateClaudeModelArgs(args);
|
|
@@ -1130,6 +1538,9 @@ export async function claudeCommand(args) {
|
|
|
1130
1538
|
}
|
|
1131
1539
|
/* ────────────────────────── bankr llm opencode ───────────────────────────── */
|
|
1132
1540
|
export async function opencodeCommand(args) {
|
|
1541
|
+
if (isHelpRequest(args)) {
|
|
1542
|
+
return launchTool("opencode", args, {}, "https://opencode.ai");
|
|
1543
|
+
}
|
|
1133
1544
|
const llmKey = requireAuth();
|
|
1134
1545
|
const llmUrl = getLlmUrl();
|
|
1135
1546
|
const configFile = join(homedir(), ".config", "opencode", "opencode.json");
|