@mmmbuto/nexuscrew 0.8.57 → 0.9.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +164 -2
  2. package/README.md +1 -0
  3. package/frontend/dist/assets/index-0vuhL1YP.css +32 -0
  4. package/frontend/dist/assets/index-zjL6kZ7J.js +93 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/dist/version.json +1 -1
  7. package/lib/audio/adapters.js +50 -6
  8. package/lib/cli/commands.js +40 -2
  9. package/lib/cli/doctor.js +95 -12
  10. package/lib/cli/init.js +25 -3
  11. package/lib/cli/path.js +43 -10
  12. package/lib/cli/pidfile.js +23 -2
  13. package/lib/config.js +15 -0
  14. package/lib/fleet/builtin.js +161 -19
  15. package/lib/fleet/catalogs/opencode-go.json +328 -0
  16. package/lib/fleet/cell-exec.js +87 -9
  17. package/lib/fleet/cell-lease-server.js +719 -0
  18. package/lib/fleet/cell-lease.js +112 -0
  19. package/lib/fleet/definitions.js +101 -7
  20. package/lib/fleet/launch-broker.js +115 -3
  21. package/lib/fleet/lease-client.js +191 -0
  22. package/lib/fleet/lease-routes.js +92 -0
  23. package/lib/fleet/lease-verifier.js +230 -0
  24. package/lib/fleet/managed.js +444 -55
  25. package/lib/fleet/prompt-delivery.js +50 -2
  26. package/lib/fleet/provider.js +1 -1
  27. package/lib/fleet/runtime.js +53 -6
  28. package/lib/live-host/bridge.js +369 -0
  29. package/lib/live-host/routes.js +184 -0
  30. package/lib/live-host/store.js +96 -0
  31. package/lib/mcp/tools.js +51 -0
  32. package/lib/nodes/commands.js +9 -2
  33. package/lib/nodes/store.js +14 -0
  34. package/lib/nodes/tunnel.js +4 -1
  35. package/lib/proxy/federation.js +106 -9
  36. package/lib/proxy/node-proxy.js +33 -0
  37. package/lib/proxy/panel-auth.js +307 -0
  38. package/lib/proxy/panel-proxy.js +305 -0
  39. package/lib/server.js +127 -4
  40. package/package.json +1 -1
  41. package/skills/alibaba-token-media/SKILL.md +19 -0
  42. package/skills/crew/SKILL.md +15 -0
  43. package/skills/fill-forms/SKILL.md +23 -0
  44. package/skills/mail-assistant/SKILL.md +15 -0
  45. package/skills/memory/SKILL.md +15 -0
  46. package/skills/nexuscrew-agent/SKILL.md +18 -0
  47. package/skills/vl-msa/SKILL.md +15 -0
  48. package/frontend/dist/assets/index-CYi_lhCg.css +0 -32
  49. package/frontend/dist/assets/index-_c-1_3iR.js +0 -93
@@ -41,6 +41,7 @@ const { probeModel } = require('./model-probe.js');
41
41
  const { validEnvKey } = require('./env-key.js');
42
42
  const { setCredential, removeCredential } = require('./credentials.js');
43
43
  const { createLaunchBroker } = require('./launch-broker.js');
44
+ const { createLeaseManager } = require('./cell-lease-server.js');
44
45
  const { MINIMAL_ENV_KEYS, termuxRuntimePaths } = require('../runtime/env.js');
45
46
  const { requireSharedTmuxProtection } = require('../tmux/shared-server.js');
46
47
 
@@ -117,6 +118,56 @@ function backfillAgyEngine(defsPath, defs, cfg = {}) {
117
118
  try { return atomicWrite(defsPath, draft); } catch (_) { return defs; }
118
119
  }
119
120
 
