@vascend/talos 0.1.5 → 0.1.6

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 (224) hide show
  1. package/dist/core/agent.d.ts +16 -3
  2. package/dist/core/agent.js +114 -34
  3. package/dist/core/background.d.ts +10 -3
  4. package/dist/core/background.js +55 -9
  5. package/dist/core/bootstrap.js +1 -0
  6. package/dist/core/compaction.js +1 -1
  7. package/dist/core/config-storage.d.ts +5 -0
  8. package/dist/core/config-storage.js +83 -0
  9. package/dist/core/config.d.ts +11 -2
  10. package/dist/core/config.js +24 -8
  11. package/dist/core/context-ledger.js +2 -1
  12. package/dist/core/context-policy.d.ts +1 -1
  13. package/dist/core/context-policy.js +10 -7
  14. package/dist/core/credentials.js +29 -30
  15. package/dist/core/effort.d.ts +1 -1
  16. package/dist/core/effort.js +5 -1
  17. package/dist/core/execution-guidance.d.ts +1 -1
  18. package/dist/core/execution-guidance.js +2 -0
  19. package/dist/core/file-coordination.d.ts +3 -0
  20. package/dist/core/file-coordination.js +28 -0
  21. package/dist/core/file-lease-store.d.ts +3 -0
  22. package/dist/core/file-lease-store.js +28 -12
  23. package/dist/core/fusion.d.ts +24 -0
  24. package/dist/core/fusion.js +80 -0
  25. package/dist/core/model-limits.d.ts +1 -0
  26. package/dist/core/model-limits.js +5 -1
  27. package/dist/core/permission-queue.js +15 -15
  28. package/dist/core/request-plan.js +4 -3
  29. package/dist/core/session-naming.js +4 -0
  30. package/dist/core/session.d.ts +4 -2
  31. package/dist/core/session.js +13 -4
  32. package/dist/core/settings.d.ts +2 -0
  33. package/dist/core/settings.js +5 -16
  34. package/dist/core/step-policy.d.ts +18 -0
  35. package/dist/core/step-policy.js +6 -0
  36. package/dist/core/tool-artifacts.d.ts +2 -0
  37. package/dist/core/tool-artifacts.js +21 -3
  38. package/dist/core/tool-result-index.js +29 -33
  39. package/dist/core/tool-result-store.js +6 -5
  40. package/dist/core/types.d.ts +10 -3
  41. package/dist/core/user-inbox.d.ts +9 -0
  42. package/dist/core/user-inbox.js +22 -3
  43. package/dist/core/user-input.js +1 -1
  44. package/dist/fusion/autopilot-fast.d.ts +59 -0
  45. package/dist/fusion/autopilot-fast.js +213 -0
  46. package/dist/fusion/autopilot.d.ts +75 -0
  47. package/dist/fusion/autopilot.js +308 -0
  48. package/dist/fusion/focus.d.ts +30 -0
  49. package/dist/fusion/focus.js +122 -0
  50. package/dist/fusion/gate.d.ts +30 -0
  51. package/dist/fusion/gate.js +95 -0
  52. package/dist/fusion/jev.d.ts +71 -0
  53. package/dist/fusion/jev.js +164 -0
  54. package/dist/fusion/receipts.d.ts +25 -0
  55. package/dist/fusion/receipts.js +34 -0
  56. package/dist/fusion/route.d.ts +25 -0
  57. package/dist/fusion/route.js +84 -0
  58. package/dist/fusion/run.d.ts +4 -0
  59. package/dist/fusion/run.js +186 -0
  60. package/dist/horizon/engine.d.ts +20 -1
  61. package/dist/horizon/engine.js +57 -0
  62. package/dist/horizon/index.d.ts +2 -2
  63. package/dist/horizon/index.js +1 -1
  64. package/dist/horizon/types.d.ts +29 -0
  65. package/dist/index.js +12 -0
  66. package/dist/providers/anthropic-oauth.d.ts +8 -3
  67. package/dist/providers/anthropic-oauth.js +13 -10
  68. package/dist/providers/anthropic.d.ts +9 -2
  69. package/dist/providers/anthropic.js +64 -21
  70. package/dist/providers/cli-models.d.ts +30 -0
  71. package/dist/providers/cli-models.js +194 -0
  72. package/dist/providers/model-effort.js +1 -1
  73. package/dist/providers/openai-oauth.d.ts +8 -3
  74. package/dist/providers/openai-oauth.js +9 -3
  75. package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
  76. package/dist/providers/openrouter-manual-fallback.js +20 -20
  77. package/dist/providers/registry.js +3 -2
  78. package/dist/providers/retry.d.ts +3 -0
  79. package/dist/providers/retry.js +44 -1
  80. package/dist/remote/cifratura.d.ts +12 -0
  81. package/dist/remote/cifratura.js +66 -0
  82. package/dist/remote/index.d.ts +15 -0
  83. package/dist/remote/index.js +65 -0
  84. package/dist/remote/relay-client.d.ts +58 -0
  85. package/dist/remote/relay-client.js +315 -0
  86. package/dist/remote/sessione-remota.d.ts +60 -0
  87. package/dist/remote/sessione-remota.js +266 -0
  88. package/dist/sdk.d.ts +7 -2
  89. package/dist/sdk.js +3 -1
  90. package/dist/tools/builtin.js +41 -20
  91. package/dist/tools/capabilities.d.ts +3 -1
  92. package/dist/tools/capabilities.js +5 -2
  93. package/dist/tools/computer-use.js +2 -1
  94. package/dist/tools/pagination.js +1 -1
  95. package/dist/tools/program.js +34 -21
  96. package/dist/tools/team-tools.js +11 -6
  97. package/dist/tui/account-entry.js +14 -9
  98. package/dist/tui/agent-label.d.ts +2 -0
  99. package/dist/tui/agent-label.js +5 -0
  100. package/dist/tui/agent-sessions-screen.d.ts +12 -2
  101. package/dist/tui/agent-sessions-screen.js +62 -16
  102. package/dist/tui/agent-sessions.d.ts +14 -0
  103. package/dist/tui/agent-sessions.js +67 -6
  104. package/dist/tui/bottom-input.d.ts +46 -2
  105. package/dist/tui/bottom-input.js +358 -59
  106. package/dist/tui/branding.js +8 -4
  107. package/dist/tui/capability-lists.d.ts +10 -0
  108. package/dist/tui/capability-lists.js +41 -0
  109. package/dist/tui/command-output-registry.js +4 -0
  110. package/dist/tui/command-transcript.d.ts +1 -1
  111. package/dist/tui/command-transcript.js +17 -21
  112. package/dist/tui/commands.js +4 -1
  113. package/dist/tui/compaction-status.d.ts +7 -0
  114. package/dist/tui/compaction-status.js +39 -0
  115. package/dist/tui/config-recovery.d.ts +12 -0
  116. package/dist/tui/config-recovery.js +54 -0
  117. package/dist/tui/context-meter.d.ts +3 -0
  118. package/dist/tui/context-meter.js +35 -8
  119. package/dist/tui/diff.d.ts +9 -0
  120. package/dist/tui/diff.js +43 -19
  121. package/dist/tui/fusion-menu.d.ts +5 -0
  122. package/dist/tui/fusion-menu.js +89 -0
  123. package/dist/tui/i18n-catalog.js +312 -1
  124. package/dist/tui/live-command.d.ts +1 -0
  125. package/dist/tui/live-command.js +9 -0
  126. package/dist/tui/live-output.d.ts +1 -0
  127. package/dist/tui/live-output.js +11 -0
  128. package/dist/tui/model-details.js +13 -12
  129. package/dist/tui/model-picker.js +17 -7
  130. package/dist/tui/model-selection.js +19 -10
  131. package/dist/tui/native-scrollback.js +5 -0
  132. package/dist/tui/prompt-queue.d.ts +3 -0
  133. package/dist/tui/prompt-queue.js +19 -1
  134. package/dist/tui/provider-label.d.ts +2 -0
  135. package/dist/tui/provider-label.js +9 -0
  136. package/dist/tui/provider-menu.js +25 -24
  137. package/dist/tui/queue-screen.js +2 -1
  138. package/dist/tui/remote-command.d.ts +7 -0
  139. package/dist/tui/remote-command.js +79 -0
  140. package/dist/tui/repl.d.ts +8 -2
  141. package/dist/tui/repl.js +355 -311
  142. package/dist/tui/review-store.d.ts +1 -0
  143. package/dist/tui/review-store.js +28 -7
  144. package/dist/tui/review-team.js +4 -3
  145. package/dist/tui/runtime-error.d.ts +2 -0
  146. package/dist/tui/runtime-error.js +26 -0
  147. package/dist/tui/selector-mouse.d.ts +1 -0
  148. package/dist/tui/selector-mouse.js +1 -1
  149. package/dist/tui/selector.d.ts +7 -0
  150. package/dist/tui/selector.js +19 -2
  151. package/dist/tui/session-controls.d.ts +26 -0
  152. package/dist/tui/session-controls.js +65 -0
  153. package/dist/tui/session-naming-notice.js +5 -3
  154. package/dist/tui/session-replay.d.ts +1 -0
  155. package/dist/tui/session-replay.js +101 -32
  156. package/dist/tui/session-transcript-layout.d.ts +9 -0
  157. package/dist/tui/session-transcript-layout.js +41 -0
  158. package/dist/tui/startup.js +63 -33
  159. package/dist/tui/status-line.d.ts +1 -1
  160. package/dist/tui/status-line.js +20 -12
  161. package/dist/tui/talos-art-data.d.ts +1 -0
  162. package/dist/tui/talos-art-data.js +2 -0
  163. package/dist/tui/talos-art.d.ts +1 -0
  164. package/dist/tui/talos-art.js +9 -0
  165. package/dist/tui/task-labels.d.ts +1 -0
  166. package/dist/tui/task-labels.js +8 -0
  167. package/dist/tui/terminal-text.d.ts +2 -1
  168. package/dist/tui/terminal-text.js +28 -2
  169. package/dist/tui/theme.js +2 -2
  170. package/dist/tui/tool-presentation.d.ts +3 -0
  171. package/dist/tui/tool-presentation.js +98 -0
  172. package/dist/tui/tool-style.js +4 -0
  173. package/dist/tui/transcript-store.d.ts +25 -0
  174. package/dist/tui/transcript-store.js +151 -0
  175. package/dist/tui/usage-footer.d.ts +1 -1
  176. package/dist/tui/usage-footer.js +3 -1
  177. package/dist/tui/vascend-id.d.ts +49 -0
  178. package/dist/tui/vascend-id.js +321 -0
  179. package/dist/tui/web-login-screen.js +4 -3
  180. package/dist/tui/web-login.d.ts +7 -15
  181. package/dist/tui/web-login.js +38 -78
  182. package/dist/tui/worker-transcripts.d.ts +31 -0
  183. package/dist/tui/worker-transcripts.js +91 -0
  184. package/dist/vascend/candidate-collector.d.ts +6 -5
  185. package/dist/vascend/candidate-collector.js +44 -43
  186. package/dist/vascend/context-lint.js +32 -2
  187. package/dist/vascend/context-market-selection.js +12 -5
  188. package/dist/vascend/context-pack-render.js +10 -4
  189. package/dist/vascend/context-packet-mirrors.d.ts +8 -0
  190. package/dist/vascend/context-packet-mirrors.js +24 -0
  191. package/dist/vascend/context-plan-events.d.ts +4 -0
  192. package/dist/vascend/context-plan-events.js +44 -0
  193. package/dist/vascend/context-snapshot.js +4 -2
  194. package/dist/vascend/context-store-reader.d.ts +20 -0
  195. package/dist/vascend/context-store-reader.js +46 -0
  196. package/dist/vascend/file-lock.d.ts +1 -0
  197. package/dist/vascend/file-lock.js +26 -0
  198. package/dist/vascend/granular-kingdom.d.ts +1 -0
  199. package/dist/vascend/granular-kingdom.js +22 -15
  200. package/dist/vascend/kingdom-bus.d.ts +1 -0
  201. package/dist/vascend/kingdom-bus.js +3 -0
  202. package/dist/vascend/kingdom-channel.d.ts +2 -0
  203. package/dist/vascend/kingdom-channel.js +3 -0
  204. package/dist/vascend/kingdom-live.d.ts +32 -4
  205. package/dist/vascend/kingdom-live.js +149 -13
  206. package/dist/vascend/kingdom-run.d.ts +5 -1
  207. package/dist/vascend/kingdom-run.js +6 -2
  208. package/dist/vascend/kingdom.d.ts +3 -0
  209. package/dist/vascend/kingdom.js +20 -13
  210. package/dist/vascend/memory-packet.d.ts +2 -1
  211. package/dist/vascend/memory-packet.js +31 -44
  212. package/dist/vascend/plan-store.d.ts +3 -1
  213. package/dist/vascend/plan-store.js +6 -4
  214. package/dist/vascend/room-memory.d.ts +10 -1
  215. package/dist/vascend/room-memory.js +42 -2
  216. package/dist/vascend/runtime.d.ts +3 -11
  217. package/dist/vascend/runtime.js +11 -123
  218. package/dist/vascend/session-search.js +2 -0
  219. package/dist/vascend/team-session.js +3 -2
  220. package/dist/vascend/vision.d.ts +3 -1
  221. package/dist/vascend/vision.js +6 -4
  222. package/dist/vascend/worker-session.d.ts +20 -0
  223. package/dist/vascend/worker-session.js +83 -0
  224. package/package.json +1 -1
