@addai/node 0.30.13 → 0.30.14

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 (143) hide show
  1. package/dist/auto-update.d.ts +3 -0
  2. package/dist/auto-update.js +15 -0
  3. package/dist/capabilities.d.ts +17 -3
  4. package/dist/capabilities.js +18 -8
  5. package/dist/claude-models.d.ts +4 -12
  6. package/dist/claude-models.js +12 -88
  7. package/dist/claude-print.d.ts +15 -0
  8. package/dist/claude-print.js +16 -0
  9. package/dist/codex-models.d.ts +81 -0
  10. package/dist/codex-models.js +385 -0
  11. package/dist/codex-spawn.d.ts +14 -8
  12. package/dist/codex-spawn.js +30 -10
  13. package/dist/command-runner.js +31 -9
  14. package/dist/desktop/docker.js +2 -0
  15. package/dist/desktop/manager.d.ts +2 -1
  16. package/dist/desktop/manager.js +51 -1
  17. package/dist/desktop/provider.d.ts +1 -1
  18. package/dist/desktop/provider.js +2 -0
  19. package/dist/desktop/relay-client.js +7 -0
  20. package/dist/desktop/spec.d.ts +2 -0
  21. package/dist/direct/aios-bridge.d.ts +13 -0
  22. package/dist/direct/aios-bridge.js +177 -0
  23. package/dist/direct/aios-mcp.d.ts +2 -0
  24. package/dist/direct/aios-mcp.js +132 -0
  25. package/dist/direct/aios-tools.d.ts +351 -0
  26. package/dist/direct/aios-tools.js +120 -0
  27. package/dist/direct/board-chat-provider.d.ts +64 -0
  28. package/dist/direct/board-chat-provider.js +151 -0
  29. package/dist/direct/board-chat.d.ts +40 -0
  30. package/dist/direct/board-chat.js +414 -0
  31. package/dist/direct/config.d.ts +2 -0
  32. package/dist/direct/config.js +60 -0
  33. package/dist/direct/launch.d.ts +14 -0
  34. package/dist/direct/launch.js +121 -0
  35. package/dist/direct/protocol.d.ts +46 -0
  36. package/dist/direct/protocol.js +84 -0
  37. package/dist/direct/relay-main.d.ts +1 -0
  38. package/dist/direct/relay-main.js +21 -0
  39. package/dist/direct/relay.d.ts +26 -0
  40. package/dist/direct/relay.js +334 -0
  41. package/dist/direct/sessions.d.ts +61 -0
  42. package/dist/direct/sessions.js +368 -0
  43. package/dist/direct/supervisor.d.ts +9 -0
  44. package/dist/direct/supervisor.js +137 -0
  45. package/dist/direct/terminal.d.ts +1 -0
  46. package/dist/direct/terminal.js +106 -0
  47. package/dist/direct/worker.d.ts +1 -0
  48. package/dist/direct/worker.js +278 -0
  49. package/dist/entitybox/container-action.cjs +85 -0
  50. package/dist/entitybox/container-watchdog.cjs +70 -0
  51. package/dist/entitybox/model-entry.d.ts +3 -0
  52. package/dist/entitybox/model-entry.js +233 -0
  53. package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
  54. package/dist/entitybox/model-mcp-proxy.js +85 -0
  55. package/dist/entitybox-application-boundary.d.ts +59 -0
  56. package/dist/entitybox-application-boundary.js +161 -0
  57. package/dist/entitybox-application-guard-core.d.ts +44 -0
  58. package/dist/entitybox-application-guard-core.js +164 -0
  59. package/dist/entitybox-application-guard-process.d.ts +1 -0
  60. package/dist/entitybox-application-guard-process.js +83 -0
  61. package/dist/entitybox-application-guard.d.ts +46 -0
  62. package/dist/entitybox-application-guard.js +239 -0
  63. package/dist/entitybox-capacity-health-host.d.ts +12 -0
  64. package/dist/entitybox-capacity-health-host.js +80 -0
  65. package/dist/entitybox-capacity-health-worker.d.ts +22 -0
  66. package/dist/entitybox-capacity-health-worker.js +168 -0
  67. package/dist/entitybox-capacity-host.d.ts +17 -0
  68. package/dist/entitybox-capacity-host.js +155 -0
  69. package/dist/entitybox-capacity-owner-process.d.ts +1 -0
  70. package/dist/entitybox-capacity-owner-process.js +32 -0
  71. package/dist/entitybox-capacity-owner.d.ts +16 -0
  72. package/dist/entitybox-capacity-owner.js +90 -0
  73. package/dist/entitybox-capacity-programs.d.ts +7 -0
  74. package/dist/entitybox-capacity-programs.js +49 -0
  75. package/dist/entitybox-capacity-provisioner.d.ts +82 -0
  76. package/dist/entitybox-capacity-provisioner.js +404 -0
  77. package/dist/entitybox-capacity-publication.d.ts +29 -0
  78. package/dist/entitybox-capacity-publication.js +199 -0
  79. package/dist/entitybox-capacity-registration.d.ts +36 -0
  80. package/dist/entitybox-capacity-registration.js +75 -0
  81. package/dist/entitybox-capacity-worker.d.ts +29 -0
  82. package/dist/entitybox-capacity-worker.js +113 -0
  83. package/dist/entitybox-computer-task.d.ts +69 -0
  84. package/dist/entitybox-computer-task.js +176 -0
  85. package/dist/entitybox-controller-recovery.d.ts +40 -0
  86. package/dist/entitybox-controller-recovery.js +156 -0
  87. package/dist/entitybox-desktop-action-server.d.ts +34 -0
  88. package/dist/entitybox-desktop-action-server.js +176 -0
  89. package/dist/entitybox-desktop-admission.d.ts +32 -0
  90. package/dist/entitybox-desktop-admission.js +79 -0
  91. package/dist/entitybox-display-binding.d.ts +13 -0
  92. package/dist/entitybox-display-binding.js +31 -0
  93. package/dist/entitybox-execution-container.d.ts +82 -0
  94. package/dist/entitybox-execution-container.js +450 -0
  95. package/dist/entitybox-execution-host.d.ts +40 -0
  96. package/dist/entitybox-execution-host.js +168 -0
  97. package/dist/entitybox-execution-lease.d.ts +81 -0
  98. package/dist/entitybox-execution-lease.js +332 -0
  99. package/dist/entitybox-file-inputs.d.ts +51 -0
  100. package/dist/entitybox-file-inputs.js +182 -0
  101. package/dist/entitybox-file-session.d.ts +38 -0
  102. package/dist/entitybox-file-session.js +82 -0
  103. package/dist/entitybox-host-process.d.ts +12 -0
  104. package/dist/entitybox-host-process.js +120 -0
  105. package/dist/entitybox-install.d.ts +10 -0
  106. package/dist/entitybox-install.js +40 -0
  107. package/dist/entitybox-mcp-broker.d.ts +77 -0
  108. package/dist/entitybox-mcp-broker.js +420 -0
  109. package/dist/entitybox-mcp-contract.d.ts +10 -0
  110. package/dist/entitybox-mcp-contract.js +24 -0
  111. package/dist/entitybox-mcp-guard.d.ts +3 -0
  112. package/dist/entitybox-mcp-guard.js +167 -0
  113. package/dist/entitybox-model-artifact.d.ts +9 -0
  114. package/dist/entitybox-model-artifact.js +83 -0
  115. package/dist/entitybox-native-services.d.ts +33 -0
  116. package/dist/entitybox-native-services.js +172 -0
  117. package/dist/entitybox-operation.d.ts +4 -0
  118. package/dist/entitybox-operation.js +34 -0
  119. package/dist/entitybox-provider-broker.d.ts +41 -0
  120. package/dist/entitybox-provider-broker.js +149 -0
  121. package/dist/entitybox-queue-request.d.ts +32 -0
  122. package/dist/entitybox-queue-request.js +141 -0
  123. package/dist/entitybox-recovery-host.d.ts +12 -0
  124. package/dist/entitybox-recovery-host.js +80 -0
  125. package/dist/entitybox-recovery-store.d.ts +33 -0
  126. package/dist/entitybox-recovery-store.js +150 -0
  127. package/dist/entitybox-recovery-worker.d.ts +30 -0
  128. package/dist/entitybox-recovery-worker.js +134 -0
  129. package/dist/entitybox-vault-provider.d.ts +27 -0
  130. package/dist/entitybox-vault-provider.js +127 -0
  131. package/dist/harness-registry.js +3 -1
  132. package/dist/index.js +72 -38
  133. package/dist/install.d.ts +14 -2
  134. package/dist/install.js +115 -10
  135. package/dist/model-cache.d.ts +38 -0
  136. package/dist/model-cache.js +137 -0
  137. package/dist/request-pump.d.ts +3 -0
  138. package/dist/request-pump.js +21 -7
  139. package/dist/session-runner.d.ts +8 -1
  140. package/dist/session-runner.js +48 -3
  141. package/dist/supabase-client.js +4 -1
  142. package/package.json +5 -1
  143. package/scripts/copy-assets.js +4 -2