121
+ // Engine desktop.local: NON managed (nessun client/provider AI, solo command+args
122
+ // come un engine custom). Entra nel container desktop via `docker exec` e
123
+ // precompila panelUrl al pannello web KasmVNC del container. `-u abc` non e' un
124
+ // dettaglio: senza, si entra come root e Chromium nel container rifiuta di
125
+ // partire ("Running as root without --no-sandbox is not supported") — un
126
+ // errore che nomina il soggetto sbagliato. panelUrl e' comodita' sopra il
127
+ // meccanismo condiviso (validPanelUrl in definitions.js): l'engine non ha una
128
+ // sua strada per imporre l'URL, dichiara solo il valore, e resta sovrascrivibile
129
+ // per-cella come qualunque altro panelUrl (vedi parseCell).
130
+ function defaultDesktopEngine() {
131
+ return {
132
+ id: 'desktop.local',
133
+ label: 'Desktop',
134
+ command: 'docker',
135
+ args: ['exec', '-it', '-u', 'abc', 'ai-desktop', 'bash'],
136
+ promptMode: 'send-keys',
137
+ panelUrl: 'https://127.0.0.1:6901',
138
+ };
139
+ }
140
+
141
+ // Backfill dell'engine desktop.local: come backfillKimiEngine, idempotente
142
+ // (gia' presente -> skip) e non distruttivo su collisione id (un 'desktop.local'
143
+ // gia' scelto dall'utente per altro non viene sovrascritto). Nessun platform
144
+ // gate: il container e' un servizio esterno raggiunto via docker, non un
145
+ // binario per-platform come agy/grok.
146
+ //
147
+ // DELIBERATAMENTE NON agganciata al bootstrap (a differenza di shell/agy/kimi/
148
+ // grok/vl, vedi piu' sotto): NON e' una dimenticanza, e' una decisione presa
149
+ // (decisione di progetto, 2026-08-14). Un backfill universale presuppone un container con un
150
+ // nome preciso — `ai-desktop` — che quasi nessuna installazione avra'. E
151
+ // desktop.local non e' managed: non ha un describeManaged che lo segnali come
152
+ // "non configurato" quando il container manca, quindi resterebbe nella lista
153
+ // engine silenziosamente rotto — esattamente la classe di difetto (un gate
154
+ // verde/una feature presente che non significa cio' che sembra) inseguita piu'
155
+ // volte in questa stessa giornata di lavoro.
156
+ // Scartata anche l'opzione apparentemente furba di un gate su un container-probe
157
+ // all'avvio: il probe e' una decisione presa UNA volta, mentre il container puo'
158
+ // nascere e morire dopo — un engine che appare/scompare a seconda di quando hai
159
+ // riavviato e' peggio di uno assente, e richiederebbe interrogare Docker a ogni
160
+ // bootstrap: una dipendenza nuova sul percorso di avvio per una comodita'.
161
+ // Resta MANUALE: chi ha il container se lo aggiunge nella propria procedura
162
+ // — e' il posto dove qualcuno sa gia' che il container esiste.
163
+ function backfillDesktopEngine(defsPath, defs) {
164
+ if (!defs || defs.engines.some((engine) => engine.id === 'desktop.local')) return defs;
165
+ if (defs.engines.length >= CAPS.MAX_ENGINES) return defs;
166
+ const draft = draftFrom(defs);
167
+ draft.engines.push(defaultDesktopEngine());
168
+ try { return atomicWrite(defsPath, draft); } catch (_) { return defs; }
169
+ }
170
+
120
171
  // Backfill dell'engine Kimi Code CLI nativo: installazioni esistenti ricevono
121
172
  // kimi.native in modo idempotente e non distruttivo. Nessun platform gate: il
122
173
  // CLI gira ovunque giri Node e su Termux il resolver applica gia' il workaround
@@ -214,24 +265,32 @@ function resolveCellCwd(cell, home) {
214
265
  relNorm = normalizeCwdRel(cell.cwdRel);
215
266
  if (relNorm === null) return { ok: false, fail: { reason: 'invalid-rel', cwdRel: cell.cwdRel } };
216
267
  }
