@mmmbuto/nexuscrew 0.9.23 → 0.9.25

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.
@@ -86,7 +86,7 @@ Lifecycle semantics:
86
86
 
87
87
  const CLI_VALUE_FLAGS = new Set([
88
88
  'label', 'ssh', 'ssh-port', 'autostart', 'visibility', 'selected',
89
- 'name', 'local-name', 'local-label', 'identity-file', 'port',
89
+ 'name', 'local-name', 'local-label', 'identity-file', 'port', 'dir',
90
90
  ]);
91
91
 
92
92
  // valueFlags (Set|array opzionale): flag che consumano il token successivo nella
@@ -1508,6 +1508,38 @@ function dispatch(argv, opts = {}) {
1508
1508
  serve({ ...opts, pidfile: flags.pidfile, serverStart: opts.serverStart });
1509
1509
  return { code: 0, keepAlive: true }; // server.listen tiene il processo vivo; non exit
1510
1510
  }
1511
+ if (cmd === 'identity') {
1512
+ const sub = rest[1];
1513
+ if (sub !== 'provision') {
1514
+ log('usage: nexuscrew identity provision [--force] [--no-write-config] [--dir <path>]');
1515
+ return { code: sub === undefined ? 0 : 1 };
1516
+ }
1517
+ const provision = require('../fleet/identity-provision.js').provisionIdentityAuthority;
1518
+ const home = opts.home || require('node:os').homedir();
1519
+ const result = provision({
1520
+ home,
1521
+ dir: flags.dir,
1522
+ force: flags.force === true,
1523
+ writeConfig: flags['no-write-config'] !== true,
1524
+ });
1525
+ if (!result.ok) {
1526
+ log(`identity provision: ${result.message}`);
1527
+ log(` existing credential files:`);
1528
+ for (const filePath of result.existing) log(` ${filePath}`);
1529
+ return { code: 1 };
1530
+ }
1531
+ log(`identity provision: credentials written under ${result.dir} (0600, dir 0700)`);
1532
+ log('identity provision: fleet.identity.mode = "authority"');
1533
+ if (result.configUpdated) {
1534
+ log(`identity provision: config updated (${result.mode})`);
1535
+ } else {
1536
+ log('identity provision: config NOT written (--no-write-config). Apply manually:');
1537
+ log(' fleet.identity.mode = "authority"');
1538
+ log(` keep both credential files in place under ${result.dir}`);
1539
+ }
1540
+ log('identity provision: restart the NexusCrew service to activate authority mode');
1541
+ return { code: 0 };
1542
+ }
1511
1543
  if (cmd === 'doctor') {
1512
1544
  if (flags.peers === true) return dispatchNodes(['nodes', 'doctor'], flags, opts);
1513
1545
  const r = doctor({ ...opts, log });
package/lib/cli/doctor.js CHANGED
@@ -462,6 +462,24 @@ function checkMcpIdentity(env) {
462
462
  const e = env || process.env;
463
463
  const hasTmux = !!(typeof e.TMUX === 'string' && e.TMUX);
464
464
  const hasSession = !!(typeof e.NEXUSCREW_MCP_SESSION === 'string' && e.NEXUSCREW_MCP_SESSION.trim());
465
+ const verifiedNames = [
466
+ 'NEXUSCREW_VERIFIED_ENV_VERSION',
467
+ 'NEXUSCREW_VERIFIED_OWNER_INSTANCE_ID',
468
+ 'NEXUSCREW_VERIFIED_CELL_ID',
469
+ 'NEXUSCREW_VERIFIED_INCARNATION_ID',
470
+ 'NEXUSCREW_VERIFIED_BINDING_ID',
471
+ 'NEXUSCREW_VERIFIED_ORIGIN',
472
+ 'NEXUSCREW_VERIFIED_THREAD_ID',
473
+ ];
474
+ const verifiedPresent = verifiedNames.filter((name) => typeof e[name] === 'string' && e[name]);
475
+ if (verifiedPresent.length) {
476
+ return {
477
+ name: 'MCP identity env',
478
+ ok: true,
479
+ detail: `metadati verified osservabili (${verifiedPresent.length}/7 nomi NEXUSCREW_VERIFIED_*): `
480
+ + 'identita attribuita solo dopo introspezione authority riuscita',
481
+ };
482
+ }
465
483
  if (hasTmux || hasSession) {
466
484
  const src = [];
467
485
  if (hasTmux) src.push('TMUX');
@@ -469,16 +487,80 @@ function checkMcpIdentity(env) {
469
487
  return {
470
488
  name: 'MCP identity env',
471
489
  ok: true,
472
- detail: `identita osservabile (${src.join('+')})`,
490
+ detail: `identita legacy osservabile (${src.join('+')})`,
473
491
  };
474
492
  }
475
493
  return {
476
494
  name: 'MCP identity env',
477
495
  ok: true, // mai FAIL: PWA-only senza MCP configurato non deve rompere il doctor
478
496
  warn: true,
479
- detail: 'nessuna identita MCP osservabile (TMUX/NEXUSCREW_MCP_SESSION assenti nel processo doctor); '
480
- + 'se integri l\'MCP stdio allowlista i nomi (codex-vl mcp add --env-var) — PWA-only: ignorabile',
497
+ detail: 'nessuna identita MCP osservabile (nessun NEXUSCREW_VERIFIED_* e nessuna variabile legacy '
498
+ + 'nel processo doctor). Percorsi validi: sessione verified (codex-vl >= 0.154.0-vl.3 + NexusCrew '
499
+ + '>= 0.9.25 con authority mode provisionato) oppure sessione legacy con le tre variabili ereditate '
500
+ + 'dal client MCP. NON allowlistare i nomi legacy in env_vars del server MCP: con vl.3 lo spawn '
501
+ + 'verified fallisce — PWA-only: ignorabile',
502
+ };
503
+ }
504
+
505
+ // Stato dell'authority mode (registro flotta). Mostra mode e costruibilita' dell'
506
+ // authority (file presenti, permessi) SENZA mai esporre valori.
507
+ function checkIdentityAuthorityMode({ home, configPath, env } = {}) {
508
+ const e = env || process.env;
509
+ const fs = require('node:fs');
510
+ const path = require('node:path');
511
+ const cfgPath = configPath
512
+ || process.env.NEXUSCREW_CONFIG_FILE
513
+ || path.join(home || os.homedir(), '.nexuscrew', 'config.json');
514
+ let cfg = {};
515
+ try { cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')) || {}; } catch (_) { cfg = {}; }
516
+ const mode = cfg.fleetIdentityMode
517
+ || (cfg.fleet && cfg.fleet.identity && cfg.fleet.identity.mode)
518
+ || 'legacy';
519
+ const dir = cfg.identityAuthorityDir || path.join(home || os.homedir(), '.nexuscrew', 'identity-authority');
520
+ const files = ['daemon.credential', 'launcher.credential'];
521
+ const present = [];
522
+ const values = {};
523
+ const problems = [];
524
+ for (const name of files) {
525
+ const filePath = path.join(dir, name);
526
+ try {
527
+ const st = fs.lstatSync(filePath);
528
+ if (!st.isFile()) { problems.push(`${name}: non regolare`); continue; }
529
+ if ((st.mode & 0o777) !== 0o600) { problems.push(`${name}: permessi non 0600`); continue; }
530
+ const value = fs.readFileSync(filePath, 'utf8').trim();
531
+ if (!value) { problems.push(`${name}: vuoto`); continue; }
532
+ present.push(name);
533
+ values[name] = value;
534
+ } catch (_) { problems.push(`${name}: assente`); }
535
+ }
536
+ // The two credentials must be DISTINCT (constant-time comparison):
537
+ // identical means the authority is not constructible, fail-closed.
538
+ let distinctLine = 'credentials distinct: n/a (una o entrambe assenti)';
539
+ if (present.length === 2) {
540
+ const distinct = Buffer.from(values['daemon.credential'])
541
+ .compare(Buffer.from(values['launcher.credential'])) !== 0;
542
+ distinctLine = `credentials distinct: ${distinct ? 'yes' : 'no'}`;
543
+ if (!distinct) problems.push('credenziali daemon e launcher identiche');
544
+ }
545
+ const constructible = present.length === 2 && problems.length === 0;
546
+ const ok = mode !== 'authority' || constructible;
547
+ const out = {
548
+ name: 'Fleet identity authority mode',
549
+ ok,
550
+ detail: `mode=${mode}; credenziali: ${present.length === 2 ? 'entrambe presenti (0600)' : present.join('+') || 'nessuna'} `
551
+ + `(${dir}); ${distinctLine}${problems.length ? '; problemi: ' + problems.join(', ') : ''}`,
481
552
  };
553
+ if (mode !== 'authority') {
554
+ out.warn = true;
555
+ out.detail += ' — il launcher espone CODEX_APP_SERVER_IDENTITY_REQUIRED=0: provisiona con '
556
+ + '`nexuscrew identity provision` per attivare l\'authority mode';
557
+ } else if (!constructible) {
558
+ out.detail += ' — authority: configured but unavailable: '
559
+ + (problems.length ? problems.join(', ') : 'credentials not constructible')
560
+ + ' — le celle protette (codex-vl) non partono [IDENTITY_AUTHORITY_UNAVAILABLE]';
561
+ }
562
+ void e;
563
+ return out;
482
564
  }
483
565
 
484
566
  function checkFleetDefinitions(home, fleetDefsPath, enabled = true) {
@@ -578,6 +660,7 @@ function doctor(opts = {}) {
578
660
  checkAutossh(existsImpl),
579
661
  checkSshPermitlisten(opts.sshVersion),
580
662
  checkMcpIdentity(opts.env),
663
+ checkIdentityAuthorityMode({ home: opts.home, env: opts.env }),
581
664
  checkTmuxServerCwd(platform, execImpl, { tmuxBin: opts.tmuxBin, procCwdImpl: opts.procCwdImpl }),
582
665
  checkTmuxServerTermuxPreload(opts.env, execImpl, { platform, home, tmuxBin: opts.tmuxBin }),
583
666
  ];
@@ -601,6 +684,6 @@ module.exports = {
601
684
  checkFleetServiceWorkingDirectory,
602
685
  checkTmuxSurvival, checkUserLinger,
603
686
  checkSshClient, checkAutossh, checkSshPermitlisten,
604
- checkMcpIdentity, checkTermuxExec,
687
+ checkMcpIdentity, checkIdentityAuthorityMode, checkTermuxExec,
605
688
  checkTmuxServerCwd, checkTmuxServerTermuxPreload,
606
689
  };
package/lib/config.js CHANGED
@@ -135,8 +135,58 @@ function envOverrides() {
135
135
  }
136
136
 
137
137
  // Precedence: baseDefaults < config.json < env < opts. [B2]
138
+ // Le credenziali authority vivono in due file 0600 nella dir
139
+ // dell'authority (default ~/.nexuscrew/identity-authority). Se esistono vengono
140
+ // caricate qui, senza MAI loggare i valori: builtin costruisce l'authority
141
+ // solo quando ENTRAMBE sono presenti e distinte.
142
+ function readIdentityAuthorityCredentials(cfg = {}) {
143
+ const fs = require('node:fs');
144
+ const crypto = require('node:crypto');
145
+ const dir = cfg.identityAuthorityDir
146
+ || path.join(cfg.home || os.homedir(), '.nexuscrew', 'identity-authority');
147
+ // Enforced AT LOAD. Identical credentials or wrong permissions
148
+ // (files != 0600, dir != 0700) => the credentials are NOT loaded: the
149
+ // authority is not constructed and the protected launch is refused instead.
150
+ // The reason travels in identityAuthorityFault, and never carries a value.
151
+ const faults = [];
152
+ let dirMode = null;
153
+ try { dirMode = fs.lstatSync(dir).mode & 0o777; } catch (_) { faults.push('authority dir missing'); }
154
+ if (dirMode !== null && dirMode !== 0o700) faults.push('authority dir mode is not 0700');
155
+ const readOne = (name) => {
156
+ try {
157
+ const st = fs.lstatSync(path.join(dir, name));
158
+ if (!st.isFile()) { faults.push(`${name}: not a regular file`); return undefined; }
159
+ if ((st.mode & 0o777) !== 0o600) { faults.push(`${name}: mode is not 0600`); return undefined; }
160
+ const value = fs.readFileSync(path.join(dir, name), 'utf8').trim();
161
+ if (!value) { faults.push(`${name}: empty`); return undefined; }
162
+ return value;
163
+ } catch (_) { faults.push(`${name}: missing`); return undefined; }
164
+ };
165
+ const daemon = readOne('daemon.credential');
166
+ const launcher = readOne('launcher.credential');
167
+ if (daemon && launcher) {
168
+ let distinct;
169
+ try {
170
+ distinct = !crypto.timingSafeEqual(Buffer.from(daemon), Buffer.from(launcher));
171
+ } catch (_) { distinct = false; }
172
+ if (!distinct) faults.push('daemon and launcher credentials are identical');
173
+ }
174
+ const out = {};
175
+ if (faults.length) {
176
+ out.identityAuthorityFault = faults.join('; ');
177
+ return out;
178
+ }
179
+ if (daemon) out.identityDaemonCredential = daemon;
180
+ if (launcher) out.identityLauncherCredential = launcher;
181
+ return out;
182
+ }
183
+
138
184
  function loadConfig(opts = {}) {
139
- return { ...baseDefaults(), ...readConfigJson(), ...envOverrides(), ...opts };
185
+ const merged = { ...baseDefaults(), ...readConfigJson(), ...envOverrides(), ...opts };
186
+ // I file di credenziale sono fallback: non sovrascrivono mai valori espliciti
187
+ // di config.json/env/opts (precedence invariata).
188
+ const creds = readIdentityAuthorityCredentials(merged);
189
+ return { ...merged, ...creds };
140
190
  }
141
191
 
142
192
  // Retrocompat: defaults() = baseDefaults + env (NO config.json — per test isolati
@@ -700,11 +700,23 @@ async function createBuiltinFleet(cfg = {}) {
700
700
  // leaseManager e launchBroker. L'authority resta server-side: nessuna
701
701
  // credenziale o chiave HMAC attraversa il payload consegnato alla cella.
702
702
  const identityMode = cfg.fleetIdentityMode || cfg.fleet?.identity?.mode || 'legacy';
703
+ // Identical credentials => the authority is NOT constructed (consistent with
704
+ // identity-authority, which would reject at runtime anyway), and the reason
705
+ // goes to the log.
706
+ const identityCredentialsDistinct = (typeof cfg.identityDaemonCredential === 'string'
707
+ && typeof cfg.identityLauncherCredential === 'string')
708
+ ? Buffer.from(cfg.identityDaemonCredential).compare(Buffer.from(cfg.identityLauncherCredential)) !== 0
709
+ : !!(cfg.identityDaemonCredential && cfg.identityLauncherCredential);
710
+ if (identityMode === 'authority' && !identityCredentialsDistinct && cfg.log) {
711
+ cfg.log('fleet identity: authority mode requested but daemon and launcher credentials '
712
+ + 'are identical or missing; identity enforcement stays off (REQUIRED=0)');
713
+ }
703
714
  const identityOwnerInstanceId = typeof cfg.identityOwnerInstanceId === 'function'
704
715
  ? cfg.identityOwnerInstanceId()
705
716
  : (cfg.identityOwnerInstanceId || null);
706
717
  const identityAuthority = cfg.identityAuthority || (
707
- cfg.identityDaemonCredential && cfg.identityLauncherCredential
718
+ identityCredentialsDistinct
719
+ && cfg.identityDaemonCredential && cfg.identityLauncherCredential
708
720
  ? createIdentityAuthority({
709
721
  dir: cfg.identityAuthorityDir || path.join(home, '.nexuscrew', 'identity-authority'),
710
722
  daemonCredential: cfg.identityDaemonCredential,
@@ -279,9 +279,102 @@
279
279
  "supports_search_tool": false
280
280
  },
281
281
  {
282
- "slug": "glm-5",
283
- "display_name": "glm-5",
284
- "description": "OpenCode Go: glm-5",
282
+ "slug": "deepseek-v4-flash-vision-exp",
283
+ "display_name": "deepseek-v4-flash-vision-exp",
284
+ "description": "OpenCode Go: deepseek-v4-flash-vision-exp (vision)",
285
+ "default_reasoning_level": "high",
286
+ "supported_reasoning_levels": [
287
+ {
288
+ "effort": "low",
289
+ "description": "Fast responses with lighter reasoning"
290
+ },
291
+ {
292
+ "effort": "high",
293
+ "description": "Greater reasoning depth for complex problems"
294
+ },
295
+ {
296
+ "effort": "max",
297
+ "description": "Maximum reasoning depth"
298
+ }
299
+ ],
300
+ "shell_type": "default",
301
+ "visibility": "list",
302
+ "supported_in_api": true,
303
+ "priority": 57,
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": 1000000
316
+ },
317
+ "supports_parallel_tool_calls": false,
318
+ "supports_image_detail_original": true,
319
+ "context_window": 1000000,
320
+ "effective_context_window_percent": 95,
321
+ "experimental_supported_tools": [],
322
+ "input_modalities": [
323
+ "text",
324
+ "image"
325
+ ],
326
+ "supports_search_tool": false
327
+ },
328
+ {
329
+ "slug": "deepseek-v4.1-flash",
330
+ "display_name": "deepseek-v4.1-flash",
331
+ "description": "OpenCode Go: deepseek-v4.1-flash",
332
+ "default_reasoning_level": "high",
333
+ "supported_reasoning_levels": [
334
+ {
335
+ "effort": "low",
336
+ "description": "Fast responses with lighter reasoning"
337
+ },
338
+ {
339
+ "effort": "high",
340
+ "description": "Greater reasoning depth for complex problems"
341
+ },
342
+ {
343
+ "effort": "max",
344
+ "description": "Maximum reasoning depth"
345
+ }
346
+ ],
347
+ "shell_type": "default",
348
+ "visibility": "list",
349
+ "supported_in_api": true,
350
+ "priority": 58,
351
+ "availability_nux": null,
352
+ "upgrade": null,
353
+ "base_instructions": "",
354
+ "supports_reasoning_summaries": true,
355
+ "default_reasoning_summary": "none",
356
+ "support_verbosity": false,
357
+ "default_verbosity": null,
358
+ "apply_patch_tool_type": null,
359
+ "web_search_tool_type": "text",
360
+ "truncation_policy": {
361
+ "mode": "tokens",
362
+ "limit": 1000000
363
+ },
364
+ "supports_parallel_tool_calls": false,
365
+ "supports_image_detail_original": false,
366
+ "context_window": 1000000,
367
+ "effective_context_window_percent": 95,
368
+ "experimental_supported_tools": [],
369
+ "input_modalities": [
370
+ "text"
371
+ ],
372
+ "supports_search_tool": false
373
+ },
374
+ {
375
+ "slug": "glm-5.3",
376
+ "display_name": "glm-5.3",
377
+ "description": "OpenCode Go: glm-5.3",
285
378
  "default_reasoning_level": "medium",
286
379
  "supported_reasoning_levels": [
287
380
  {
@@ -300,7 +393,7 @@
300
393
  "shell_type": "default",
301
394
  "visibility": "list",
302
395
  "supported_in_api": true,
303
- "priority": 55,
396
+ "priority": 59,
304
397
  "availability_nux": null,
305
398
  "upgrade": null,
306
399
  "base_instructions": "",
@@ -312,11 +405,11 @@
312
405
  "web_search_tool_type": "text",
313
406
  "truncation_policy": {
314
407
  "mode": "tokens",
315
- "limit": 202752
408
+ "limit": 1000000
316
409
  },
317
410
  "supports_parallel_tool_calls": false,
318
411
  "supports_image_detail_original": false,
319
- "context_window": 202752,
412
+ "context_window": 1000000,
320
413
  "effective_context_window_percent": 95,
321
414
  "experimental_supported_tools": [],
322
415
  "input_modalities": [
@@ -325,28 +418,74 @@
325
418
  "supports_search_tool": false
326
419
  },
327
420
  {
328
- "slug": "deepseek-v4-flash-vision-exp",
329
- "display_name": "deepseek-v4-flash-vision-exp",
330
- "description": "OpenCode Go: deepseek-v4-flash-vision-exp (vision)",
331
- "default_reasoning_level": "high",
421
+ "slug": "glm-5.3-flash",
422
+ "display_name": "glm-5.3-flash",
423
+ "description": "OpenCode Go: glm-5.3-flash",
424
+ "default_reasoning_level": "medium",
332
425
  "supported_reasoning_levels": [
333
426
  {
334
427
  "effort": "low",
335
428
  "description": "Fast responses with lighter reasoning"
336
429
  },
430
+ {
431
+ "effort": "medium",
432
+ "description": "Balanced reasoning depth"
433
+ },
337
434
  {
338
435
  "effort": "high",
339
436
  "description": "Greater reasoning depth for complex problems"
437
+ }
438
+ ],
439
+ "shell_type": "default",
440
+ "visibility": "list",
441
+ "supported_in_api": true,
442
+ "priority": 60,
443
+ "availability_nux": null,
444
+ "upgrade": null,
445
+ "base_instructions": "",
446
+ "supports_reasoning_summaries": true,
447
+ "default_reasoning_summary": "none",
448
+ "support_verbosity": false,
449
+ "default_verbosity": null,
450
+ "apply_patch_tool_type": null,
451
+ "web_search_tool_type": "text",
452
+ "truncation_policy": {
453
+ "mode": "tokens",
454
+ "limit": 1000000
455
+ },
456
+ "supports_parallel_tool_calls": false,
457
+ "supports_image_detail_original": false,
458
+ "context_window": 1000000,
459
+ "effective_context_window_percent": 95,
460
+ "experimental_supported_tools": [],
461
+ "input_modalities": [
462
+ "text"
463
+ ],
464
+ "supports_search_tool": false
465
+ },
466
+ {
467
+ "slug": "longcat-2.0",
468
+ "display_name": "longcat-2.0",
469
+ "description": "OpenCode Go: longcat-2.0",
470
+ "default_reasoning_level": "medium",
471
+ "supported_reasoning_levels": [
472
+ {
473
+ "effort": "low",
474
+ "description": "Fast responses with lighter reasoning"
340
475
  },
341
476
  {
342
- "effort": "max",
343
- "description": "Maximum reasoning depth"
477
+ "effort": "medium",
478
+ "description": "Balanced reasoning depth"
479
+ },
480
+ {
481
+ "effort": "high",
482
+ "description": "Greater reasoning depth for complex problems"
344
483
  }
345
484
  ],
346
485
  "shell_type": "default",
347
486
  "visibility": "list",
348
487
  "supported_in_api": true,
349
- "priority": 57,
488
+ "priority": 61,
350
489
  "availability_nux": null,
351
490
  "upgrade": null,
352
491
  "base_instructions": "",
@@ -361,13 +500,58 @@
361
500
  "limit": 1000000
362
501
  },
363
502
  "supports_parallel_tool_calls": false,
364
- "supports_image_detail_original": true,
503
+ "supports_image_detail_original": false,
365
504
  "context_window": 1000000,
366
505
  "effective_context_window_percent": 95,
367
506
  "experimental_supported_tools": [],
368
507
  "input_modalities": [
369
- "text",
370
- "image"
508
+ "text"
509
+ ],
510
+ "supports_search_tool": false
511
+ },
512
+ {
513
+ "slug": "hy4-preview",
514
+ "display_name": "hy4-preview",
515
+ "description": "OpenCode Go: hy4-preview",
516
+ "default_reasoning_level": "medium",
517
+ "supported_reasoning_levels": [
518
+ {
519
+ "effort": "low",
520
+ "description": "Fast responses with lighter reasoning"
521
+ },
522
+ {
523
+ "effort": "medium",
524
+ "description": "Balanced reasoning depth"
525
+ },
526
+ {
527
+ "effort": "high",
528
+ "description": "Greater reasoning depth for complex problems"
529
+ }
530
+ ],
531
+ "shell_type": "default",
532
+ "visibility": "list",
533
+ "supported_in_api": true,
534
+ "priority": 62,
535
+ "availability_nux": null,
536
+ "upgrade": null,
537
+ "base_instructions": "",
538
+ "supports_reasoning_summaries": true,
539
+ "default_reasoning_summary": "none",
540
+ "support_verbosity": false,
541
+ "default_verbosity": null,
542
+ "apply_patch_tool_type": null,
543
+ "web_search_tool_type": "text",
544
+ "truncation_policy": {
545
+ "mode": "tokens",
546
+ "limit": 1024000
547
+ },
548
+ "supports_parallel_tool_calls": false,
549
+ "supports_image_detail_original": false,
550
+ "context_window": 1024000,
551
+ "effective_context_window_percent": 95,
552
+ "experimental_supported_tools": [],
553
+ "input_modalities": [
554
+ "text"
371
555
  ],
372
556
  "supports_search_tool": false
373
557
  }