@@ -103,6 +103,9 @@ type ProbeShape = Partial<Record<HarnessId, {
103
103
  available?: boolean;
104
104
  }>>;
105
105
  export interface AutoUpdateDeps {
106
+ /** Optional admission gate for direct sessions. Legacy callers keep the existing update path. */
107
+ quiesceDirect?: () => Promise<boolean>;
108
+ releaseDirect?: () => void;
106
109
  /** The daemon token — only used to derive this node's slot. */
107
110
  token: () => string | null;
108
111
  /** True while this node holds a child process for a live run. */
@@ -143,6 +143,21 @@ function installedHarnesses(probe) {
143
143
  /** One pass. Exported so `ainode` can be made to do it on demand later, and
144
144
  * so a test can drive it without waiting an hour. */
145
145
  async function runOnce(d) {
146
+ if (!d.quiesceDirect || shouldRun({ enabled: d.enabled(), busy: d.isBusy(), breakerOpen: d.breakerOpen() })) {
147
+ return runUpdate(d);
148
+ }
149
+ if (!await d.quiesceDirect()) {
150
+ state = { ...state, last_result: 'skipped', skipped_reason: 'busy' };
151
+ return;
152
+ }
153
+ try {
154
+ await runUpdate(d);
155
+ }
156
+ finally {
157
+ d.releaseDirect?.();
158
+ }
159
+ }
160
+ async function runUpdate(d) {
146
161
  const skip = shouldRun({ enabled: d.enabled(), busy: d.isBusy(), breakerOpen: d.breakerOpen() });
147
162
  if (skip) {
148
163
  state = { ...state, last_checked_at: new Date().toISOString(), last_result: 'skipped', skipped_reason: skip };
@@ -1,5 +1,7 @@
1
+ import { entityboxFileCapabilities } from './entitybox-file-session';
1
2
  import type { EngineInfo } from './desktop/provider';
2
3
  import { type ClaudeModelEntry } from './claude-models';
4
+ import { type CodexModelEntry } from './codex-models';
3
5
  import { type AutostartState } from './autostart';
4
6
  import { type AutoUpdateState } from './auto-update';
5
7
  import { type MachineSample } from './machine-metrics';
@@ -39,14 +41,15 @@ interface FlowsCapability {
39
41
  max_concurrent: number;
40
42
  }
41
43
  interface CapabilitiesShape {
44
+ entitybox?: {
45
+ files: NonNullable<ReturnType<typeof entityboxFileCapabilities>>;
46
+ };
42
47
  /** Version of @addai/node running. Lets the studio / CodeFlows
43
48
  * UIs show "outdated daemon" hints and helps us debug field issues
44
49
  * ("which version was this row produced by?"). */
45
50
  daemon_version?: string;
46
51
  claude: BinaryProbe & ClaudeModelFields;
47
- codex: BinaryProbe & {
48
- models?: string[];
49
- };
52
+ codex: BinaryProbe & CodexModelFields;
50
53
  kimi: BinaryProbe & {
51
54
  models?: string[];
52
55
  };
@@ -91,6 +94,17 @@ interface ClaudeModelFields {
91
94
  * fallback above — so a stale picker can be told apart from a live one. */
92
95
  models_source?: 'cli' | 'builtin';
93
96
  }
97
+ interface CodexModelFields {
98
+ /** Asked of codex's app-server (codex-models.ts); empty until it answers.
99
+ * There is no built-in list: a blank model — codex's own default — is the
100
+ * one choice that always runs. */
101
+ models?: string[];
102
+ model_catalog?: CodexModelEntry[];
103
+ models_source?: 'cli' | 'builtin';
104
+ /** 'account' when the list is what this node's login can run, 'bundled'
105
+ * when the login was left alone and it is what the CLI ships with. */
106
+ models_scope?: 'account' | 'bundled';
107
+ }
94
108
  /** Codex's credential store: $CODEX_HOME (or ~/.codex) + /auth.json. */
95
109
  export declare function codexHome(): string;
96
110
  /**
@@ -42,12 +42,14 @@ exports.codexHome = codexHome;
42
42
  exports.codexAuthFromStore = codexAuthFromStore;
43
43
  exports.probeCapabilities = probeCapabilities;
44
44
  const engine_1 = require("./desktop/engine");
45
+ const entitybox_file_session_1 = require("./entitybox-file-session");
45
46
  const child_process_1 = require("child_process");
46
47
  const fs = __importStar(require("fs"));
47
48
  const path = __importStar(require("path"));
48
49
  const os = __importStar(require("os"));
49
50
  const claude_binary_1 = require("./claude-binary");
50
51
  const claude_models_1 = require("./claude-models");
52
+ const codex_models_1 = require("./codex-models");
51
53
  const kimi_binary_1 = require("./kimi-binary");
52
54
  const gemini_binary_1 = require("./gemini-binary");
53
55
  const grok_binary_1 = require("./grok-binary");
@@ -132,12 +134,6 @@ const CLAUDE_MODELS = [
132
134
  'claude-sonnet-4-6',
133
135
  'claude-haiku-4-5',
134
136
  ];
135
- const CODEX_MODELS = [
136
- 'gpt-5',
137
- 'gpt-5-mini',
138
- 'o4',
139
- 'o4-mini',
140
- ];
141
137
  const KIMI_MODELS = [
142
138
  'kimi-k2-instruct',
143
139
  'kimi-k2-base',
@@ -257,7 +253,18 @@ async function probeCodex() {
257
253
  else {
258
254
  authShape = codexAuthFromStore(codexHome());
259
255
  }
260
- return { available: true, version: ver.stdout, models: CODEX_MODELS, ...authShape };
256
+ // Asked once per binary and version, in the background; see codex-models.ts.
257
+ // Its efforts replace the registry's when it answers — they are per model,
258
+ // and newer codex takes xhigh and max.
259
+ const live = (0, codex_models_1.codexModelsFor)(bin, `${bin}|${ver.stdout}`);
260
+ return {
261
+ available: true,
262
+ version: ver.stdout,
263
+ models: live?.models ?? [],
264
+ ...(live ? { model_catalog: live.catalog, models_scope: live.scope, efforts: (0, codex_models_1.offeredEfforts)(live) } : {}),
265
+ models_source: live ? 'cli' : 'builtin',
266
+ ...authShape,
267
+ };
261
268
  }
262
269
  async function probeKimi() {
263
270
  const bin = (0, kimi_binary_1.findKimiBinary)();
@@ -487,7 +494,8 @@ async function probeCapabilities() {
487
494
  // login strategy) so Studio and the TUI render from one source of truth.
488
495
  const deco = (id, probe) => ({
489
496
  ...probe,
490
- efforts: harness_registry_1.HARNESSES[id].efforts,
497
+ // A list the CLI gave itself (codex) beats the registry's.
498
+ efforts: probe.efforts?.length ? probe.efforts : harness_registry_1.HARNESSES[id].efforts,
491
499
  installable: (0, harness_registry_1.isInstallable)(id),
492
500
  login_strategy: harness_registry_1.HARNESSES[id].login.strategy,
493
501
  login_methods: [
@@ -495,7 +503,9 @@ async function probeCapabilities() {
495
503
  ...(harness_registry_1.HARNESSES[id].extraLogins ?? []).map(m => m.method),
496
504
  ],
497
505
  });
506
+ const fileCapability = (0, entitybox_file_session_1.entityboxFileCapabilities)(claude);
498
507
  return {
508
+ ...(fileCapability ? { entitybox: { files: fileCapability } } : {}),
499
509
  daemon_version: readDaemonVersion(),
500
510
  autostart: (0, autostart_1.status)(),
501
511
  container_engine: containerEngine,
@@ -1,3 +1,4 @@
1
+ import { type ModelProbeResult } from './model-cache';
1
2
  import { type CliInvocation } from './win';
2
3
  export interface ClaudeModelEntry {
3
4
  /** What goes to `--model`: an alias ('opus') or a full id. */
@@ -21,13 +22,7 @@ export interface ClaudeModelReport {
21
22
  * the probe ever sees, so it is not reported as this node's default. */
22
23
  defaultModel?: string;
23
24
  }
24
- export type ProbeResult = {
25
- ok: true;
26
- report: ClaudeModelReport;
27
- } | {
28
- ok: false;
29
- reason: string;
30
- };
25
+ export type ProbeResult = ModelProbeResult<ClaudeModelReport>;
31
26
  /**
32
27
  * Turn the `models` array of an initialize response into a report.
33
28
  *
@@ -67,13 +62,10 @@ export interface ModelCacheDeps {
67
62
  now: () => number;
68
63
  log: (line: string) => void;
69
64
  }
65
+ /** Refreshed every 6h, retried 30 min after a failure — see model-cache.ts. */
70
66
  export declare function createClaudeModelCache(deps: ModelCacheDeps): {
71
- /**
72
- * The models for this binary state, or null to mean "use the built-in
73
- * list". Never waits: a missing or stale answer is fetched in the
74
- * background and arrives on a later call.
75
- */
76
67
  get(bin: string, key: string): ClaudeModelReport | null;
68
+ last(): ClaudeModelReport | null;
77
69
  };
78
70
  /** What capabilities.ts reports for claude's models. */
79
71
  export declare function claudeModelsFor(bin: string, key: string): ClaudeModelReport | null;
@@ -77,9 +77,7 @@ exports.createClaudeModelCache = createClaudeModelCache;
77
77
  exports.claudeModelsFor = claudeModelsFor;
78
78
  const child_process_1 = require("child_process");
79
79
  const fs = __importStar(require("fs"));
80
- const os = __importStar(require("os"));
81
- const path = __importStar(require("path"));
82
- const paths_1 = require("./paths");
80
+ const model_cache_1 = require("./model-cache");
83
81
  const win_1 = require("./win");
84
82
  // What a model id may look like before it is passed on: `claude-opus-5-5`,
85
83
  // `opus[1m]`, `claude-haiku-4-5-20251001`. Anything else is dropped rather
@@ -164,34 +162,6 @@ const CREDENTIAL_ENV = [
164
162
  'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN',
165
163
  'CLAUDE_CODE_OAUTH_REFRESH_TOKEN', 'CLAUDE_CONFIG_DIR',
166
164
  ];
167
- /** A throwaway folder that is both the probe's cwd and its CLAUDE_CONFIG_DIR,
168
- * so no project's settings or CLAUDE.md are read and nothing the CLI writes
169
- * lands in the real ~/.claude. Folders older than a day are swept — a
170
- * probe's own cleanup can lose a race with Windows releasing the cwd. */
171
- function makeProbeDir() {
172
- const root = path.join(paths_1.RUNTIME_HOME, 'model-probe');
173
- try {
174
- fs.mkdirSync(root, { recursive: true });
175
- const cutoff = Date.now() - 24 * 60 * 60 * 1000;
176
- for (const name of fs.readdirSync(root)) {
177
- const p = path.join(root, name);
178
- try {
179
- if (fs.statSync(p).mtimeMs < cutoff)
180
- fs.rmSync(p, { recursive: true, force: true });
181
- }
182
- catch { /* next time */ }
183
- }
184
- return fs.mkdtempSync(path.join(root, 'p-'));
185
- }
186
- catch {
187
- try {
188
- return fs.mkdtempSync(path.join(os.tmpdir(), 'ainode-model-probe-'));
189
- }
190
- catch {
191
- return null;
192
- }
193
- }
194
- }
195
165
  const PROBE_ARGS = [
196
166
  '--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
197
167
  // No keychain reads (the macOS login), no hooks, no plugin sync, no
@@ -223,7 +193,10 @@ function probeClaudeModels(bin, opts = {}) {
223
193
  done({ ok: false, reason: err.message || String(err) });
224
194
  return;
225
195
  }
226
- const dir = opts.dir ?? makeProbeDir();
196
+ // Both the probe's cwd and its CLAUDE_CONFIG_DIR, so no project's
197
+ // settings or CLAUDE.md are read and nothing the CLI writes lands in the
198
+ // real ~/.claude.
199
+ const dir = opts.dir ?? (0, model_cache_1.makeProbeDir)();
227
200
  if (!dir) {
228
201
  done({ ok: false, reason: 'no folder to probe from' });
229
202
  return;
@@ -337,63 +310,14 @@ function probeClaudeModels(bin, opts = {}) {
337
310
  }
338
311
  /** Tests only. */
339
312
  function __resetIsolationForTests() { isolationBroken = false; }
340
- /* ── the cache the heartbeat reads ─────────────────────────────────────── */
341
- /** Re-ask now and then even when nothing changed: the CLI's list can move
342
- * without a CLI update. */
343
- const REFRESH_MS = 6 * 60 * 60 * 1000;
344
- const RETRY_AFTER_FAIL_MS = 30 * 60 * 1000;
313
+ /** Refreshed every 6h, retried 30 min after a failure — see model-cache.ts. */
345
314
  function createClaudeModelCache(deps) {
346
- let good = null;
347
- let failed = null;
348
- let inflight = null;
349
- const start = (bin, key) => {
350
- if (inflight)
351
- return;
352
- inflight = deps.probe(bin).then(result => {
353
- if (result.ok) {
354
- const changed = !good || good.report.models.join() !== result.report.models.join();
355
- good = { key, report: result.report, at: deps.now() };
356
- if (failed?.key === key)
357
- failed = null;
358
- if (changed) {
359
- const head = result.report.catalog.filter(e => e.alias && e.resolved !== e.id)
360
- .map(e => `${e.id} → ${e.resolved}`).join(', ');
361
- deps.log(`[models] claude: ${result.report.models.length} models from the CLI${head ? ` (${head})` : ''}`);
362
- }
363
- }
364
- else {
365
- // An answer this exact binary already gave stays good: a refresh
366
- // that fails is a reason to retry, not to forget it.
367
- failed = { key, at: deps.now() };
368
- const kept = good?.key === key;
369
- deps.log(`[models] could not read claude's model list (${result.reason}) — ${kept ? 'keeping the last answer' : 'offering the built-in list'}; next try in ${RETRY_AFTER_FAIL_MS / 60000} min`);
370
- }
371
- }).catch(() => { failed = { key, at: deps.now() }; })
372
- .finally(() => { inflight = null; });
373
- };
374
- return {
375
- /**
376
- * The models for this binary state, or null to mean "use the built-in
377
- * list". Never waits: a missing or stale answer is fetched in the
378
- * background and arrives on a later call.
379
- */
380
- get(bin, key) {
381
- const now = deps.now();
382
- const recentlyFailed = failed?.key === key && now - failed.at < RETRY_AFTER_FAIL_MS;
383
- if (good?.key === key) {
384
- if (now - good.at > REFRESH_MS && !recentlyFailed)
385
- start(bin, key);
386
- return good.report;
387
- }
388
- if (!recentlyFailed)
389
- start(bin, key);
390
- // A new CLI version that has not answered yet: keep showing the last
391
- // real list — closer to the truth than the constant, and it stops the
392
- // pickers flickering to the built-in list across an upgrade. Once the
393
- // new version has actually failed, the built-in list it is.
394
- return failed?.key === key ? null : (good?.report ?? null);
395
- },
396
- };
315
+ return (0, model_cache_1.createModelCache)({
316
+ ...deps,
317
+ agent: 'claude',
318
+ describe: report => report.catalog.filter(e => e.alias && e.resolved !== e.id)
319
+ .map(e => `${e.id} → ${e.resolved}`).join(', '),
320
+ });
397
321
  }
398
322
  const sharedCache = createClaudeModelCache({
399
323
  probe: bin => probeClaudeModels(bin),
@@ -2,6 +2,8 @@ import type { RuntimeEvent, RuntimePermissionMode, RuntimeEffortLevel } from './
2
2
  export interface ClaudePrintInput {
3
3
  workingDirectory: string;
4
4
  prompt: string;
5
+ /** Verified original file blocks for explicitly marked EntityBox chat. */
6
+ inputBlocks?: readonly ClaudePrintInputBlock[];
5
7
  permissionMode?: RuntimePermissionMode;
6
8
  effortLevel?: RuntimeEffortLevel;
7
9
  model?: string;
@@ -13,6 +15,19 @@ export interface ClaudePrintInput {
13
15
  /** Restrict to a specific set of tools (claude only). null/undefined = inherit. */
14
16
  allowedTools?: string[];
15
17
  }
18
+ export type ClaudePrintInputBlock = {
19
+ type: 'text';
20
+ text: string;
21
+ } | {
22
+ type: 'image' | 'document';
23
+ source: {
24
+ type: 'base64';
25
+ media_type: string;
26
+ data: string;
27
+ };
28
+ };
29
+ /** Stream JSON reaches stdin only; files never become command-line arguments. */
30
+ export declare function claudePrintInputMessage(prompt: string, blocks: readonly ClaudePrintInputBlock[]): string;
16
31
  /**
17
32
  * The text to emit for one flushed block, given what came before.
18
33
  *
@@ -5,6 +5,7 @@
5
5
  // our normalised RuntimeEvent shape, and resolves when the process exits.
6
6
  // No PTY needed — Claude detects non-TTY and runs headless.
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.claudePrintInputMessage = claudePrintInputMessage;
8
9
  exports.blockDelta = blockDelta;
9
10
  exports.lineToEvents = lineToEvents;
10
11
  exports.spawnClaudePrint = spawnClaudePrint;
@@ -13,6 +14,16 @@ const claude_binary_1 = require("./claude-binary");
13
14
  const win_1 = require("./win");
14
15
  const claude_system_prompt_1 = require("./claude-system-prompt");
15
16
  const prompt_transport_1 = require("./prompt-transport");
17
+ /** Stream JSON reaches stdin only; files never become command-line arguments. */
18
+ function claudePrintInputMessage(prompt, blocks) {
19
+ if (!Array.isArray(blocks) || blocks.length < 1 || blocks.length > 12 || !blocks.every(b => b && (b.type === 'text' && typeof b.text === 'string' || ['image', 'document'].includes(b.type) && 'source' in b && b.source?.type === 'base64' && typeof b.source.data === 'string' && /^[A-Za-z0-9+/]*={0,2}$/.test(b.source.data)
20
+ && (b.type === 'document' ? b.source.media_type === 'application/pdf' : ['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(b.source.media_type)))))
21
+ throw new Error('Invalid file input');
22
+ const message = JSON.stringify({ type: 'user', session_id: '', parent_tool_use_id: null, message: { role: 'user', content: [...blocks, { type: 'text', text: prompt }] } }) + '\n';
23
+ if (Buffer.byteLength(message) > 30 * 1024 * 1024)
24
+ throw new Error('File input is too large');
25
+ return message;
26
+ }
16
27
  /**
17
28
  * The text to emit for one flushed block, given what came before.
18
29
  *
@@ -113,6 +124,7 @@ function lineToEvents(line, suppressText = false) {
113
124
  return [{ type: `claude:${type || 'unknown'}`, raw: line }];
114
125
  }
115
126
  function spawnClaudePrint(input) {
127
+ const inputMessage = input.inputBlocks ? claudePrintInputMessage(input.prompt, input.inputBlocks) : undefined;
116
128
  const bin = (0, claude_binary_1.findClaudeBinary)();
117
129
  // No claude on this runtime — fail THIS request cleanly rather than
118
130
  // spawning a bare 'claude' that ENOENTs and crashes the daemon. Both
@@ -138,6 +150,8 @@ function spawnClaudePrint(input) {
138
150
  ];
139
151
  if (partialStream)
140
152
  args.push('--include-partial-messages');
153
+ if (input.inputBlocks)
154
+ args.push('--input-format', 'stream-json');
141
155
  if (input.permissionMode === 'bypassPermissions') {
142
156
  args.push('--dangerously-skip-permissions');
143
157
  }
@@ -183,6 +197,8 @@ function spawnClaudePrint(input) {
183
197
  const isWindows = process.platform === 'win32';
184
198
  const sysPrompt = (0, claude_system_prompt_1.offloadLongSystemPrompt)(bin, args, isWindows, (0, claude_system_prompt_1.claudeSupportsPromptFile)(bin));
185
199
  const transport = (0, prompt_transport_1.offloadLongPrompt)(bin, sysPrompt.args, sysPrompt.args.length - 1, null, isWindows, true);
200
+ if (inputMessage !== undefined)
201
+ transport.stdin = inputMessage;
186
202
  let proc;
187
203
  try {
188
204
  const inv = (0, win_1.resolveCliInvocation)(bin, transport.args);
@@ -0,0 +1,81 @@
1
+ import { type ModelProbeResult } from './model-cache';
2
+ import type { RuntimeEffortLevel } from './types';
3
+ import { type CliInvocation } from './win';
4
+ export interface CodexModelEntry {
5
+ /** What goes to `--model`. */
6
+ id: string;
7
+ /** Always `id` — codex has no aliases. Here so Studio reads both CLIs alike. */
8
+ resolved: string;
9
+ /** Codex's own label, e.g. 'GPT-6-Astra'. */
10
+ name: string;
11
+ alias: false;
12
+ /** Effort levels this model takes, in codex's spelling. */
13
+ efforts?: string[];
14
+ /** What a run with no model set gets. */
15
+ default?: true;
16
+ }
17
+ export interface CodexModelReport {
18
+ /** Plain ids, the default first — the shape `capabilities.codex.models`
19
+ * has always had. */
20
+ models: string[];
21
+ catalog: CodexModelEntry[];
22
+ defaultModel?: string;
23
+ /** 'account': what this node's login can run. 'bundled': what the CLI
24
+ * ships with, read without the login. */
25
+ scope: 'account' | 'bundled';
26
+ }
27
+ export type ProbeResult = ModelProbeResult<CodexModelReport>;
28
+ /**
29
+ * Turn the `data` of a `model/list` answer into a report. Hidden models
30
+ * (codex's internal reviewers and the like) are left out. Returns null when
31
+ * nothing usable is in there.
32
+ */
33
+ export declare function parseModelList(raw: unknown, scope: CodexModelReport['scope']): CodexModelReport | null;
34
+ /** What a picker can offer: every level some listed model takes, in order. */
35
+ export declare function offeredEfforts(report: CodexModelReport): RuntimeEffortLevel[];
36
+ export type ProbeLogin = {
37
+ use: true;
38
+ file: string;
39
+ } | {
40
+ use: false;
41
+ why: string;
42
+ };
43
+ /**
44
+ * May the probe use this login? Only if nothing about it can change while the
45
+ * probe runs: an API key never does, and a ChatGPT login only while its token
46
+ * is fresh. Never throws.
47
+ */
48
+ export declare function loginForProbe(codexHome: string, now?: number): ProbeLogin;
49
+ /**
50
+ * Ask one codex binary for its models. Never throws; resolves within
51
+ * `timeoutMs` however the CLI behaves, and leaves no process behind.
52
+ */
53
+ export declare function probeCodexModels(bin: string, opts?: {
54
+ timeoutMs?: number;
55
+ /** Where the login lives. Runs link it from ~/.codex (writeCodexHome), so
56
+ * the probe does too. */
57
+ codexHome?: string;
58
+ now?: number;
59
+ /** Tests only: run something else in place of the CLI. */
60
+ resolve?: (bin: string, args: string[]) => CliInvocation;
61
+ /** Tests only: where the throwaway CODEX_HOME goes. */
62
+ dir?: string;
63
+ }): Promise<ProbeResult>;
64
+ export interface CodexModelCacheDeps {
65
+ probe: (bin: string) => Promise<ProbeResult>;
66
+ now: () => number;
67
+ log: (line: string) => void;
68
+ }
69
+ /** Refreshed every 6h, retried 30 min after a failure — see model-cache.ts. */
70
+ export declare function createCodexModelCache(deps: CodexModelCacheDeps): {
71
+ get(bin: string, key: string): CodexModelReport | null;
72
+ last(): CodexModelReport | null;
73
+ };
74
+ /** What capabilities.ts reports for codex's models. */
75
+ export declare function codexModelsFor(bin: string, key: string): CodexModelReport | null;
76
+ /**
77
+ * The effort levels a model takes, from the last list codex gave: the
78
+ * default model's when none is named. Null when codex never said, or the
79
+ * model is not in its list.
80
+ */
81
+ export declare function codexModelEfforts(model?: string | null, report?: CodexModelReport | null): string[] | null;