217
- const realHome = resolveCwd(home, home);
218
- if (!realHome) return { ok: false, fail: { reason: 'home-unavailable' } };
219
- const realCwd = hasCwd ? resolveCwd(cell.cwd, home) : null;
220
- const realFromRel = hasRel ? resolveCwd(path.join(realHome, relNorm), home) : null;
268
+ // out.unverifiable distingue "non esiste / fuori home / non directory"
269
+ // (legittimo rifiuto) da "esiste ma non ho potuto verificarlo" (EACCES/ELOOP su
270
+ // realpath): stesso verdetto (ok:false, fail-closed di sicurezza), messaggio
271
+ // diverso. Vedi resolveCwd e unportableCwdError. Il discriminante e' CHI ha
272
+ // fallito, non che ci sia stata un'eccezione.
273
+ const homeOut = {};
274
+ const realHome = resolveCwd(home, home, homeOut);
275
+ if (!realHome) return { ok: false, fail: { reason: 'home-unavailable', ...(homeOut.unverifiable ? { unverifiable: homeOut.unverifiable } : {}) } };
276
+ const cwdOut = {};
277
+ const realCwd = hasCwd ? resolveCwd(cell.cwd, home, cwdOut) : null;
278
+ const relOut = {};
279
+ const realFromRel = hasRel ? resolveCwd(path.join(realHome, relNorm), home, relOut) : null;
221
280
  if (hasCwd && hasRel) {
222
- if (!realCwd) return { ok: false, fail: { reason: 'invalid-cwd', cwd: cell.cwd } };
223
- if (!realFromRel) return { ok: false, fail: { reason: 'invalid-rel', cwdRel: cell.cwdRel } };
281
+ if (!realCwd) return { ok: false, fail: { reason: 'invalid-cwd', cwd: cell.cwd, ...(cwdOut.unverifiable ? { unverifiable: cwdOut.unverifiable } : {}) } };
282
+ if (!realFromRel) return { ok: false, fail: { reason: 'invalid-rel', cwdRel: cell.cwdRel, ...(relOut.unverifiable ? { unverifiable: relOut.unverifiable } : {}) } };
224
283
  if (realCwd !== realFromRel) return { ok: false, fail: { reason: 'mismatch', cwd: cell.cwd, cwdRel: cell.cwdRel } };
225
284
  return { ok: true, cwd: realCwd, cwdRel: relNorm };
226
285
  }
227
286
  if (hasCwd) {
228
- if (!realCwd) return { ok: false, fail: { reason: 'invalid-cwd', cwd: cell.cwd } };
287
+ if (!realCwd) return { ok: false, fail: { reason: 'invalid-cwd', cwd: cell.cwd, ...(cwdOut.unverifiable ? { unverifiable: cwdOut.unverifiable } : {}) } };
229
288
  const rel = deriveCwdRel(realCwd, realHome);
230
289
  if (rel === null) return { ok: false, fail: { reason: 'not-under-home', cwd: cell.cwd } };
231
290
  return { ok: true, cwd: realCwd, cwdRel: rel };
232
291
  }
233
292
  // solo cwdRel
234
- if (!realFromRel) return { ok: false, fail: { reason: 'invalid-rel', cwdRel: cell.cwdRel } };
293
+ if (!realFromRel) return { ok: false, fail: { reason: 'invalid-rel', cwdRel: cell.cwdRel, ...(relOut.unverifiable ? { unverifiable: relOut.unverifiable } : {}) } };
235
294
  return { ok: true, cwd: realFromRel, cwdRel: relNorm };
236
295
  }
237
296
 
@@ -258,12 +317,28 @@ function unportableCwdError(failures, home) {
258
317
  const entry = { id };
259
318
  const cwd = typeof fail.cwd === 'string' ? fail.cwd : undefined;
260
319
  if (cwd !== undefined) entry.cwd = cwd;
320
+ // "non ho potuto verificare" (EACCES/ELOOP...) vs "non esiste/fuori home":
321
+ // stesso verdetto (rifiuto, fail-closed di sicurezza), ma la causa e' diversa
322
+ // e l'operatore deve saperlo: potrebbe bastare un permesso, non un path
323
+ // sbagliato. Il discriminante e' CHI ha fallito, non che ci sia stata
324
+ // un'eccezione.
325
+ if (fail.unverifiable) entry.unverifiable = fail.unverifiable;
261
326
  const suggestion = suggestCwdRel(cwd, home);
262
327
  if (suggestion) entry.suggestion = suggestion;
263
328
  return entry;
264
329
  });