@@ -11,8 +11,15 @@ interface ApiMessage {
11
11
  }
12
12
  export declare function toApiMessages(req: CompletionRequest): unknown[];
13
13
  export declare function normalizeToolPairing(messages: ApiMessage[]): ApiMessage[];
14
- export declare function withSystemCache(system: string, turnContext?: string): unknown[];
15
- export declare function applyMessageCache(messages: unknown[]): unknown[];
14
+ export type CacheTtl = "5m" | "1h";
15
+ export declare function claudeCacheTtl(env: NodeJS.ProcessEnv): CacheTtl;
16
+ export declare function supportsConversationSystem(model: string): boolean;
17
+ export declare function withSystemCache(system: string, turnContext?: string, ttl?: CacheTtl): unknown[];
18
+ export declare function applyMessageCache(messages: unknown[], ttl?: CacheTtl): unknown[];
19
+ export declare function claudePrompt(req: CompletionRequest, ttl?: CacheTtl): {
20
+ system: unknown[];
21
+ messages: unknown[];
22
+ };
16
23
  export declare const CONTEXT_1M_BETA = "context-1m-2025-08-07";
17
24
  export declare function wants1m(model: string, env: NodeJS.ProcessEnv): boolean;
18
25
  export declare class AnthropicProvider implements Provider {
@@ -163,12 +163,34 @@ export function normalizeToolPairing(messages) {
163
163
  }
164
164
  return out;
165
165
  }