330
+ const unverifiable = failures.filter((f) => f.fail.unverifiable);
331
+ const code = 'unportable-cwd';
332
+ if (unverifiable.length) {
333
+ const codes = [...new Set(unverifiable.map((f) => f.fail.unverifiable))].join(', ');
334
+ return httpError(400, `cwd non verificabile (esiste ma illeggibile: ${codes}), per: ${unverifiable.map((f) => f.id).join(', ')}: il percorso potrebbe esserci ma non si e' potuto verificare (permessi/symlink)`, {
335
+ code,
336
+ cells,
337
+ hint: 'non e\' necessariamente un path sbagliato: il percorso potrebbe esistere sotto la home ma i permessi della directory non hanno permesso di verificarlo',
338
+ });
339
+ }
265
340
  return httpError(400, `cwd non portabile (deve esistere sotto la home) per: ${failures.map((f) => f.id).join(', ')}`, {
266
- code: 'unportable-cwd',
341
+ code,
267
342
  cells,
268
343
  hint: 'il percorso appartiene a un altro dispositivo o non esiste sotto la home di questo device: scegli una cartella sotto la home',
269
344
  });
@@ -298,6 +373,9 @@ async function createBuiltinFleet(cfg = {}) {
298
373
  const off = {
299
374
  available: false, provider: 'builtin',
300
375
  isCellSession: () => false, capabilities: () => [],
376
+ // B4: close() sempre sicura su qualunque Fleet restituita, anche unavailable:
377
+ // il caller non deve distinguere `off` dalla Fleet reale.
378
+ close: async () => {},
301
379
  };
302
380
  const blocked = (reason, code) => ({ ...off, reason, ...(code ? { migrationCode: code } : {}) });
303
381
  if (cfg.builtinEnabled === false) return off;
@@ -306,16 +384,38 @@ async function createBuiltinFleet(cfg = {}) {
306
384
  const defsPath = cfg.fleetDefsPath || path.join(home, '.nexuscrew', 'fleet.json');
307
385
  const tmuxBin = cfg.tmuxBin || process.env.TMUX_BIN || 'tmux';
308
386
  const readonly = () => (cfg.readonlyDefault === true || process.env.NEXUSCREW_READONLY === '1');
309
- const launchBroker = cfg.launchBroker || createLaunchBroker({ ...cfg, home });
387
+ // Lease opt-in: attivo solo quando cfg.cellLeaseEnabled === true. server.js lo
388
+ // passa true in start(); i test che chiamano createServer() direttamente non
389
+ // passano il flag, quindi la suite esistente non vede endpoint lease aperti da up().
390
+ const cellLeaseEnabled = cfg.cellLeaseEnabled === true;
391
+
392
+ // --- P1-2 (audit 3405df0): TUTTI i gate availability AVVENGONO PRIMA di creare
393
+ // leaseManager/launchBroker e di leaseManager.boot(). leaseManager non esiste finche'
394
+ // la Fleet non e' confermata disponibile, quindi NESSUN endpoint lease resta vivo su
395
+ // un early return (off/blocked) finche' ogni gate resta sopra questa boundary.
396
+ // P2 (audit 2a05db2): questo e' un fatto contingente del ref corrente, NON una
397
+ // garanzia anti-refactor. Un gate aggiunto DOPO la creazione del manager passerebbe
398
+ // inosservato senza un controllo statico/mutation-sensitive: il commento non impedisce
399
+ // un `return off` futuro oltre la boundary. La promessa vera e' limitata: oggi nessun
400
+ // early return segue la creazione del manager, e la creazione avviene dopo tutti i
401
+ // gate noti. Manternerla richiede che ogni nuovo gate resti qui sopra.
402
+
403
+ // Bootstrap: fleet.json valido? garbage -> unavailable (fail-closed, design §7).
404
+ // ENOENT (missing) o parse invalido -> off (unavailable legittimo, fail-closed);
405
+ // EACCES/ELOOP sul lstat (file presente ma non verificabile) -> off CON reason,
406
+ // perche' "non ho potuto guardare" e' un problema diverso da "non c'e'" o "e'
407
+ // garbage". Verdetto (available:false) invariato; il discriminante e' CHI ha
408
+ // fallito, non che ci sia stata un'eccezione.
409
+ const bootStat = {};
410
+ let boot = loadDefinitions(defsPath, bootStat);
411
+ if (!boot) return bootStat.lstatBlocked
412
+ ? { ...off, reason: `fleet.json non verificabile (${bootStat.lstatBlocked}): fail-closed, non "assente" né "invalido"` }
413
+ : off;
310
414
 
311
- // Bootstrap: fleet.json valido? garbage -> unavailable (fail-closed, design §7)
312
- let boot = loadDefinitions(defsPath);
313
- if (!boot) return off;
314
415
  if (!readonly()) {
315
- // La migrazione precede QUALUNQUE backfill/scrittura: loadDefinitions
316
- // normalizza i nomi legacy soltanto in memoria. Se il rename e' ambiguo o
317
- // fallisce, fleet.json resta byte-invariato e la Fleet non puo creare una
318
- // seconda sessione safe accanto a quella legacy.
416
+ // La migrazione precede QUALUNQUE backfill/scrittura: loadDefinitions normalizza
417
+ // i nomi legacy solo in memoria. Se il rename e' ambiguo o fallisce, fleet.json
418
+ // resta byte-invariato e la Fleet non puo creare una seconda sessione safe.
319
419
  let migration;
320
420
  try {
321
421
  migration = await migrateLegacyTmuxSessions(tmuxBin, boot, { readonly: false });
@@ -351,12 +451,46 @@ async function createBuiltinFleet(cfg = {}) {
351
451
  });
352
452
  await ensureProtection();
353
453
 
454
+ // --- Tutti i gate availability passati. Ora (e solo ora) creiamo leaseManager e
455
+ // launchBroker e riapriamo gli endpoint lease delle celle note (R3.3.1 recovery,
456
+ // rilievo 3). Non fatale: un fallimento di singolo endpoint non blocca il boot.
457
+ const leaseManager = cellLeaseEnabled ? createLeaseManager({ ...cfg, home }) : null;
458
+ const launchBroker = cfg.launchBroker || createLaunchBroker({
459
+ ...cfg, home,
460
+ // R3.1.1: la connessione broker one-shot resta APERTA dopo il payload e diviene
461
+ // il canale lease del supervisore.
462
+ ...(leaseManager ? {
463
+ onLease: (socket, lease) => {
464
+ // P1-3 (reaudit dd38c83): attachInitial restituisce false se la
465
+ // persistenza del bind fallisce. F-B (audit 2a @ 142e272): ora il
466
+ // broker chiama onLease PRIMA di scrivere il payload. Il ritorno
467
+ // ESPLICITO false (o la socket distrutta qui) comunica al broker di NON
468
+ // consegnare il payload: il supervisore riceve la chiusura prima del
469
+ // frame e non spawna MAI un child con una lease che il server non ha
470
+ // committato; e non resta un lease-client connesso a un manager vuoto
471
+ // (state=none) — l'orfano di prima nasceva cosi'.
472
+ const ok = lease && lease.cellId
473
+ ? leaseManager.attachInitial(lease.cellId, socket, { generation: 0 })
474
+ : false;
475
+ if (!ok) {
476
+ try { socket.destroy(); } catch (_) {}
477
+ }
478
+ return ok;
479
+ },
480
+ } : {}),
481
+ });
482
+ if (leaseManager) {
483
+ try { await leaseManager.boot(); } catch {
484
+ // logged dal manager; un fallimento di singolo endpoint non blocca il boot.
485
+ }
486
+ }
487
+
354
488
  // Runtime estratto (lib/fleet/runtime.js): possiede cache + definizioni e
355
489
  // espone status/up/down/restart/isCellSession + gli accessor allo store
356
490
  // (reloadDefs/findCell/findEngine/refreshSessions/commitDefs) che il CRUD
357
491
  // qui sotto riusa. status/up/down/restart sono INVARIATI.
358
492
  const rt = createBuiltinRuntime({
359
- cfg, home, defsPath, tmuxBin, readonly, launchBroker, boot, ensureProtection,
493
+ cfg, home, defsPath, tmuxBin, readonly, launchBroker, leaseManager, boot, ensureProtection,
360
494
  });
361
495
  const {
362
496
  status, cellStatus, up, down, restart, isCellSession,
@@ -1039,7 +1173,7 @@ async function createBuiltinFleet(cfg = {}) {
1039
1173
  return { engine: profile.id, model: wanted, ...out };
1040
1174
  }
1041
1175
 
1042
- async function close() { await launchBroker.close(); }
1176
+ async function close() { await launchBroker.close(); if (leaseManager) leaseManager.close(); }
1043
1177
 
1044
1178
  return {
1045
1179
  available: true,
@@ -1050,6 +1184,10 @@ async function createBuiltinFleet(cfg = {}) {
1050
1184
  schema, definitions, capabilities,
1051
1185
  credentialStatus, setLocalCredential, removeLocalCredential, testModel,
1052
1186
  defineModel, removeModel, close,
1187
+ // Fetta 2b (D3): il lease Live esposto alle route /api/lease, che il bridge
1188
+ // MCP delle celle consuma per register/refresh/recovery (B5). Assente se il
1189
+ // lease e' disattivato: le route rispondono 501, non 500.
1190
+ ...(leaseManager ? { lease: leaseManager } : {}),
1053
1191
  };
1054
1192
  }
1055
1193
 
@@ -1060,6 +1198,10 @@ module.exports = {
1060
1198
  backfillKimiEngine,
1061
1199
  backfillGrokEngine,
1062
1200
  backfillVlEngine,
1201
+ // Esportate ma NON chiamate qui sopra (§bootstrap, righe ~354-358): scelta
1202
+ // deliberata, non una dimenticanza — vedi il commento su backfillDesktopEngine.
1203
+ backfillDesktopEngine,
1204
+ defaultDesktopEngine,
1063
1205
  resolveCellCwd,
1064
1206
  composeLaunchArgv,
1065
1207
  composeClientInvocation,
@@ -0,0 +1,328 @@
1
+ {
2
+ "models": [
3
+ {
4
+ "slug": "deepseek-v4-flash",
5
+ "display_name": "deepseek-v4-flash",
6
+ "description": "OpenCode Go: deepseek-v4-flash",
7
+ "default_reasoning_level": "high",
8
+ "supported_reasoning_levels": [
9
+ {
10
+ "effort": "low",
11
+ "description": "Fast responses with lighter reasoning"
12
+ },
13
+ {
14
+ "effort": "high",
15
+ "description": "Greater reasoning depth for complex problems"
16
+ },
17
+ {
18
+ "effort": "max",
19
+ "description": "Maximum reasoning depth"
20
+ }
21
+ ],
22
+ "shell_type": "default",
23
+ "visibility": "list",
24
+ "supported_in_api": true,
25
+ "priority": 50,
26
+ "availability_nux": null,
27
+ "upgrade": null,
28
+ "base_instructions": "",
29
+ "supports_reasoning_summaries": true,
30
+ "default_reasoning_summary": "none",
31
+ "support_verbosity": false,
32
+ "default_verbosity": null,
33
+ "apply_patch_tool_type": null,
34
+ "web_search_tool_type": "text",
35
+ "truncation_policy": {
36
+ "mode": "tokens",
37
+ "limit": 1000000
38
+ },
39
+ "supports_parallel_tool_calls": false,
40
+ "supports_image_detail_original": false,
41
+ "context_window": 1000000,
42
+ "effective_context_window_percent": 95,
43
+ "experimental_supported_tools": [],
44
+ "input_modalities": [
45
+ "text"
46
+ ],
47
+ "supports_search_tool": false
48
+ },
49
+ {
50
+ "slug": "deepseek-v4-pro",
51
+ "display_name": "deepseek-v4-pro",
52
+ "description": "OpenCode Go: deepseek-v4-pro",
53
+ "default_reasoning_level": "high",
54
+ "supported_reasoning_levels": [
55
+ {
56
+ "effort": "low",
57
+ "description": "Fast responses with lighter reasoning"
58
+ },
59
+ {
60
+ "effort": "high",
61
+ "description": "Greater reasoning depth for complex problems"
62
+ },
63
+ {
64
+ "effort": "max",
65
+ "description": "Maximum reasoning depth"
66
+ }
67
+ ],
68
+ "shell_type": "default",
69
+ "visibility": "list",
70
+ "supported_in_api": true,
71
+ "priority": 56,
72
+ "availability_nux": null,
73
+ "upgrade": null,
74
+ "base_instructions": "",
75
+ "supports_reasoning_summaries": true,
76
+ "default_reasoning_summary": "none",
77
+ "support_verbosity": false,
78
+ "default_verbosity": null,
79
+ "apply_patch_tool_type": null,
80
+ "web_search_tool_type": "text",
81
+ "truncation_policy": {
82
+ "mode": "tokens",
83
+ "limit": 1000000
84
+ },
85
+ "supports_parallel_tool_calls": false,
86
+ "supports_image_detail_original": false,
87
+ "context_window": 1000000,
88
+ "effective_context_window_percent": 95,
89
+ "experimental_supported_tools": [],
90
+ "input_modalities": [
91
+ "text"
92
+ ],
93
+ "supports_search_tool": false
94
+ },
95
+ {
96
+ "slug": "gpt-5.6-luna",
97
+ "display_name": "gpt-5.6-luna",
98
+ "description": "OpenCode Go: gpt-5.6-luna",
99
+ "default_reasoning_level": "medium",
100
+ "supported_reasoning_levels": [
101
+ {
102
+ "effort": "low",
103
+ "description": "Fast responses with lighter reasoning"
104
+ },
105
+ {
106
+ "effort": "medium",
107
+ "description": "Balanced reasoning depth"
108
+ },
109
+ {
110
+ "effort": "high",
111
+ "description": "Greater reasoning depth for complex problems"
112
+ }
113
+ ],
114
+ "shell_type": "default",
115
+ "visibility": "list",
116
+ "supported_in_api": true,
117
+ "priority": 51,
118
+ "availability_nux": null,
119
+ "upgrade": null,
120
+ "base_instructions": "",
121
+ "supports_reasoning_summaries": true,
122
+ "default_reasoning_summary": "none",
123
+ "support_verbosity": false,
124
+ "default_verbosity": null,
125
+ "apply_patch_tool_type": null,
126
+ "web_search_tool_type": "text",
127
+ "truncation_policy": {
128
+ "mode": "tokens",
129
+ "limit": 1050000
130
+ },
131
+ "supports_parallel_tool_calls": false,
132
+ "supports_image_detail_original": true,
133
+ "context_window": 1050000,
134
+ "effective_context_window_percent": 95,
135
+ "experimental_supported_tools": [],
136
+ "input_modalities": [
137
+ "text",
138
+ "image"
139
+ ],
140
+ "supports_search_tool": false
141
+ },
142
+ {
143
+ "slug": "grok-4.5",
144
+ "display_name": "grok-4.5",
145
+ "description": "OpenCode Go: grok-4.5",
146
+ "default_reasoning_level": "medium",
147
+ "supported_reasoning_levels": [
148
+ {
149
+ "effort": "low",
150
+ "description": "Fast responses with lighter reasoning"
151
+ },
152
+ {
153
+ "effort": "medium",
154
+ "description": "Balanced reasoning depth"
155
+ },
156
+ {
157
+ "effort": "high",
158
+ "description": "Greater reasoning depth for complex problems"
159
+ }
160
+ ],
161
+ "shell_type": "default",
162
+ "visibility": "list",
163
+ "supported_in_api": true,
164
+ "priority": 52,
165
+ "availability_nux": null,
166
+ "upgrade": null,
167
+ "base_instructions": "",
168
+ "supports_reasoning_summaries": true,
169
+ "default_reasoning_summary": "none",
170
+ "support_verbosity": false,
171
+ "default_verbosity": null,
172
+ "apply_patch_tool_type": null,
173
+ "web_search_tool_type": "text",
174
+ "truncation_policy": {
175
+ "mode": "tokens",
176
+ "limit": 500000
177
+ },
178
+ "supports_parallel_tool_calls": false,
179
+ "supports_image_detail_original": true,
180
+ "context_window": 500000,
181
+ "effective_context_window_percent": 95,
182
+ "experimental_supported_tools": [],
183
+ "input_modalities": [
184
+ "text",
185
+ "image"
186
+ ],
187
+ "supports_search_tool": false
188
+ },
189
+ {
190
+ "slug": "glm-5.2",
191
+ "display_name": "glm-5.2",
192
+ "description": "OpenCode Go: glm-5.2",
193
+ "default_reasoning_level": "medium",
194
+ "supported_reasoning_levels": [
195
+ {
196
+ "effort": "low",
197
+ "description": "Fast responses with lighter reasoning"
198
+ },
199
+ {
200
+ "effort": "medium",
201
+ "description": "Balanced reasoning depth"
202
+ },
203
+ {
204
+ "effort": "high",
205
+ "description": "Greater reasoning depth for complex problems"
206
+ }
207
+ ],
208
+ "shell_type": "default",
209
+ "visibility": "list",
210
+ "supported_in_api": true,
211
+ "priority": 53,
212
+ "availability_nux": null,
213
+ "upgrade": null,
214
+ "base_instructions": "",
215
+ "supports_reasoning_summaries": true,
216
+ "default_reasoning_summary": "none",
217
+ "support_verbosity": false,
218
+ "default_verbosity": null,
219
+ "apply_patch_tool_type": null,
220
+ "web_search_tool_type": "text",
221
+ "truncation_policy": {
222
+ "mode": "tokens",
223
+ "limit": 1000000
224
+ },
225
+ "supports_parallel_tool_calls": false,
226
+ "supports_image_detail_original": false,
227
+ "context_window": 1000000,
228
+ "effective_context_window_percent": 95,
229
+ "experimental_supported_tools": [],
230
+ "input_modalities": [
231
+ "text"
232
+ ],
233
+ "supports_search_tool": false
234
+ },
235
+ {
236
+ "slug": "glm-5.1",
237
+ "display_name": "glm-5.1",
238
+ "description": "OpenCode Go: glm-5.1",
239
+ "default_reasoning_level": "medium",
240
+ "supported_reasoning_levels": [
241
+ {
242
+ "effort": "low",
243
+ "description": "Fast responses with lighter reasoning"
244
+ },
245
+ {
246
+ "effort": "medium",
247
+ "description": "Balanced reasoning depth"
248
+ },
249
+ {
250
+ "effort": "high",
251
+ "description": "Greater reasoning depth for complex problems"
252
+ }
253
+ ],
254
+ "shell_type": "default",
255
+ "visibility": "list",
256
+ "supported_in_api": true,
257
+ "priority": 54,
258
+ "availability_nux": null,
259
+ "upgrade": null,
260
+ "base_instructions": "",
261
+ "supports_reasoning_summaries": true,
262
+ "default_reasoning_summary": "none",
263
+ "support_verbosity": false,
264
+ "default_verbosity": null,
265
+ "apply_patch_tool_type": null,
266
+ "web_search_tool_type": "text",
267
+ "truncation_policy": {
268
+ "mode": "tokens",
269
+ "limit": 202752
270
+ },
271
+ "supports_parallel_tool_calls": false,
272
+ "supports_image_detail_original": false,
273
+ "context_window": 202752,
274
+ "effective_context_window_percent": 95,
275
+ "experimental_supported_tools": [],
276
+ "input_modalities": [
277
+ "text"
278
+ ],
279
+ "supports_search_tool": false
280
+ },
281
+ {
282
+ "slug": "glm-5",
283
+ "display_name": "glm-5",
284
+ "description": "OpenCode Go: glm-5",
285
+ "default_reasoning_level": "medium",
286
+ "supported_reasoning_levels": [
287
+ {
288
+ "effort": "low",
289
+ "description": "Fast responses with lighter reasoning"
290
+ },
291
+ {
292
+ "effort": "medium",
293
+ "description": "Balanced reasoning depth"
294
+ },
295
+ {
296
+ "effort": "high",
297
+ "description": "Greater reasoning depth for complex problems"
298
+ }
299
+ ],
300
+ "shell_type": "default",
301
+ "visibility": "list",
302
+ "supported_in_api": true,
303
+ "priority": 55,
304
+ "availability_nux": null,
305
+ "upgrade": null,
306
+ "base_instructions": "",
307
+ "supports_reasoning_summaries": true,
308
+ "default_reasoning_summary": "none",
309
+ "support_verbosity": false,
310
+ "default_verbosity": null,
311
+ "apply_patch_tool_type": null,
312
+ "web_search_tool_type": "text",
313
+ "truncation_policy": {
314
+ "mode": "tokens",
315
+ "limit": 202752
316
+ },
317
+ "supports_parallel_tool_calls": false,
318
+ "supports_image_detail_original": false,
319
+ "context_window": 202752,
320
+ "effective_context_window_percent": 95,
321
+ "experimental_supported_tools": [],
322
+ "input_modalities": [
323
+ "text"
324
+ ],
325
+ "supports_search_tool": false
326
+ }
327
+ ]
328
+ }