166
- // Applica i breakpoint di cache (prompt caching Anthropic): marca l'ultimo
167
- // blocco di system e l'ultimo blocco dell'ultimo messaggio con cache_control
168
- // ephemeral. Riduce i token fatturati sui turni successivi.
169
- export function withSystemCache(system, turnContext = "") {
166
+ // Un'ora come Claude Code, per abbonamento e API key: ogni lettura rinnova la
167
+ // scadenza, quindi le sessioni con pause oltre i 5 minuti non riscrivono tutto
168
+ // il prefisso. TALOS_CLAUDE_CACHE_TTL=5m torna alla durata breve.
169
+ export function claudeCacheTtl(env) {
170
+ return env["TALOS_CLAUDE_CACHE_TTL"] === "5m" ? "5m" : "1h";
171
+ }
172
+ const CACHE_TTL_MS = { "5m": 300_000, "1h": 3_600_000 };
173
+ // Durata applicata dal provider alla cache di questa risposta: la dice la
174
+ // suddivisione di cache_creation per TTL. Una lettura rinnova la durata gia'
175
+ // scritta, che resta quella dell'ultima scrittura nota.
176
+ function reportedCacheTtlMs(usage) {
177
+ const creation = usage["cache_creation"];
178
+ if (Number(creation?.["ephemeral_1h_input_tokens"] ?? 0) > 0)
179
+ return CACHE_TTL_MS["1h"];
180
+ if (Number(creation?.["ephemeral_5m_input_tokens"] ?? 0) > 0)
181
+ return CACHE_TTL_MS["5m"];
182
+ return undefined;
183
+ }
184
+ function cacheControl(ttl) {
185
+ return { type: "ephemeral", ...(ttl === "1h" ? { ttl } : {}) };
186
+ }
187
+ // Only documented models accept authoritative system messages in the history.
188
+ export function supportsConversationSystem(model) {
189
+ return /^claude-(?:opus-(?:5(?:-\d)?|4-8)|(?:fable|mythos)-5(?:-\d)?)(?:-\d{8})?$/.test(model);
190
+ }
191
+ export function withSystemCache(system, turnContext = "", ttl = "5m") {
170
192
  return [
171
- { type: "text", text: system, cache_control: { type: "ephemeral" } },
193
+ { type: "text", text: system, cache_control: cacheControl(ttl) },
172
194
  ...(turnContext ? [{
173
195
  type: "text",
174
196
  text: `[HORIZON_TURN_CONTEXT]\n${turnContext}\n[/HORIZON_TURN_CONTEXT]`,
@@ -181,20 +203,39 @@ export function withSystemCache(system, turnContext = "") {
181
203
  // finisce col turno precedente resta cacheato e viene riconosciuto al turno
182
204
  // dopo. Insieme al breakpoint su system restano 3 marcatori, sotto il tetto di
183
205
  // 4 dell'API.
184
- export function applyMessageCache(messages) {
185
- markCacheBreakpoint(messages[messages.length - 1]);
186
- if (messages.length > 1)
187
- markCacheBreakpoint(messages[messages.length - 2]);
188
- return messages;
206
+ export function applyMessageCache(messages, ttl = "5m") {
207
+ // Provider projections must not mutate a caller's history.
208
+ const copy = messages.map((message) => {
209
+ const m = message;
210
+ return { ...m, ...(Array.isArray(m.content) ? { content: m.content.map(b => ({ ...b })) } : {}) };
211
+ });
212
+ markCacheBreakpoint(copy[copy.length - 1], ttl);
213
+ if (copy.length > 1)
214
+ markCacheBreakpoint(copy[copy.length - 2], ttl);
215
+ return copy;
189
216
  }
190
- function markCacheBreakpoint(message) {
217
+ function markCacheBreakpoint(message, ttl) {
191
218
  const target = message;
192
219
  if (!target || !Array.isArray(target.content) || !target.content.length)
193
220
  return;
194
221
  const blocks = target.content;
195
222
  const lastBlock = blocks[blocks.length - 1];
196
223
  if (lastBlock)
197
- lastBlock["cache_control"] = { type: "ephemeral" };
224
+ lastBlock["cache_control"] = cacheControl(ttl);
225
+ }
226
+ export function claudePrompt(req, ttl = "5m") {
227
+ const inline = supportsConversationSystem(req.model);
228
+ const messages = toApiMessages(req);
229
+ if (inline && req.turnContext)
230
+ messages.push({ role: "system", content: [{
231
+ type: "text", text: `[HORIZON_TURN_CONTEXT]\n${req.turnContext}\n[/HORIZON_TURN_CONTEXT]`,
232
+ }] });
233
+ // Cache the history immediately BEFORE the volatile tail as well as the tail.
234
+ // On the next request only the tail is replaced; all prior history still matches.
235
+ return {
236
+ system: withSystemCache(req.system, inline ? "" : req.turnContext, ttl),
237
+ messages: applyMessageCache(messages, ttl),
238
+ };
198
239
  }
199
240
  import { anthropicEffort } from "./effort.js";
200
241
  import { resolveMaxTokens } from "../core/model-limits.js";
@@ -233,8 +274,7 @@ export class AnthropicProvider {
233
274
  const body = {
234
275
  model: req.model,
235
276
  max_tokens: resolveMaxTokens(req.maxTokens, `anthropic/${req.model}`),
236
- system: withSystemCache(req.system, req.turnContext),
237
- messages: applyMessageCache(toApiMessages(req)),
277
+ ...claudePrompt(req, claudeCacheTtl(process.env)),
238
278
  stream: true,
239
279
  tools: req.tools.map((t) => ({
240
280
  name: t.name,
@@ -331,7 +371,7 @@ export async function parseStream(body, onDelta, onActivity, onReasoning, onTool
331
371
  stopReason ??= "end";
332
372
  sawTerminal = true;
333
373
  }
334
- const u = readUsage(evt);
374
+ const u = readUsage(evt, usage);
335
375
  if (u)
336
376
  usage = u;
337
377
  };
@@ -511,16 +551,19 @@ function readProviderError(evt) {
511
551
  const message = String(raw?.["message"] ?? evt["message"] ?? "errore streaming senza dettagli");
512
552
  return `Errore provider Anthropic (${kind}): ${message}${errorTimingDetail(raw)}${errorTimingDetail(evt)}`;
513
553
  }
514
- function readUsage(evt) {
515
- const usage = evt["usage"];
554
+ function readUsage(evt, previous) {
555
+ const message = evt["message"];
556
+ const usage = (evt["usage"] ?? message?.["usage"]);
516
557
  if (!usage)
517
558
  return undefined;
518
559
  const cacheRead = usage["cache_read_input_tokens"];
519
560
  const cacheWrite = usage["cache_creation_input_tokens"];
561
+ const cacheTtlMs = reportedCacheTtlMs(usage) ?? previous?.cacheTtlMs;
520
562
  return {
521
- inputTokens: Number(usage["input_tokens"] ?? 0),
522
- outputTokens: Number(usage["output_tokens"] ?? 0),
523
- ...(cacheRead !== undefined ? { cacheReadTokens: Number(cacheRead) } : {}),
524
- ...(cacheWrite !== undefined ? { cacheWriteTokens: Number(cacheWrite) } : {}),
563
+ inputTokens: Number(usage["input_tokens"] ?? previous?.inputTokens ?? 0),
564
+ outputTokens: Number(usage["output_tokens"] ?? previous?.outputTokens ?? 0),
565
+ ...((cacheRead ?? previous?.cacheReadTokens) !== undefined ? { cacheReadTokens: Number(cacheRead ?? previous?.cacheReadTokens) } : {}),
566
+ ...((cacheWrite ?? previous?.cacheWriteTokens) !== undefined ? { cacheWriteTokens: Number(cacheWrite ?? previous?.cacheWriteTokens) } : {}),
567
+ ...(cacheTtlMs !== undefined ? { cacheTtlMs } : {}),
525
568
  };
526
569
  }
@@ -0,0 +1,30 @@
1
+ export type CliName = "claude" | "codex";
2
+ export interface CliModel {
3
+ readonly id: string;
4
+ readonly label: string;
5
+ readonly description?: string;
6
+ }
7
+ /** Primo CLI nel PATH con l'impronta del binario reale (symlink risolti), o null se non installato. */
8
+ export declare function findCli(name: CliName, env: NodeJS.ProcessEnv): {
9
+ readonly executable: string;
10
+ readonly stamp: string;
11
+ } | null;
12
+ /** Modelli in cache per il CLI installato ora, o undefined se assenti o superati da un aggiornamento. */
13
+ export declare function cachedCliModels(name: CliName, env: NodeJS.ProcessEnv): readonly CliModel[] | undefined;
14
+ /**
15
+ * Righe di `initialize` del CLI Claude Code. Gli alias (default, opus, sonnet)
16
+ * diventano il modello versionato che eseguono: una voce per modello reale,
17
+ * nell'ordine del CLI.
18
+ */
19
+ export declare function claudeModelsFromInitialize(models: unknown): CliModel[];
20
+ /** Righe di `model/list` dell'app-server Codex, senza i modelli nascosti. */
21
+ export declare function codexModelsFromList(data: unknown): CliModel[];
22
+ /**
23
+ * Rilegge i modelli dai CLI installati la cui cache manca o e' superata, e la
24
+ * salva. Un CLI assente, lento o in errore lascia la cache com'e': il catalogo
25
+ * ripiega su quello statico.
26
+ */
27
+ export declare function refreshCliModels(env: NodeJS.ProcessEnv, options?: {
28
+ readonly timeoutMs?: number;
29
+ readonly force?: boolean;
30
+ }): Promise<void>;
@@ -0,0 +1,194 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { delimiter, dirname, isAbsolute, join } from "node:path";
5
+ import { talosConfigDir } from "../core/env-paths.js";
6
+ const QUERY_TIMEOUT_MS = 20_000;
7
+ function cachePath(env) {
8
+ if (env["TALOS_NO_GLOBAL_CONFIG"] === "1" || env["TALOS_DISABLE_CLI_MODELS"] === "1")
9
+ return null;
10
+ return join(talosConfigDir(env), "cli-models.json");
11
+ }
12
+ /** Primo CLI nel PATH con l'impronta del binario reale (symlink risolti), o null se non installato. */
13
+ export function findCli(name, env) {
14
+ const names = process.platform === "win32" ? [`${name}.exe`, `${name}.cmd`] : [name];
15
+ for (const directory of String(env["PATH"] ?? env["Path"] ?? "").split(delimiter)) {
16
+ if (!directory || !isAbsolute(directory))
17
+ continue;
18
+ for (const file of names) {
19
+ const candidate = join(directory, file);
20
+ if (!existsSync(candidate))
21
+ continue;
22
+ try {
23
+ const real = realpathSync(candidate);
24
+ return { executable: candidate, stamp: `${real}@${statSync(real).mtimeMs}` };
25
+ }
26
+ catch {
27
+ // Voce del PATH non leggibile: si prova la successiva.
28
+ }
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ function readCache(env) {
34
+ const path = cachePath(env);
35
+ if (!path || !existsSync(path))
36
+ return {};
37
+ try {
38
+ const raw = JSON.parse(readFileSync(path, "utf8"));
39
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
40
+ }
41
+ catch {
42
+ return {};
43
+ }
44
+ }
45
+ /** Modelli in cache per il CLI installato ora, o undefined se assenti o superati da un aggiornamento. */
46
+ export function cachedCliModels(name, env) {
47
+ if (!cachePath(env))
48
+ return undefined;
49
+ const cli = findCli(name, env);
50
+ const entry = readCache(env)[name];
51
+ if (!cli || !entry || entry.stamp !== cli.stamp)
52
+ return undefined;
53
+ const models = Array.isArray(entry.models) ? entry.models.filter((model) => typeof model?.id === "string" && model.id && typeof model.label === "string") : [];
54
+ return models.length ? models : undefined;
55
+ }
56
+ /**
57
+ * Righe di `initialize` del CLI Claude Code. Gli alias (default, opus, sonnet)
58
+ * diventano il modello versionato che eseguono: una voce per modello reale,
59
+ * nell'ordine del CLI.
60
+ */
61
+ export function claudeModelsFromInitialize(models) {
62
+ const out = new Map();
63
+ for (const raw of Array.isArray(models) ? models : []) {
64
+ const row = raw;
65
+ if (row["value"] === "default")
66
+ continue;
67
+ const id = String(row["resolvedModel"] || row["value"] || "").trim();
68
+ if (!id || out.has(id))
69
+ continue;
70
+ const label = String(row["displayName"] || id).trim();
71
+ const description = String(row["description"] ?? "").trim();
72
+ out.set(id, { id, label, ...(description ? { description } : {}) });
73
+ }
74
+ return [...out.values()];
75
+ }
76
+ /** Righe di `model/list` dell'app-server Codex, senza i modelli nascosti. */
77
+ export function codexModelsFromList(data) {
78
+ const out = new Map();
79
+ for (const raw of Array.isArray(data) ? data : []) {
80
+ const row = raw;
81
+ if (row["hidden"] === true)
82
+ continue;
83
+ const id = String(row["model"] || row["id"] || "").trim();
84
+ if (!id || out.has(id))
85
+ continue;
86
+ const label = String(row["displayName"] || id).trim();
87
+ const description = String(row["description"] ?? "").trim();
88
+ out.set(id, { id, label, ...(description ? { description } : {}) });
89
+ }
90
+ return [...out.values()];
91
+ }
92
+ // Avvia il CLI, scrive le richieste JSONL e attende la prima riga che `pick`
93
+ // riconosce. Il processo viene sempre terminato, anche su timeout o errore.
94
+ function queryCli(executable, args, requests, pick, env, timeoutMs) {
95
+ return new Promise((resolve, reject) => {
96
+ const child = spawn(executable, [...args], {
97
+ env, cwd: tmpdir(), stdio: ["pipe", "pipe", "ignore"], windowsHide: true,
98
+ shell: process.platform === "win32" && executable.toLowerCase().endsWith(".cmd"),
99
+ });
100
+ let buffer = "";
101
+ let settled = false;
102
+ const finish = (error, value) => {
103
+ if (settled)
104
+ return;
105
+ settled = true;
106
+ clearTimeout(timer);
107
+ child.kill();
108
+ if (error)
109
+ reject(error);
110
+ else
111
+ resolve(value);
112
+ };
113
+ const timer = setTimeout(() => finish(new Error(`${executable} did not answer in time`)), timeoutMs);
114
+ child.on("error", (error) => finish(error));
115
+ child.on("close", () => finish(new Error(`${executable} exited before answering`)));
116
+ child.stdout.setEncoding("utf8");
117
+ child.stdout.on("data", (chunk) => {
118
+ buffer += chunk;
119
+ let newline;
120
+ while ((newline = buffer.indexOf("\n")) >= 0) {
121
+ const line = buffer.slice(0, newline).trim();
122
+ buffer = buffer.slice(newline + 1);
123
+ if (!line.startsWith("{"))
124
+ continue;
125
+ try {
126
+ const value = pick(JSON.parse(line));
127
+ if (value !== undefined)
128
+ finish(null, value);
129
+ }
130
+ catch {
131
+ // Righe non JSON (log) ignorate.
132
+ }
133
+ }
134
+ });
135
+ child.stdin.on("error", () => { });
136
+ child.stdin.write(requests.map((request) => JSON.stringify(request)).join("\n") + "\n");
137
+ });
138
+ }
139
+ async function queryClaudeModels(executable, env, timeoutMs) {
140
+ const response = await queryCli(executable, ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"], [{ type: "control_request", request_id: "talos-models", request: { subtype: "initialize" } }], (message) => {
141
+ const body = message["response"];
142
+ return message["type"] === "control_response" && body?.["request_id"] === "talos-models" ? body : undefined;
143
+ }, env, timeoutMs);
144
+ if (response["subtype"] !== "success")
145
+ throw new Error(String(response["error"] ?? "Claude Code initialize failed"));
146
+ return claudeModelsFromInitialize(response["response"]?.["models"]);
147
+ }
148
+ async function queryCodexModels(executable, env, timeoutMs) {
149
+ const response = await queryCli(executable, ["app-server"], [
150
+ { id: 1, method: "initialize", params: { clientInfo: { name: "talos", version: "1" } } },
151
+ { method: "initialized" },
152
+ { id: 2, method: "model/list", params: {} },
153
+ ], (message) => message["id"] === 2 ? message : undefined, env, timeoutMs);
154
+ if (response["error"])
155
+ throw new Error(JSON.stringify(response["error"]));
156
+ return codexModelsFromList(response["result"]?.["data"]);
157
+ }
158
+ /**
159
+ * Rilegge i modelli dai CLI installati la cui cache manca o e' superata, e la
160
+ * salva. Un CLI assente, lento o in errore lascia la cache com'e': il catalogo
161
+ * ripiega su quello statico.
162
+ */
163
+ export async function refreshCliModels(env, options = {}) {
164
+ const path = cachePath(env);
165
+ if (!path)
166
+ return;
167
+ const timeoutMs = options.timeoutMs ?? QUERY_TIMEOUT_MS;
168
+ const queries = { claude: queryClaudeModels, codex: queryCodexModels };
169
+ const updates = await Promise.all(Object.keys(queries).map(async (name) => {
170
+ if (!options.force && cachedCliModels(name, env))
171
+ return null;
172
+ const cli = findCli(name, env);
173
+ if (!cli)
174
+ return null;
175
+ try {
176
+ const models = await queries[name](cli.executable, env, timeoutMs);
177
+ return models.length ? [name, { stamp: cli.stamp, models }] : null;
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ }));
183
+ const fresh = updates.filter((update) => update !== null);
184
+ if (!fresh.length)
185
+ return;
186
+ const cache = { ...readCache(env), ...Object.fromEntries(fresh) };
187
+ try {
188
+ mkdirSync(dirname(path), { recursive: true });
189
+ writeFileSync(path, JSON.stringify(cache, null, 2) + "\n", { mode: 0o600 });
190
+ }
191
+ catch {
192
+ // Cache non scrivibile: il catalogo resta quello statico.
193
+ }
194
+ }
@@ -26,7 +26,7 @@ export function modelEfforts(fullModel, metadata = modelCapabilities(fullModel))
26
26
  return ["", "low", "medium", "high"];
27
27
  }
28
28
  if (provider === "openai") {
29
- if (/gpt-6-astra/.test(id))
29
+ if (/gpt-6/.test(id))
30
30
  return ["", "low", "medium", "high", "xhigh", "max"];
31
31
  if (/gpt-5\.[456]/.test(id))
32
32
  return ["", "low", "medium", "high", "xhigh"];
@@ -1,3 +1,4 @@
1
+ import type { CliModel } from "./cli-models.js";
1
2
  import type { CompletionRequest, ModelTurn, Provider } from "../core/types.js";
2
3
  import { type OAuthCreds } from "./oauth-store.js";
3
4
  export declare class OpenAIOAuthProvider implements Provider {
@@ -5,10 +6,14 @@ export declare class OpenAIOAuthProvider implements Provider {
5
6
  private readonly env;
6
7
  private readonly now;
7
8
  readonly id = "chatgpt";
8
- readonly label = "ChatGPT (abbonamento)";
9
- readonly models: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-5-codex", "gpt-5", "gpt-5-mini", "o4-mini"];
9
+ readonly label = "ChatGPT";
10
+ readonly models: readonly string[];
11
+ readonly modelLabels?: ReadonlyMap<string, {
12
+ readonly label: string;
13
+ readonly description?: string;
14
+ }>;
10
15
  readonly supportsFastMode = true;
11
- constructor(creds: OAuthCreds, env: NodeJS.ProcessEnv, now?: () => number);
16
+ constructor(creds: OAuthCreds, env: NodeJS.ProcessEnv, now?: () => number, cliModels?: readonly CliModel[]);
12
17
  private ensureToken;
13
18
  complete(req: CompletionRequest): Promise<ModelTurn>;
14
19
  }
@@ -105,13 +105,19 @@ export class OpenAIOAuthProvider {
105
105
  env;
106
106
  now;
107
107
  id = "chatgpt";
108
- label = "ChatGPT (abbonamento)";
109
- models = CHATGPT_MODELS;
108
+ label = "ChatGPT";
109
+ models;
110
+ modelLabels;
110
111
  supportsFastMode = true;
111
- constructor(creds, env, now = () => Date.now()) {
112
+ constructor(creds, env, now = () => Date.now(),
113
+ // Modelli del CLI ufficiale installato; senza, il catalogo statico.
114
+ cliModels) {
112
115
  this.creds = creds;
113
116
  this.env = env;
114
117
  this.now = now;
118
+ this.models = cliModels?.length ? cliModels.map((model) => model.id) : CHATGPT_MODELS;
119
+ if (cliModels?.length)
120
+ this.modelLabels = new Map(cliModels.map((model) => [model.id, model]));
115
121
  }
116
122
  async ensureToken(signal) {
117
123
  if (!isExpired(this.creds, this.now()) || !this.creds.refreshToken) {
@@ -1,16 +1,18 @@
1
1
  import type { OAuthFlow } from "./openrouter-auth.js";
2
+ export type ManualTranslator = (italian: string, english: string, values?: Readonly<Record<string, string | number>>) => string;
2
3
  export declare function normalizeAuthCode(raw: string): string;
3
4
  export declare function isLikelyAuthCode(value: string): boolean;
4
5
  export interface ManualInstructions {
5
6
  readonly lines: readonly string[];
6
7
  readonly authUrl: string;
7
8
  }
8
- export declare function buildManualInstructions(authUrl: string, reason?: string): ManualInstructions;
9
+ export declare function buildManualInstructions(authUrl: string, reason?: string, tr?: ManualTranslator): ManualInstructions;
9
10
  export interface ManualFallbackIo {
10
11
  print(line: string): void;
11
12
  prompt(label: string): Promise<string | null>;
12
13
  }
13
14
  export interface ManualFallbackOptions {
15
+ readonly translate?: ManualTranslator;
14
16
  readonly reason?: string;
15
17
  readonly maxAttempts?: number;
16
18
  readonly signal?: AbortSignal;
@@ -13,6 +13,7 @@
13
13
  // (niente accesso diretto a stdin/stdout/console), e lo scambio del code arriva
14
14
  // dall'OAuthFlow.complete gia' fornito da openrouter-auth.ts. Nessun side effect
15
15
  // nascosto, niente dipendenze a runtime.
16
+ const defaultTranslation = (italian, _english, values) => italian.replace(/\{(\w+)\}/g, (match, key) => String(values?.[key] ?? match));
16
17
  // ---------------------------------------------------------------------------
17
18
  // Validazione / normalizzazione del code incollato
18
19
  // ---------------------------------------------------------------------------
@@ -91,26 +92,24 @@ export function isLikelyAuthCode(value) {
91
92
  // Costruisce le istruzioni per il percorso manuale. `reason` (opzionale) spiega
92
93
  // perche' siamo qui (es. "porta 8976 occupata", "nessun browser disponibile"):
93
94
  // se assente usiamo un messaggio neutro.
94
- export function buildManualInstructions(authUrl, reason) {
95
+ export function buildManualInstructions(authUrl, reason, tr = defaultTranslation) {
95
96
  const why = (reason ?? "").trim();
96
97
  const lines = [];
97
- lines.push("Login OpenRouter — modalita' manuale");
98
+ lines.push(tr("Login OpenRouter — modalita' manuale", "OpenRouter sign-in — manual mode"));
98
99
  if (why) {
99
- lines.push(`(callback automatico non disponibile: ${why})`);
100
+ lines.push(tr("(callback automatico non disponibile: {reason})", "(automatic callback unavailable: {reason})", { reason: why }));
100
101
  }
101
102
  else {
102
- lines.push("(callback automatico non disponibile: completiamo a mano)");
103
+ lines.push(tr("(callback automatico non disponibile: completiamo a mano)", "(automatic callback unavailable: complete sign-in manually)"));
103
104
  }
104
105
  lines.push("");
105
- lines.push("1. Apri questo link nel browser (copialo se non e' cliccabile):");
106
+ lines.push(tr("1. Apri questo link nel browser (copialo se non e' cliccabile):", "1. Open this link in the browser (copy it if it is not clickable):"));
106
107
  lines.push(` ${authUrl}`);
107
108
  lines.push("");
108
- lines.push("2. Autorizza l'accesso su OpenRouter.");
109
- lines.push("3. Verrai rediretto a un indirizzo localhost che potrebbe non");
110
- lines.push(" aprirsi (\"impossibile raggiungere il sito\"): e' normale.");
111
- lines.push("4. Copia il valore del parametro `code` dalla barra degli indirizzi");
112
- lines.push(" (oppure incolla l'intero URL: estraggo io il code).");
113
- lines.push("5. Incollalo qui sotto e premi Invio.");
109
+ lines.push(tr("2. Autorizza l'accesso su OpenRouter.", "2. Authorize access on OpenRouter."));
110
+ lines.push(tr("3. Verrai rediretto a un indirizzo localhost che potrebbe non aprirsi: e' normale.", "3. You will be redirected to a localhost address that may not open: this is normal."));
111
+ lines.push(tr("4. Copia il parametro `code` dalla barra degli indirizzi (oppure incolla l'intero URL).", "4. Copy the `code` parameter from the address bar (or paste the entire URL)."));
112
+ lines.push(tr("5. Incollalo qui sotto e premi Invio.", "5. Paste it below and press Enter."));
114
113
  return { lines, authUrl };
115
114
  }
116
115
  // Esegue il percorso di fallback manuale: mostra link+istruzioni, chiede il
@@ -118,15 +117,16 @@ export function buildManualInstructions(authUrl, reason) {
118
117
  // input invalido o scambio fallito, fino a maxAttempts o annullamento.
119
118
  export async function runManualCodeFallback(io, flow, opts = {}) {
120
119
  const maxAttempts = Math.max(1, Math.floor(opts.maxAttempts ?? 3));
121
- const instructions = buildManualInstructions(flow.authUrl, opts.reason);
120
+ const tr = opts.translate ?? defaultTranslation;
121
+ const instructions = buildManualInstructions(flow.authUrl, opts.reason, tr);
122
122
  for (const line of instructions.lines)
123
123
  io.print(line);
124
124
  let attempts = 0;
125
125
  let lastError = "";
126
126
  while (attempts < maxAttempts) {
127
127
  const remaining = maxAttempts - attempts;
128
- const hint = attempts === 0 ? "" : ` (tentativi rimasti: ${remaining})`;
129
- const raw = await io.prompt(`Incolla il code${hint}`);
128
+ const hint = attempts === 0 ? "" : tr(" (tentativi rimasti: {count})", " (attempts remaining: {count})", { count: remaining });
129
+ const raw = await io.prompt(tr("Incolla il code{hint}", "Paste the code{hint}", { hint }));
130
130
  // Annullamento esplicito (Esc/EOF): usciamo subito senza consumare logica.
131
131
  if (raw === null) {
132
132
  return { ok: false, error: "cancelled", attempts };
@@ -134,9 +134,9 @@ export async function runManualCodeFallback(io, flow, opts = {}) {
134
134
  attempts += 1;
135
135
  const code = normalizeAuthCode(raw);
136
136
  if (!isLikelyAuthCode(code)) {
137
- lastError = "code non riconosciuto";
137
+ lastError = tr("code non riconosciuto", "unrecognized code");
138
138
  if (attempts < maxAttempts) {
139
- io.print("Input non valido: incolla il code (o l'intero URL del callback). Riprova.");
139
+ io.print(tr("Input non valido: incolla il code (o l'intero URL del callback). Riprova.", "Invalid input: paste the code (or the entire callback URL). Try again."));
140
140
  }
141
141
  continue;
142
142
  }
@@ -148,14 +148,14 @@ export async function runManualCodeFallback(io, flow, opts = {}) {
148
148
  result = { ok: false, error: err instanceof Error ? err.message : String(err) };
149
149
  }
150
150
  if (result.ok && result.apiKey) {
151
- io.print("Autenticazione completata: API key ottenuta e salvata.");
151
+ io.print(tr("Autenticazione completata: API key ottenuta e salvata.", "Authentication complete: API key obtained and saved."));
152
152
  return { ok: true, apiKey: result.apiKey, attempts };
153
153
  }
154
- lastError = result.error ?? "scambio fallito";
154
+ lastError = result.error ?? tr("scambio fallito", "exchange failed");
155
155
  if (attempts < maxAttempts) {
156
- io.print(`Scambio fallito: ${lastError}. Verifica di aver copiato il code giusto e riprova.`);
156
+ io.print(tr("Scambio fallito: {error}. Verifica di aver copiato il code giusto e riprova.", "Exchange failed: {error}. Check that you copied the correct code and try again.", { error: lastError }));
157
157
  }
158
158
  }
159
- io.print("Tentativi esauriti: login manuale non riuscito.");
159
+ io.print(tr("Tentativi esauriti: login manuale non riuscito.", "No attempts left: manual sign-in failed."));
160
160
  return { ok: false, error: lastError || "exhausted", attempts };
161
161
  }
@@ -3,6 +3,7 @@ import { AnthropicOAuthProvider } from "./anthropic-oauth.js";
3
3
  import { OpenAIOAuthProvider } from "./openai-oauth.js";
4
4
  import { OpenAICompatProvider } from "./openai-compat.js";
5
5
  import { readClaudeCreds, readCodexCreds } from "./oauth-store.js";
6
+ import { cachedCliModels } from "./cli-models.js";
6
7
  import { readOpenRouterCreds } from "./openrouter-auth.js";
7
8
  import { PROVIDER_CATALOG } from "./catalog.js";
8
9
  import { retryConfigFromEnv, withRetry } from "./retry.js";
@@ -202,11 +203,11 @@ export function defaultRegistry(env, opts = {}) {
202
203
  if (opts.detectOAuth !== false) {
203
204
  const claude = readClaudeCreds(env);
204
205
  if (claude)
205
- add(new AnthropicOAuthProvider(claude, env));
206
+ add(new AnthropicOAuthProvider(claude, env, undefined, cachedCliModels("claude", env)));
206
207
  if (env["TALOS_DISABLE_CHATGPT_OAUTH"] !== "1") {
207
208
  const codex = readCodexCreds(env);
208
209
  if (codex)
209
- add(new OpenAIOAuthProvider(codex, env));
210
+ add(new OpenAIOAuthProvider(codex, env, undefined, cachedCliModels("codex", env)));
210
211
  }
211
212
  }
212
213
  return reg;
@@ -1,5 +1,6 @@
1
1
  import type { CompletionRequest, ModelTurn, PlanLimits, Provider } from "../core/types.js";
2
2
  export interface RetryConfig {
3
+ readonly autoResumeSession?: boolean;
3
4
  readonly maxRetries: number;
4
5
  readonly baseDelayMs: number;
5
6
  readonly timeoutMs: number;
@@ -25,12 +26,14 @@ export declare class RetryProvider implements Provider {
25
26
  get id(): string;
26
27
  get label(): string | undefined;
27
28
  get models(): readonly string[];
29
+ get modelLabels(): Provider["modelLabels"];
28
30
  get supportsFastMode(): boolean;
29
31
  private get netMax();
30
32
  private get netDelay();
31
33
  private get overloadMaxAttempts();
32
34
  private overloadDelay;
33
35
  private waitForOverload;
36
+ private waitForSessionReset;
34
37
  complete(req: CompletionRequest): Promise<ModelTurn>;
35
38
  private backoff;
36
39
  }