@armadra/agent 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. package/CHANGELOG.md +174 -1
  2. package/README.md +201 -82
  3. package/THIRD_PARTY_NOTICES.md +34 -0
  4. package/dist/acp.d.ts +18 -0
  5. package/dist/acp.js +19 -0
  6. package/dist/agent/limits.d.ts +38 -0
  7. package/dist/agent/limits.js +127 -0
  8. package/dist/agent/loop-guard.d.ts +38 -0
  9. package/dist/agent/loop-guard.js +95 -0
  10. package/dist/agent/loop.d.ts +4 -0
  11. package/dist/agent/loop.js +7 -0
  12. package/dist/agent/prompt-rules.d.ts +12 -0
  13. package/dist/agent/prompt-rules.js +18 -0
  14. package/dist/agent/reminders.d.ts +59 -0
  15. package/dist/agent/reminders.js +327 -0
  16. package/dist/agent/session-cache.d.ts +5 -0
  17. package/dist/agent/session-cache.js +17 -1
  18. package/dist/agent/session-classifier.d.ts +1 -1
  19. package/dist/agent/session-classifier.js +3 -1
  20. package/dist/agent/session-compaction.d.ts +35 -6
  21. package/dist/agent/session-compaction.js +157 -31
  22. package/dist/agent/session-core.d.ts +27 -1
  23. package/dist/agent/session-extensions.d.ts +61 -0
  24. package/dist/agent/session-extensions.js +129 -0
  25. package/dist/agent/session-images.d.ts +23 -0
  26. package/dist/agent/session-images.js +97 -0
  27. package/dist/agent/session-plan.d.ts +21 -0
  28. package/dist/agent/session-plan.js +524 -0
  29. package/dist/agent/session-rewind.d.ts +81 -0
  30. package/dist/agent/session-rewind.js +354 -0
  31. package/dist/agent/session-run.d.ts +28 -1
  32. package/dist/agent/session-run.js +157 -6
  33. package/dist/agent/session-settings.d.ts +49 -0
  34. package/dist/agent/session-settings.js +95 -0
  35. package/dist/agent/session-subagent.d.ts +36 -24
  36. package/dist/agent/session-subagent.js +259 -129
  37. package/dist/agent/session-sync.d.ts +10 -3
  38. package/dist/agent/session-sync.js +37 -8
  39. package/dist/agent/session-telemetry.d.ts +39 -0
  40. package/dist/agent/session-telemetry.js +231 -0
  41. package/dist/agent/session-tools.js +40 -1
  42. package/dist/agent/session.d.ts +30 -29
  43. package/dist/agent/session.js +79 -123
  44. package/dist/agent/subagent-registry.d.ts +91 -0
  45. package/dist/agent/subagent-registry.js +494 -0
  46. package/dist/agent/system-prompt.d.ts +6 -3
  47. package/dist/agent/system-prompt.js +7 -2
  48. package/dist/agent/tool-runner.d.ts +8 -1
  49. package/dist/agent/tool-runner.js +57 -4
  50. package/dist/agent/types-w5.d.ts +192 -0
  51. package/dist/agent/types-w5.js +10 -0
  52. package/dist/agent/types.d.ts +47 -4
  53. package/dist/agent/types.js +2 -0
  54. package/dist/agent/worktree.d.ts +36 -0
  55. package/dist/agent/worktree.js +86 -0
  56. package/dist/agents/builtin.d.ts +14 -0
  57. package/dist/agents/builtin.js +39 -0
  58. package/dist/agents/catalog.d.ts +43 -0
  59. package/dist/agents/catalog.js +82 -0
  60. package/dist/agents/discover.d.ts +35 -0
  61. package/dist/agents/discover.js +83 -0
  62. package/dist/agents/external.d.ts +82 -0
  63. package/dist/agents/external.js +326 -0
  64. package/dist/agents/parse.d.ts +18 -0
  65. package/dist/agents/parse.js +116 -0
  66. package/dist/agents/result.d.ts +32 -0
  67. package/dist/agents/result.js +67 -0
  68. package/dist/agents/task-control.d.ts +17 -0
  69. package/dist/agents/task-control.js +17 -0
  70. package/dist/agents/task-record.d.ts +78 -0
  71. package/dist/agents/task-record.js +141 -0
  72. package/dist/agents/types.d.ts +46 -0
  73. package/dist/agents/types.js +8 -0
  74. package/dist/ai/apis/anthropic-compat.d.ts +36 -0
  75. package/dist/ai/apis/anthropic-compat.js +92 -0
  76. package/dist/ai/apis/anthropic-request.d.ts +7 -6
  77. package/dist/ai/apis/anthropic-request.js +10 -28
  78. package/dist/ai/apis/cache-params.d.ts +11 -2
  79. package/dist/ai/apis/cache-params.js +25 -6
  80. package/dist/ai/apis/openai-compat.d.ts +4 -1
  81. package/dist/ai/apis/openai-compat.js +8 -1
  82. package/dist/ai/image-limits.d.ts +45 -0
  83. package/dist/ai/image-limits.js +68 -0
  84. package/dist/ai/providers/builtin.d.ts +18 -6
  85. package/dist/ai/providers/builtin.js +135 -6
  86. package/dist/ai/providers/catalog-data.js +17 -13
  87. package/dist/ai/providers/catalog.d.ts +60 -7
  88. package/dist/ai/providers/catalog.js +197 -38
  89. package/dist/ai/providers/channels.d.ts +33 -3
  90. package/dist/ai/providers/channels.js +108 -7
  91. package/dist/ai/providers/enrich.d.ts +6 -3
  92. package/dist/ai/providers/enrich.js +19 -4
  93. package/dist/ai/providers/models-dev-cache.d.ts +28 -24
  94. package/dist/ai/providers/models-dev-cache.js +92 -78
  95. package/dist/ai/providers/models-dev-data.d.ts +7 -0
  96. package/dist/ai/providers/models-dev-data.js +32 -0
  97. package/dist/ai/providers/models-dev-snapshot.d.ts +50 -0
  98. package/dist/ai/providers/models-dev-snapshot.js +137 -0
  99. package/dist/ai/providers/models-dev.d.ts +51 -10
  100. package/dist/ai/providers/models-dev.js +132 -39
  101. package/dist/ai/providers/registry.d.ts +20 -10
  102. package/dist/ai/providers/registry.js +82 -43
  103. package/dist/ai/types.d.ts +28 -2
  104. package/dist/ai/types.js +5 -0
  105. package/dist/bundle/ama.cjs +36203 -20499
  106. package/dist/checkpoints/backend.d.ts +61 -0
  107. package/dist/checkpoints/backend.js +135 -0
  108. package/dist/checkpoints/blobs.d.ts +60 -0
  109. package/dist/checkpoints/blobs.js +203 -0
  110. package/dist/checkpoints/gc.d.ts +47 -0
  111. package/dist/checkpoints/gc.js +144 -0
  112. package/dist/checkpoints/git-head.d.ts +15 -0
  113. package/dist/checkpoints/git-head.js +89 -0
  114. package/dist/checkpoints/index.d.ts +17 -0
  115. package/dist/checkpoints/index.js +17 -0
  116. package/dist/checkpoints/replay.d.ts +48 -0
  117. package/dist/checkpoints/replay.js +154 -0
  118. package/dist/checkpoints/restore.d.ts +86 -0
  119. package/dist/checkpoints/restore.js +263 -0
  120. package/dist/checkpoints/settings.d.ts +14 -0
  121. package/dist/checkpoints/settings.js +23 -0
  122. package/dist/checkpoints/shadow-git.d.ts +108 -0
  123. package/dist/checkpoints/shadow-git.js +491 -0
  124. package/dist/checkpoints/shadow-restore.d.ts +25 -0
  125. package/dist/checkpoints/shadow-restore.js +128 -0
  126. package/dist/checkpoints/tracker.d.ts +64 -0
  127. package/dist/checkpoints/tracker.js +192 -0
  128. package/dist/checkpoints/types.d.ts +99 -0
  129. package/dist/checkpoints/types.js +12 -0
  130. package/dist/cli/args.d.ts +8 -4
  131. package/dist/cli/args.js +17 -97
  132. package/dist/cli/bootstrap.js +13 -1
  133. package/dist/cli/codemode-notice.d.ts +3 -2
  134. package/dist/cli/codemode-notice.js +3 -2
  135. package/dist/cli/compose-agents.d.ts +27 -0
  136. package/dist/cli/compose-agents.js +111 -0
  137. package/dist/cli/compose-extensions.d.ts +29 -0
  138. package/dist/cli/compose-extensions.js +44 -0
  139. package/dist/cli/compose-session.d.ts +3 -0
  140. package/dist/cli/compose-session.js +47 -4
  141. package/dist/cli/compose.d.ts +3 -0
  142. package/dist/cli/compose.js +33 -6
  143. package/dist/cli/deps.d.ts +6 -0
  144. package/dist/cli/exit-codes.d.ts +10 -0
  145. package/dist/cli/exit-codes.js +12 -0
  146. package/dist/cli/help-text.d.ts +4 -0
  147. package/dist/cli/help-text.js +102 -0
  148. package/dist/cli/startup-screen.js +5 -3
  149. package/dist/cli/startup-steps.js +7 -1
  150. package/dist/cli/subcommands/config.d.ts +4 -1
  151. package/dist/cli/subcommands/config.js +17 -6
  152. package/dist/cli/subcommands/doctor.js +21 -0
  153. package/dist/cli/subcommands/models-discover.js +5 -6
  154. package/dist/cli/subcommands/models.d.ts +2 -0
  155. package/dist/cli/subcommands/models.js +33 -14
  156. package/dist/cli/subcommands/providers.js +5 -6
  157. package/dist/cli/subcommands/sessions.d.ts +3 -1
  158. package/dist/cli/subcommands/sessions.js +36 -2
  159. package/dist/codemode/capability.d.ts +24 -7
  160. package/dist/codemode/capability.js +38 -10
  161. package/dist/codemode/host-side.d.ts +14 -0
  162. package/dist/codemode/host-side.js +24 -1
  163. package/dist/codemode/tool.d.ts +1 -1
  164. package/dist/codemode/tool.js +6 -4
  165. package/dist/compaction/breaker.d.ts +25 -9
  166. package/dist/compaction/breaker.js +47 -17
  167. package/dist/compaction/estimate.d.ts +11 -3
  168. package/dist/compaction/estimate.js +64 -22
  169. package/dist/compaction/image-budget.d.ts +45 -0
  170. package/dist/compaction/image-budget.js +115 -0
  171. package/dist/compaction/post-compact.d.ts +38 -0
  172. package/dist/compaction/post-compact.js +152 -0
  173. package/dist/compaction/protect.d.ts +27 -0
  174. package/dist/compaction/protect.js +56 -0
  175. package/dist/compaction/prune-tier.d.ts +58 -12
  176. package/dist/compaction/prune-tier.js +120 -54
  177. package/dist/compaction/summarize-tier.d.ts +23 -4
  178. package/dist/compaction/summarize-tier.js +100 -29
  179. package/dist/config/checker.d.ts +28 -0
  180. package/dist/config/checker.js +97 -0
  181. package/dist/config/json-schema.js +63 -2
  182. package/dist/config/key-docs.js +72 -3
  183. package/dist/config/merge.d.ts +5 -3
  184. package/dist/config/merge.js +81 -7
  185. package/dist/config/profile.d.ts +2 -0
  186. package/dist/config/profile.js +2 -0
  187. package/dist/config/schema-w5.d.ts +18 -0
  188. package/dist/config/schema-w5.js +109 -0
  189. package/dist/config/schema.d.ts +5 -9
  190. package/dist/config/schema.js +68 -114
  191. package/dist/config/types-w5.d.ts +118 -0
  192. package/dist/config/types-w5.js +20 -0
  193. package/dist/config/types.d.ts +50 -1
  194. package/dist/config/types.js +12 -0
  195. package/dist/drivers/acp/client.d.ts +57 -0
  196. package/dist/drivers/acp/client.js +160 -0
  197. package/dist/drivers/acp/driver.d.ts +31 -0
  198. package/dist/drivers/acp/driver.js +274 -0
  199. package/dist/drivers/acp/testing/fake-agent-main.d.ts +5 -0
  200. package/dist/drivers/acp/testing/fake-agent-main.js +7 -0
  201. package/dist/drivers/acp/testing/fake-agent.d.ts +27 -0
  202. package/dist/drivers/acp/testing/fake-agent.js +211 -0
  203. package/dist/drivers/acp/types.d.ts +285 -0
  204. package/dist/drivers/acp/types.js +36 -0
  205. package/dist/drivers/agents.d.ts +58 -0
  206. package/dist/drivers/agents.js +142 -0
  207. package/dist/drivers/base.d.ts +35 -0
  208. package/dist/drivers/base.js +59 -0
  209. package/dist/drivers/catalog.d.ts +46 -0
  210. package/dist/drivers/catalog.js +167 -0
  211. package/dist/drivers/env.d.ts +20 -0
  212. package/dist/drivers/env.js +46 -0
  213. package/dist/drivers/host-runners.d.ts +18 -0
  214. package/dist/drivers/host-runners.js +45 -0
  215. package/dist/drivers/jsonrpc.d.ts +58 -0
  216. package/dist/drivers/jsonrpc.js +180 -0
  217. package/dist/drivers/native/claude-normalize.d.ts +46 -0
  218. package/dist/drivers/native/claude-normalize.js +106 -0
  219. package/dist/drivers/native/claude-stream.d.ts +29 -0
  220. package/dist/drivers/native/claude-stream.js +456 -0
  221. package/dist/drivers/native/codex-app-server.d.ts +26 -0
  222. package/dist/drivers/native/codex-app-server.js +407 -0
  223. package/dist/drivers/native/codex-normalize.d.ts +38 -0
  224. package/dist/drivers/native/codex-normalize.js +124 -0
  225. package/dist/drivers/native/oneshot.d.ts +28 -0
  226. package/dist/drivers/native/oneshot.js +312 -0
  227. package/dist/drivers/permissions.d.ts +40 -0
  228. package/dist/drivers/permissions.js +92 -0
  229. package/dist/drivers/pids.d.ts +32 -0
  230. package/dist/drivers/pids.js +93 -0
  231. package/dist/drivers/pool.d.ts +28 -0
  232. package/dist/drivers/pool.js +96 -0
  233. package/dist/drivers/probe.d.ts +47 -0
  234. package/dist/drivers/probe.js +172 -0
  235. package/dist/drivers/process.d.ts +48 -0
  236. package/dist/drivers/process.js +100 -0
  237. package/dist/drivers/runner.d.ts +61 -0
  238. package/dist/drivers/runner.js +388 -0
  239. package/dist/drivers/store.d.ts +49 -0
  240. package/dist/drivers/store.js +91 -0
  241. package/dist/drivers/turn.d.ts +41 -0
  242. package/dist/drivers/turn.js +99 -0
  243. package/dist/drivers/types.d.ts +122 -0
  244. package/dist/drivers/types.js +11 -0
  245. package/dist/git/info.d.ts +75 -0
  246. package/dist/git/info.js +191 -0
  247. package/dist/hooks/protocol.js +6 -1
  248. package/dist/hooks/types.d.ts +10 -1
  249. package/dist/hooks/types.js +2 -0
  250. package/dist/host/api-impl.d.ts +7 -0
  251. package/dist/host/api-impl.js +15 -0
  252. package/dist/host/types.d.ts +24 -2
  253. package/dist/host/types.js +4 -0
  254. package/dist/index.d.ts +3 -0
  255. package/dist/index.js +1 -0
  256. package/dist/modes/acp/acp-events.d.ts +46 -0
  257. package/dist/modes/acp/acp-events.js +233 -0
  258. package/dist/modes/acp/acp-mode.d.ts +17 -0
  259. package/dist/modes/acp/acp-mode.js +47 -0
  260. package/dist/modes/acp/acp-server.d.ts +79 -0
  261. package/dist/modes/acp/acp-server.js +315 -0
  262. package/dist/modes/commands-core.d.ts +7 -1
  263. package/dist/modes/commands-core.js +55 -0
  264. package/dist/modes/image-input.d.ts +12 -4
  265. package/dist/modes/image-input.js +22 -5
  266. package/dist/modes/interactive/agent-panels.d.ts +14 -0
  267. package/dist/modes/interactive/agent-panels.js +103 -0
  268. package/dist/modes/interactive/agent-ui.d.ts +73 -0
  269. package/dist/modes/interactive/agent-ui.js +234 -0
  270. package/dist/modes/interactive/approval-dialog.d.ts +21 -4
  271. package/dist/modes/interactive/approval-dialog.js +98 -20
  272. package/dist/modes/interactive/approval-merge.d.ts +41 -0
  273. package/dist/modes/interactive/approval-merge.js +89 -0
  274. package/dist/modes/interactive/clipboard-paste.d.ts +18 -0
  275. package/dist/modes/interactive/clipboard-paste.js +28 -0
  276. package/dist/modes/interactive/commands.d.ts +9 -1
  277. package/dist/modes/interactive/commands.js +19 -0
  278. package/dist/modes/interactive/double-esc.d.ts +20 -0
  279. package/dist/modes/interactive/double-esc.js +34 -0
  280. package/dist/modes/interactive/event-notices.d.ts +32 -0
  281. package/dist/modes/interactive/event-notices.js +79 -0
  282. package/dist/modes/interactive/external-editor.d.ts +16 -0
  283. package/dist/modes/interactive/external-editor.js +39 -0
  284. package/dist/modes/interactive/interactive-mode.d.ts +14 -1
  285. package/dist/modes/interactive/interactive-mode.js +134 -136
  286. package/dist/modes/interactive/key-dispatch.d.ts +16 -2
  287. package/dist/modes/interactive/key-dispatch.js +49 -1
  288. package/dist/modes/interactive/line/line-mode.d.ts +2 -1
  289. package/dist/modes/interactive/line/line-mode.js +22 -5
  290. package/dist/modes/interactive/line/line-render.d.ts +2 -1
  291. package/dist/modes/interactive/line/line-render.js +40 -4
  292. package/dist/modes/interactive/message-view.d.ts +1 -1
  293. package/dist/modes/interactive/message-view.js +24 -2
  294. package/dist/modes/interactive/panels.d.ts +18 -1
  295. package/dist/modes/interactive/panels.js +19 -5
  296. package/dist/modes/interactive/plan-command.d.ts +40 -0
  297. package/dist/modes/interactive/plan-command.js +129 -0
  298. package/dist/modes/interactive/plan-dialog.d.ts +98 -0
  299. package/dist/modes/interactive/plan-dialog.js +290 -0
  300. package/dist/modes/interactive/plan-flow.d.ts +34 -0
  301. package/dist/modes/interactive/plan-flow.js +98 -0
  302. package/dist/modes/interactive/rewind-command.d.ts +31 -0
  303. package/dist/modes/interactive/rewind-command.js +106 -0
  304. package/dist/modes/interactive/rewind-flow.d.ts +37 -0
  305. package/dist/modes/interactive/rewind-flow.js +151 -0
  306. package/dist/modes/interactive/rewind-list.d.ts +36 -0
  307. package/dist/modes/interactive/rewind-list.js +123 -0
  308. package/dist/modes/interactive/rewind-panel.d.ts +77 -0
  309. package/dist/modes/interactive/rewind-panel.js +309 -0
  310. package/dist/modes/interactive/rewind-text.d.ts +40 -0
  311. package/dist/modes/interactive/rewind-text.js +139 -0
  312. package/dist/modes/interactive/session-events.d.ts +24 -0
  313. package/dist/modes/interactive/session-events.js +96 -0
  314. package/dist/modes/interactive/status-area.d.ts +73 -0
  315. package/dist/modes/interactive/status-area.js +190 -0
  316. package/dist/modes/interactive/status-bar.d.ts +99 -11
  317. package/dist/modes/interactive/status-bar.js +273 -90
  318. package/dist/modes/interactive/status-line.d.ts +33 -0
  319. package/dist/modes/interactive/status-line.js +113 -0
  320. package/dist/modes/interactive/subagent-view.d.ts +56 -0
  321. package/dist/modes/interactive/subagent-view.js +154 -0
  322. package/dist/modes/interactive/tasks-report.d.ts +27 -0
  323. package/dist/modes/interactive/tasks-report.js +114 -0
  324. package/dist/modes/interactive/tool-view.d.ts +12 -2
  325. package/dist/modes/interactive/tool-view.js +34 -4
  326. package/dist/modes/print/print-mode.d.ts +13 -4
  327. package/dist/modes/print/print-mode.js +56 -12
  328. package/dist/modes/rpc/commands.d.ts +9 -0
  329. package/dist/modes/rpc/commands.js +44 -0
  330. package/dist/modes/rpc/rpc-mode.js +7 -1
  331. package/dist/modes/session-report.d.ts +11 -0
  332. package/dist/modes/session-report.js +48 -1
  333. package/dist/permissions/auto-safe.d.ts +5 -0
  334. package/dist/permissions/auto-safe.js +18 -0
  335. package/dist/permissions/classifier.d.ts +4 -0
  336. package/dist/permissions/classifier.js +2 -0
  337. package/dist/permissions/modes.js +1 -1
  338. package/dist/permissions/pipeline.d.ts +43 -4
  339. package/dist/permissions/pipeline.js +114 -9
  340. package/dist/permissions/readonly-bash.d.ts +30 -0
  341. package/dist/permissions/readonly-bash.js +118 -0
  342. package/dist/permissions/types.d.ts +42 -0
  343. package/dist/plan/compose.d.ts +9 -0
  344. package/dist/plan/compose.js +19 -0
  345. package/dist/plan/controller.d.ts +71 -0
  346. package/dist/plan/controller.js +27 -0
  347. package/dist/plan/done-markers.d.ts +17 -0
  348. package/dist/plan/done-markers.js +51 -0
  349. package/dist/plan/extract.d.ts +29 -0
  350. package/dist/plan/extract.js +160 -0
  351. package/dist/plan/prompts.d.ts +36 -0
  352. package/dist/plan/prompts.js +89 -0
  353. package/dist/plan/store.d.ts +37 -0
  354. package/dist/plan/store.js +90 -0
  355. package/dist/rpc.d.ts +58 -2
  356. package/dist/rpc.js +7 -0
  357. package/dist/sandbox/bash.d.ts +74 -0
  358. package/dist/sandbox/bash.js +126 -0
  359. package/dist/sandbox/detect.d.ts +48 -0
  360. package/dist/sandbox/detect.js +149 -0
  361. package/dist/sandbox/index.d.ts +5 -0
  362. package/dist/sandbox/index.js +5 -0
  363. package/dist/sandbox/profile.d.ts +33 -0
  364. package/dist/sandbox/profile.js +76 -0
  365. package/dist/sandbox/wrap.d.ts +29 -0
  366. package/dist/sandbox/wrap.js +117 -0
  367. package/dist/sdk.d.ts +33 -2
  368. package/dist/sdk.js +24 -2
  369. package/dist/session/types.d.ts +3 -1
  370. package/dist/session/types.js +1 -0
  371. package/dist/skills/builtin.d.ts +31 -0
  372. package/dist/skills/builtin.js +109 -0
  373. package/dist/skills/discover.d.ts +2 -1
  374. package/dist/skills/index-prompt.js +5 -8
  375. package/dist/tools/background-jobs.d.ts +71 -0
  376. package/dist/tools/background-jobs.js +198 -0
  377. package/dist/tools/bash.d.ts +53 -1
  378. package/dist/tools/bash.js +158 -7
  379. package/dist/tools/clipboard-image.d.ts +44 -0
  380. package/dist/tools/clipboard-image.js +148 -0
  381. package/dist/tools/edit.d.ts +2 -0
  382. package/dist/tools/edit.js +18 -3
  383. package/dist/tools/image-file.d.ts +38 -3
  384. package/dist/tools/image-file.js +81 -10
  385. package/dist/tools/image-resize.d.ts +58 -0
  386. package/dist/tools/image-resize.js +97 -0
  387. package/dist/tools/presets.d.ts +7 -2
  388. package/dist/tools/presets.js +11 -4
  389. package/dist/tools/read.d.ts +5 -2
  390. package/dist/tools/read.js +13 -10
  391. package/dist/tools/registry.d.ts +7 -1
  392. package/dist/tools/registry.js +27 -4
  393. package/dist/tools/task-ctl.d.ts +22 -0
  394. package/dist/tools/task-ctl.js +116 -0
  395. package/dist/tools/task.d.ts +22 -10
  396. package/dist/tools/task.js +66 -26
  397. package/dist/tools/todo.d.ts +22 -2
  398. package/dist/tools/todo.js +58 -14
  399. package/dist/tools/truncate.d.ts +15 -0
  400. package/dist/tools/truncate.js +25 -0
  401. package/dist/tools/types.d.ts +113 -0
  402. package/dist/tools/types.js +5 -0
  403. package/dist/tools/write.js +3 -0
  404. package/dist/tui/glyphs.d.ts +4 -0
  405. package/dist/tui/glyphs.js +4 -0
  406. package/dist/tui/keybindings.d.ts +5 -0
  407. package/dist/tui/keybindings.js +5 -0
  408. package/docs/acp.md +78 -0
  409. package/docs/agents.md +250 -0
  410. package/docs/codemode.md +12 -8
  411. package/docs/hooks.md +26 -22
  412. package/docs/permissions.md +77 -13
  413. package/docs/plan.md +107 -0
  414. package/docs/providers.md +228 -52
  415. package/docs/rewind-plan.md +181 -0
  416. package/docs/rpc.md +92 -5
  417. package/docs/sandbox.md +215 -0
  418. package/docs/session-format.md +41 -21
  419. package/docs/sessions.md +41 -1
  420. package/docs/tui-design.md +751 -0
  421. package/docs/tui.md +125 -6
  422. package/package.json +13 -2
@@ -10,17 +10,32 @@
10
10
  * auth.json 的同名条目),没有时用供应商的 key。
11
11
  */
12
12
  import type { ProviderConfig } from "../../config/types.js";
13
- import type { Api, ProviderChannel } from "../types.js";
13
+ import type { Api, Model, ProviderChannel, ProviderData } from "../types.js";
14
14
  /** 隐式渠道名(单渠道供应商)。 */
15
15
  export declare const DEFAULT_CHANNEL = "default";
16
16
  /** 渠道 key 在 key 解析里的键。 */
17
17
  export declare function channelKeyId(providerId: string, channel: string): string;
18
- /** config 的 `channels` → 物化渠道表(跳过名字非法或缺 api / baseUrl 的,返回警告)。 */
19
- export declare function parseChannels(providerId: string, config: ProviderConfig): {
18
+ /**
19
+ * 内置渠道 ⊕ 用户渠道([W5-M2]):同名按字段覆盖(`headers` / `compat` 再合并一层)、新名追加在后。
20
+ */
21
+ export declare function mergeChannels(base: readonly ProviderChannel[], user: readonly ProviderChannel[]): ProviderChannel[];
22
+ /**
23
+ * config 的 `channels` → 物化渠道表(跳过名字非法或缺 api / baseUrl 的,返回警告)。给了 `builtin`
24
+ * (内置供应商的内置渠道)时与之合并:同名渠道可只写要改的字段;`defaultChannel` 用户优先,其次内置。
25
+ */
26
+ export declare function parseChannels(providerId: string, config: ProviderConfig, builtin?: {
27
+ channels: readonly ProviderChannel[];
28
+ defaultChannel: string | undefined;
29
+ }): {
20
30
  channels: ProviderChannel[];
21
31
  defaultChannel: string | undefined;
22
32
  warnings: string[];
23
33
  };
34
+ /**
35
+ * 内置供应商目录模型的渠道表:目录条目写了 `channels` 就用它(去掉不存在的),否则挂全部渠道;
36
+ * 缺省渠道在表里时排到首位(首个 = 首选)。
37
+ */
38
+ export declare function catalogChannels(wanted: readonly string[] | undefined, known: readonly ProviderChannel[], defaultChannel: string): string[];
24
39
  /** 模型的渠道表:去掉不存在的;空 → [defaultChannel]。 */
25
40
  export declare function modelChannels(wanted: readonly string[] | undefined, known: readonly ProviderChannel[], defaultChannel: string, onUnknown: (name: string) => void): string[];
26
41
  /**
@@ -49,3 +64,18 @@ export declare function modelRefOf(model: {
49
64
  };
50
65
  /** 协议的短名(表格用)。 */
51
66
  export declare function apiShortName(api: Api): string;
67
+ /**
68
+ * 物化:模型补齐供应商级字段(不可变:返回新对象)。给了 `channel` 时协议与地址取渠道的(模型级
69
+ * `explicit` 覆盖优先),headers / compat 按 供应商 ← 渠道 ← 模型 合并,并记下渠道名。
70
+ */
71
+ export declare function materializeModel(model: Model, provider: ProviderData, channel?: ProviderChannel, explicit?: {
72
+ api?: Api | undefined;
73
+ baseUrl?: string | undefined;
74
+ }): Model;
75
+ /** 内置渠道作废:按供应商级 api / baseUrl 单渠道处理。 */
76
+ export declare function dropChannels(provider: ProviderData): void;
77
+ /**
78
+ * 内置渠道 + 用户只改供应商级字段:改了 baseUrl → 内置渠道作废(单渠道回落);只写 `api` → 选同协议的
79
+ * 内置渠道作缺省(没有就作废);`defaultChannel` 必须是内置渠道名。
80
+ */
81
+ export declare function pickBuiltinChannel(provider: ProviderData, config: ProviderConfig, warn: (message: string) => void): void;
@@ -10,37 +10,84 @@
10
10
  * auth.json 的同名条目),没有时用供应商的 key。
11
11
  */
12
12
  import { CHANNEL_NAME_PATTERN } from "../../config/types.js";
13
+ import { mergeHeaders } from "../http.js";
13
14
  /** 隐式渠道名(单渠道供应商)。 */
14
15
  export const DEFAULT_CHANNEL = "default";
15
16
  /** 渠道 key 在 key 解析里的键。 */
16
17
  export function channelKeyId(providerId, channel) {
17
18
  return `${providerId}@${channel}`;
18
19
  }
19
- /** config 的 `channels` → 物化渠道表(跳过名字非法或缺 api / baseUrl 的,返回警告)。 */
20
- export function parseChannels(providerId, config) {
20
+ /**
21
+ * 内置渠道 ⊕ 用户渠道([W5-M2]):同名按字段覆盖(`headers` / `compat` 再合并一层)、新名追加在后。
22
+ */
23
+ export function mergeChannels(base, user) {
24
+ const out = base.map((c) => structuredClone(c));
25
+ for (const channel of user) {
26
+ const index = out.findIndex((c) => c.name === channel.name);
27
+ const current = out[index];
28
+ if (current === undefined) {
29
+ out.push(channel);
30
+ continue;
31
+ }
32
+ const next = { ...current, ...channel };
33
+ if (current.headers && channel.headers)
34
+ next.headers = { ...current.headers, ...channel.headers };
35
+ if (current.compat && channel.compat)
36
+ next.compat = { ...current.compat, ...channel.compat };
37
+ out[index] = next;
38
+ }
39
+ return out;
40
+ }
41
+ /**
42
+ * config 的 `channels` → 物化渠道表(跳过名字非法或缺 api / baseUrl 的,返回警告)。给了 `builtin`
43
+ * (内置供应商的内置渠道)时与之合并:同名渠道可只写要改的字段;`defaultChannel` 用户优先,其次内置。
44
+ */
45
+ export function parseChannels(providerId, config, builtin) {
21
46
  const warnings = [];
22
- const channels = [];
47
+ const own = [];
48
+ const known = new Set((builtin?.channels ?? []).map((c) => c.name));
23
49
  for (const [name, raw] of Object.entries(config.channels ?? {})) {
24
50
  const c = raw;
25
- if (!CHANNEL_NAME_PATTERN.test(name) || !c || !c.api || !c.baseUrl) {
51
+ const partial = known.has(name);
52
+ if (!CHANNEL_NAME_PATTERN.test(name) || !c || (!partial && (!c.api || !c.baseUrl))) {
26
53
  warnings.push(`provider "${providerId}" channel "${name}" is invalid; ignored`);
27
54
  continue;
28
55
  }
29
- const channel = { name, api: c.api, baseUrl: c.baseUrl };
56
+ const channel = { name };
57
+ if (c.api)
58
+ channel.api = c.api;
59
+ if (c.baseUrl)
60
+ channel.baseUrl = c.baseUrl;
30
61
  if (c.authHeader !== undefined)
31
62
  channel.authHeader = c.authHeader;
32
63
  if (c.headers !== undefined)
33
64
  channel.headers = { ...c.headers };
34
65
  if (c.compat !== undefined)
35
66
  channel.compat = { ...c.compat };
36
- channels.push(channel);
67
+ own.push(channel);
37
68
  }
69
+ const channels = mergeChannels(builtin?.channels ?? [], own);
38
70
  let defaultChannel = config.defaultChannel;
39
71
  if (defaultChannel !== undefined && !channels.some((c) => c.name === defaultChannel)) {
40
72
  warnings.push(`provider "${providerId}" defaultChannel "${defaultChannel}" not found`);
41
73
  defaultChannel = undefined;
42
74
  }
43
- return { channels, defaultChannel: defaultChannel ?? channels[0]?.name, warnings };
75
+ const fallback = channels.some((c) => c.name === builtin?.defaultChannel)
76
+ ? builtin?.defaultChannel
77
+ : channels[0]?.name;
78
+ return { channels, defaultChannel: defaultChannel ?? fallback, warnings };
79
+ }
80
+ /**
81
+ * 内置供应商目录模型的渠道表:目录条目写了 `channels` 就用它(去掉不存在的),否则挂全部渠道;
82
+ * 缺省渠道在表里时排到首位(首个 = 首选)。
83
+ */
84
+ export function catalogChannels(wanted, known, defaultChannel) {
85
+ const names = known.map((c) => c.name);
86
+ const list = wanted === undefined ? names : wanted.filter((name) => names.includes(name));
87
+ const out = list.includes(defaultChannel)
88
+ ? [defaultChannel, ...list.filter((name) => name !== defaultChannel)]
89
+ : [...list];
90
+ return out.length > 0 ? out : [defaultChannel];
44
91
  }
45
92
  /** 模型的渠道表:去掉不存在的;空 → [defaultChannel]。 */
46
93
  export function modelChannels(wanted, known, defaultChannel, onUnknown) {
@@ -94,3 +141,57 @@ export function apiShortName(api) {
94
141
  return api;
95
142
  }
96
143
  }
144
+ /**
145
+ * 物化:模型补齐供应商级字段(不可变:返回新对象)。给了 `channel` 时协议与地址取渠道的(模型级
146
+ * `explicit` 覆盖优先),headers / compat 按 供应商 ← 渠道 ← 模型 合并,并记下渠道名。
147
+ */
148
+ export function materializeModel(model, provider, channel, explicit) {
149
+ const out = {
150
+ ...model,
151
+ provider: provider.id,
152
+ baseUrl: model.baseUrl ?? provider.baseUrl,
153
+ };
154
+ if (channel !== undefined) {
155
+ out.api = explicit?.api ?? channel.api;
156
+ out.baseUrl = explicit?.baseUrl ?? channel.baseUrl;
157
+ out.channel = channel.name;
158
+ }
159
+ if (provider.headers || channel?.headers || model.headers)
160
+ out.headers = mergeHeaders(provider.headers, channel?.headers, model.headers);
161
+ if (provider.compat || channel?.compat || model.compat)
162
+ out.compat = { ...provider.compat, ...channel?.compat, ...model.compat };
163
+ const authHeader = channel?.authHeader ?? provider.authHeader;
164
+ if (authHeader !== undefined)
165
+ out.authHeader = model.authHeader ?? authHeader;
166
+ out.requiresApiKey = provider.requiresApiKey;
167
+ return out;
168
+ }
169
+ /** 内置渠道作废:按供应商级 api / baseUrl 单渠道处理。 */
170
+ export function dropChannels(provider) {
171
+ delete provider.channels;
172
+ delete provider.defaultChannel;
173
+ }
174
+ /**
175
+ * 内置渠道 + 用户只改供应商级字段:改了 baseUrl → 内置渠道作废(单渠道回落);只写 `api` → 选同协议的
176
+ * 内置渠道作缺省(没有就作废);`defaultChannel` 必须是内置渠道名。
177
+ */
178
+ export function pickBuiltinChannel(provider, config, warn) {
179
+ if (config.baseUrl !== undefined) {
180
+ dropChannels(provider);
181
+ return;
182
+ }
183
+ const channels = provider.channels ?? [];
184
+ if (config.defaultChannel !== undefined) {
185
+ if (channels.some((c) => c.name === config.defaultChannel))
186
+ provider.defaultChannel = config.defaultChannel;
187
+ else
188
+ warn(`provider "${provider.id}" defaultChannel "${config.defaultChannel}" not found`);
189
+ }
190
+ else if (config.api !== undefined) {
191
+ const same = channels.find((c) => c.api === config.api);
192
+ if (same !== undefined)
193
+ provider.defaultChannel = same.name;
194
+ else
195
+ dropChannels(provider);
196
+ }
197
+ }
@@ -2,8 +2,8 @@
2
2
  * 用 models.dev 缓存给自定义模型补元数据(docs/providers.md「模型元数据:models.dev」)。
3
3
  *
4
4
  * 优先级:用户配置写了的字段 > models.dev > 自定义缺省(maxTokens 8192、input ["text"]、
5
- * reasoning false、不猜 contextWindow)。内置目录的模型不经过这里(目录本身就是人工校对的值)。
6
- * 只读传入的索引,不联网。
5
+ * reasoning false、不猜 contextWindow)。内置目录的模型不经过这里:目录在 catalog.ts 里已经
6
+ * 「快照 ⊕ 覆盖」合并好([W5-M1]),这里只给它们标来源。只读传入的索引,不联网。
7
7
  */
8
8
  import type { ModelConfig } from "../../config/types.js";
9
9
  import type { Model } from "../types.js";
@@ -29,6 +29,9 @@ export declare function enrichEntry(entry: ModelConfig, index: () => ModelsDevIn
29
29
  entry: Omit<ModelConfig, "modelsDev" | "channels">;
30
30
  metadata: ModelMetadata;
31
31
  };
32
- /** 内置目录模型的来源(全部 catalog;目录没写 contextWindow / cost 的为缺省)。 */
32
+ /**
33
+ * 内置目录模型的来源:目录自己写的为 catalog,从入库快照继承的为 models.dev(catalog.ts),
34
+ * 都没有的为缺省。
35
+ */
33
36
  export declare function catalogMetadata(model: Model): ModelMetadata;
34
37
  export declare function sourceText(source: FieldSource): string;
@@ -2,9 +2,10 @@
2
2
  * 用 models.dev 缓存给自定义模型补元数据(docs/providers.md「模型元数据:models.dev」)。
3
3
  *
4
4
  * 优先级:用户配置写了的字段 > models.dev > 自定义缺省(maxTokens 8192、input ["text"]、
5
- * reasoning false、不猜 contextWindow)。内置目录的模型不经过这里(目录本身就是人工校对的值)。
6
- * 只读传入的索引,不联网。
5
+ * reasoning false、不猜 contextWindow)。内置目录的模型不经过这里:目录在 catalog.ts 里已经
6
+ * 「快照 ⊕ 覆盖」合并好([W5-M1]),这里只给它们标来源。只读传入的索引,不联网。
7
7
  */
8
+ import { catalogInherited } from "./catalog.js";
8
9
  import { ENRICHABLE_FIELDS, modelsDevFields, } from "./models-dev.js";
9
10
  /** 惰性取索引:只有真的要补字段时才读缓存(启动时没有自定义模型就不读约 2 MB 的缓存)。 */
10
11
  export function lazyIndex(source) {
@@ -25,6 +26,8 @@ export function lazyIndex(source) {
25
26
  return index;
26
27
  };
27
28
  }
29
+ /** 只作说明的元数据(不进来源表):缺了就从 models.dev 补。 */
30
+ const METADATA_FIELDS = ["family", "knowledge", "releaseDate", "inputLimit", "status"];
28
31
  function needsLookup(entry) {
29
32
  return ENRICHABLE_FIELDS.some((field) => entry[field] === undefined);
30
33
  }
@@ -65,13 +68,25 @@ export function enrichEntry(entry, index) {
65
68
  }
66
69
  if (out.name === undefined && fields.name !== undefined)
67
70
  out.name = fields.name;
71
+ for (const field of METADATA_FIELDS) {
72
+ if (out[field] === undefined && fields[field] !== undefined)
73
+ out[field] = fields[field];
74
+ }
68
75
  return { entry: out, metadata };
69
76
  }
70
- /** 内置目录模型的来源(全部 catalog;目录没写 contextWindow / cost 的为缺省)。 */
77
+ /**
78
+ * 内置目录模型的来源:目录自己写的为 catalog,从入库快照继承的为 models.dev(catalog.ts),
79
+ * 都没有的为缺省。
80
+ */
71
81
  export function catalogMetadata(model) {
82
+ const inherited = catalogInherited(model.provider, model.id);
72
83
  const sources = {};
73
84
  for (const field of ENRICHABLE_FIELDS) {
74
- sources[field] = model[field] !== undefined ? "catalog" : "default";
85
+ sources[field] = inherited.includes(field)
86
+ ? "models.dev"
87
+ : model[field] !== undefined
88
+ ? "catalog"
89
+ : "default";
75
90
  }
76
91
  return { sources, looked: false };
77
92
  }
@@ -1,53 +1,57 @@
1
1
  /**
2
- * models.dev 缓存(docs/providers.md「模型元数据:models.dev」)。
2
+ * models.dev 索引 = 内置快照 ⊕ 用户级覆盖(docs/wave5-plan.md §2.2、docs/providers.md「模型元数据」)。
3
3
  *
4
- * - 缓存文件 `<dataDir>/models-dev.json`:`{ version, url, fetchedAt, etag?, providers }`,providers 是
5
- * `trimModelsDev()` 裁剪后的数据(约 2.3 MB,原始约 5 MB)。写入走同目录临时文件 + rename。
6
- * - **启动不联网**:`loadModelsDevIndex()` 只读缓存(按路径 + mtime 记忆,同一进程只解析一次)。
7
- * - 联网只在 `ama providers add|refresh`、`ama models discover`、`ama models refresh-catalog` 调
8
- * `refreshModelsDev()` 时发生:缓存 24 小时内不重拉(`force` 例外),带 `If-None-Match`;304 只刷新时间;
9
- * 失败时返回旧缓存与 warning。`AMA_MODELS_DEV_URL` 换数据源。
4
+ * - 内置快照随 bundle 携带(models-dev-snapshot.ts),**启动与运行都不联网**。
5
+ * - 覆盖文件 `<dataDir>/models-dev.json`:`{ version: 2, url, fetchedAt, providers }`,只有
6
+ * `ama models refresh` 会写(`refreshModelsDev()`,显式联网,按快照同一清单裁剪)。存在且
7
+ * fetchedAt 晚于快照才叠加;同 provider/model 以覆盖为准。旧版(version 1,全量 2 MB 缓存)忽略。
8
+ * - `loadModelsDevIndex()` 按覆盖文件路径 + mtime + 大小记忆,同一进程只解析一次。
9
+ * - `AMA_MODELS_DEV_URL` 换刷新的数据源。
10
10
  */
11
- import { ModelsDevIndex, type ModelsDevData } from "./models-dev.js";
11
+ import type { ModelsDevData, ModelsDevIndex } from "./models-dev.js";
12
+ import { type SnapshotDiff } from "./models-dev-snapshot.js";
12
13
  export declare const MODELS_DEV_URL = "https://models.dev/api.json";
13
14
  export declare const MODELS_DEV_URL_ENV = "AMA_MODELS_DEV_URL";
14
15
  export declare const MODELS_DEV_FILE = "models-dev.json";
15
- export declare const MODELS_DEV_TTL_MS: number;
16
- export declare const MODELS_DEV_TIMEOUT_MS = 30000;
16
+ export declare const MODELS_DEV_TIMEOUT_MS = 60000;
17
17
  export interface ModelsDevCacheFile {
18
- version: 1;
18
+ version: 2;
19
19
  url: string;
20
20
  /** ISO 8601。 */
21
21
  fetchedAt: string;
22
- etag?: string;
23
22
  providers: ModelsDevData;
24
23
  }
25
24
  export declare function modelsDevCachePath(dataDir: string): string;
26
25
  export declare function modelsDevUrl(env?: Readonly<Record<string, string | undefined>>): string;
27
- /** 读缓存;不存在或损坏返回 undefined(损坏不报错:下次拉取会覆盖)。 */
26
+ /** 读覆盖文件;不存在、旧版或损坏返回 undefined(损坏不报错:下次 refresh 会覆盖)。 */
28
27
  export declare function readModelsDevCache(dataDir: string): ModelsDevCacheFile | undefined;
29
28
  export declare function writeModelsDevCache(dataDir: string, file: ModelsDevCacheFile): void;
30
- /** 只读缓存构造索引(不联网);没有缓存返回 undefined。 */
31
- export declare function loadModelsDevIndex(dataDir: string): ModelsDevIndex | undefined;
32
- export type RefreshStatus = "fresh" | "updated" | "not-modified" | "stale" | "unavailable";
29
+ /** 覆盖文件是否生效:fetchedAt 晚于内置快照。 */
30
+ export declare function overrideApplies(file: ModelsDevCacheFile): boolean;
31
+ /** 快照 ⊕ 覆盖(零网络;没有覆盖文件时只做一次 stat)。 */
32
+ export declare function loadModelsDevIndex(dataDir: string): ModelsDevIndex;
33
+ /** 当前索引的来源说明(`providers add` / `models discover` 输出用)。 */
34
+ export declare function describeModelsDev(dataDir: string): string;
35
+ export type RefreshStatus = "updated" | "unchanged" | "failed";
33
36
  export interface RefreshResult {
34
37
  status: RefreshStatus;
35
- index?: ModelsDevIndex;
36
- fetchedAt?: string;
37
38
  url: string;
39
+ fetchedAt?: string;
40
+ /** 与刷新前的索引(快照 ⊕ 旧覆盖)相比。 */
41
+ diff?: SnapshotDiff;
42
+ index: ModelsDevIndex;
38
43
  warning?: string;
39
44
  }
40
45
  export interface RefreshOptions {
41
46
  dataDir: string;
42
47
  env?: Readonly<Record<string, string | undefined>>;
43
- /** 忽略 TTL(`ama models refresh-catalog`)。 */
44
- force?: boolean;
45
- ttlMs?: number;
48
+ /** 只刷新这几家(须在快照清单里)。 */
49
+ providers?: readonly string[];
46
50
  timeoutMs?: number;
47
51
  now?: () => number;
48
52
  fetch?: typeof fetch;
49
53
  }
50
- /** 按需拉取 models.dev 并更新缓存;失败时回落旧缓存(status `stale`)或无数据(`unavailable`)。 */
54
+ /** 显式联网刷新(只被 `ama models refresh` 调用):拉 api.json、按清单裁剪、写用户级覆盖。 */
51
55
  export declare function refreshModelsDev(options: RefreshOptions): Promise<RefreshResult>;
52
- /** 一行状态说明(命令输出用)。 */
53
- export declare function describeRefresh(result: RefreshResult): string;
56
+ /** 刷新结果的说明(命令输出用):一行状态 + 新增 / 删除 / 变化清单。 */
57
+ export declare function describeRefresh(result: RefreshResult, limit?: number): string;
@@ -1,21 +1,21 @@
1
1
  /**
2
- * models.dev 缓存(docs/providers.md「模型元数据:models.dev」)。
2
+ * models.dev 索引 = 内置快照 ⊕ 用户级覆盖(docs/wave5-plan.md §2.2、docs/providers.md「模型元数据」)。
3
3
  *
4
- * - 缓存文件 `<dataDir>/models-dev.json`:`{ version, url, fetchedAt, etag?, providers }`,providers 是
5
- * `trimModelsDev()` 裁剪后的数据(约 2.3 MB,原始约 5 MB)。写入走同目录临时文件 + rename。
6
- * - **启动不联网**:`loadModelsDevIndex()` 只读缓存(按路径 + mtime 记忆,同一进程只解析一次)。
7
- * - 联网只在 `ama providers add|refresh`、`ama models discover`、`ama models refresh-catalog` 调
8
- * `refreshModelsDev()` 时发生:缓存 24 小时内不重拉(`force` 例外),带 `If-None-Match`;304 只刷新时间;
9
- * 失败时返回旧缓存与 warning。`AMA_MODELS_DEV_URL` 换数据源。
4
+ * - 内置快照随 bundle 携带(models-dev-snapshot.ts),**启动与运行都不联网**。
5
+ * - 覆盖文件 `<dataDir>/models-dev.json`:`{ version: 2, url, fetchedAt, providers }`,只有
6
+ * `ama models refresh` 会写(`refreshModelsDev()`,显式联网,按快照同一清单裁剪)。存在且
7
+ * fetchedAt 晚于快照才叠加;同 provider/model 以覆盖为准。旧版(version 1,全量 2 MB 缓存)忽略。
8
+ * - `loadModelsDevIndex()` 按覆盖文件路径 + mtime + 大小记忆,同一进程只解析一次。
9
+ * - `AMA_MODELS_DEV_URL` 换刷新的数据源。
10
10
  */
11
11
  import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
12
12
  import { dirname, join } from "node:path";
13
- import { ModelsDevIndex, trimModelsDev } from "./models-dev.js";
13
+ import { ModelsDevIndex as Index } from "./models-dev.js";
14
+ import { buildSnapshot, builtinSnapshot, builtinSnapshotIndex, diffSnapshots, mergeSnapshot, snapshotList, snapshotMeta, } from "./models-dev-snapshot.js";
14
15
  export const MODELS_DEV_URL = "https://models.dev/api.json";
15
16
  export const MODELS_DEV_URL_ENV = "AMA_MODELS_DEV_URL";
16
17
  export const MODELS_DEV_FILE = "models-dev.json";
17
- export const MODELS_DEV_TTL_MS = 24 * 60 * 60 * 1000;
18
- export const MODELS_DEV_TIMEOUT_MS = 30_000;
18
+ export const MODELS_DEV_TIMEOUT_MS = 60_000;
19
19
  export function modelsDevCachePath(dataDir) {
20
20
  return join(dataDir, MODELS_DEV_FILE);
21
21
  }
@@ -23,14 +23,16 @@ export function modelsDevUrl(env = process.env) {
23
23
  const custom = env[MODELS_DEV_URL_ENV]?.trim();
24
24
  return custom ? custom : MODELS_DEV_URL;
25
25
  }
26
- /** 读缓存;不存在或损坏返回 undefined(损坏不报错:下次拉取会覆盖)。 */
26
+ /** 读覆盖文件;不存在、旧版或损坏返回 undefined(损坏不报错:下次 refresh 会覆盖)。 */
27
27
  export function readModelsDevCache(dataDir) {
28
28
  try {
29
29
  const value = JSON.parse(readFileSync(modelsDevCachePath(dataDir), "utf8"));
30
30
  if (typeof value !== "object" || value === null)
31
31
  return undefined;
32
32
  const file = value;
33
- if (file.version !== 1 || typeof file.fetchedAt !== "string")
33
+ if (file.version !== 2 || typeof file.fetchedAt !== "string")
34
+ return undefined;
35
+ if (Number.isNaN(Date.parse(file.fetchedAt)))
34
36
  return undefined;
35
37
  if (typeof file.providers !== "object" || file.providers === null)
36
38
  return undefined;
@@ -48,8 +50,12 @@ export function writeModelsDevCache(dataDir, file) {
48
50
  renameSync(tmp, path);
49
51
  memo = undefined;
50
52
  }
53
+ /** 覆盖文件是否生效:fetchedAt 晚于内置快照。 */
54
+ export function overrideApplies(file) {
55
+ return Date.parse(file.fetchedAt) > Date.parse(snapshotMeta().fetchedAt);
56
+ }
51
57
  let memo;
52
- /** 只读缓存构造索引(不联网);没有缓存返回 undefined。 */
58
+ /** 快照 ⊕ 覆盖(零网络;没有覆盖文件时只做一次 stat)。 */
53
59
  export function loadModelsDevIndex(dataDir) {
54
60
  const path = modelsDevCachePath(dataDir);
55
61
  let key;
@@ -58,90 +64,98 @@ export function loadModelsDevIndex(dataDir) {
58
64
  key = `${path}\0${stat.mtimeMs}\0${stat.size}`;
59
65
  }
60
66
  catch {
61
- return undefined;
67
+ return builtinSnapshotIndex();
62
68
  }
63
69
  if (memo?.key === key)
64
70
  return memo.index;
65
71
  const file = readModelsDevCache(dataDir);
66
- const index = file !== undefined ? new ModelsDevIndex(file.providers) : undefined;
72
+ const index = file !== undefined && overrideApplies(file)
73
+ ? new Index(mergeSnapshot(builtinSnapshot(), file.providers))
74
+ : builtinSnapshotIndex();
67
75
  memo = { key, index };
68
76
  return index;
69
77
  }
70
- /** 按需拉取 models.dev 并更新缓存;失败时回落旧缓存(status `stale`)或无数据(`unavailable`)。 */
78
+ /** 当前索引的来源说明(`providers add` / `models discover` 输出用)。 */
79
+ export function describeModelsDev(dataDir) {
80
+ const index = loadModelsDevIndex(dataDir);
81
+ const file = readModelsDevCache(dataDir);
82
+ const size = `${index.providerCount} 家供应商、${index.modelCount} 个模型`;
83
+ const via = file !== undefined && overrideApplies(file)
84
+ ? `快照 ${snapshotMeta().fetchedAt} ⊕ 刷新 ${file.fetchedAt}`
85
+ : `快照 ${snapshotMeta().fetchedAt}`;
86
+ return `models.dev:${via}(${size})`;
87
+ }
88
+ /** 显式联网刷新(只被 `ama models refresh` 调用):拉 api.json、按清单裁剪、写用户级覆盖。 */
71
89
  export async function refreshModelsDev(options) {
72
90
  const url = modelsDevUrl(options.env);
73
91
  const now = options.now ?? Date.now;
74
- const cached = readModelsDevCache(options.dataDir);
75
- const cachedIndex = () => cached !== undefined ? new ModelsDevIndex(cached.providers) : undefined;
76
- const age = cached !== undefined ? now() - Date.parse(cached.fetchedAt) : Infinity;
77
- if (cached !== undefined &&
78
- cached.url === url &&
79
- !options.force &&
80
- age >= 0 &&
81
- age < (options.ttlMs ?? MODELS_DEV_TTL_MS)) {
82
- return {
83
- status: "fresh",
84
- index: cachedIndex(),
85
- fetchedAt: cached.fetchedAt,
86
- url,
87
- };
88
- }
89
- const doFetch = options.fetch ?? fetch;
92
+ const before = loadModelsDevIndex(options.dataDir);
93
+ const fail = (reason) => ({
94
+ status: "failed",
95
+ url,
96
+ index: before,
97
+ warning: `models.dev 刷新失败(${reason}),沿用现有数据`,
98
+ });
99
+ const list = snapshotList();
100
+ const unknown = (options.providers ?? []).filter((p) => !list.providers.includes(p));
101
+ if (unknown.length > 0)
102
+ return fail(`不在收录清单里:${unknown.join(", ")};可选 ${list.providers.join(", ")}`);
103
+ let fresh;
90
104
  try {
91
- const headers = { accept: "application/json" };
92
- if (cached?.etag !== undefined && cached.url === url)
93
- headers["if-none-match"] = cached.etag;
94
- const response = await doFetch(url, {
95
- headers,
105
+ const response = await (options.fetch ?? fetch)(url, {
106
+ headers: { accept: "application/json" },
96
107
  signal: AbortSignal.timeout(options.timeoutMs ?? MODELS_DEV_TIMEOUT_MS),
97
108
  });
98
- const fetchedAt = new Date(now()).toISOString();
99
- if (response.status === 304 && cached !== undefined) {
100
- writeModelsDevCache(options.dataDir, { ...cached, fetchedAt });
101
- return { status: "not-modified", index: cachedIndex(), fetchedAt, url };
102
- }
103
109
  if (!response.ok)
104
110
  throw new Error(`HTTP ${response.status}`);
105
- const providers = trimModelsDev(await response.json());
106
- const etag = response.headers.get("etag") ?? undefined;
107
- writeModelsDevCache(options.dataDir, {
108
- version: 1,
109
- url,
110
- fetchedAt,
111
- ...(etag !== undefined ? { etag } : {}),
112
- providers,
113
- });
114
- return { status: "updated", index: new ModelsDevIndex(providers), fetchedAt, url };
111
+ fresh = buildSnapshot(await response.json(), list, options.providers);
115
112
  }
116
113
  catch (error) {
117
- const reason = error instanceof Error ? error.message : String(error);
118
- if (cached !== undefined) {
119
- return {
120
- status: "stale",
121
- index: cachedIndex(),
122
- fetchedAt: cached.fetchedAt,
123
- url,
124
- warning: `models.dev 拉取失败(${reason}),沿用 ${cached.fetchedAt} 的缓存`,
125
- };
126
- }
127
- return {
128
- status: "unavailable",
129
- url,
130
- warning: `models.dev 拉取失败(${reason}),没有缓存可用`,
131
- };
114
+ return fail(error instanceof Error ? error.message : String(error));
132
115
  }
116
+ const fetchedAt = new Date(now()).toISOString();
117
+ const previous = readModelsDevCache(options.dataDir);
118
+ const kept = previous !== undefined && overrideApplies(previous) ? previous.providers : {};
119
+ writeModelsDevCache(options.dataDir, {
120
+ version: 2,
121
+ url,
122
+ fetchedAt,
123
+ providers: { ...kept, ...fresh },
124
+ });
125
+ const diff = diffSnapshots(before.data, fresh);
126
+ const changed = diff.added.length + diff.removed.length + diff.changed.length > 0;
127
+ return {
128
+ status: changed ? "updated" : "unchanged",
129
+ url,
130
+ fetchedAt,
131
+ diff,
132
+ index: loadModelsDevIndex(options.dataDir),
133
+ };
133
134
  }
134
- /** 一行状态说明(命令输出用)。 */
135
- export function describeRefresh(result) {
136
- const size = result.index
137
- ? `${result.index.providerCount} 家供应商、${result.index.modelCount} 个模型`
138
- : "无数据";
135
+ /** 刷新结果的说明(命令输出用):一行状态 + 新增 / 删除 / 变化清单。 */
136
+ export function describeRefresh(result, limit = 20) {
137
+ const size = `${result.index.providerCount} 家供应商、${result.index.modelCount} 个模型`;
139
138
  const text = {
140
- fresh: "缓存未过期",
141
- updated: "已更新",
142
- "not-modified": "未变化(304)",
143
- stale: "拉取失败,用旧缓存",
144
- unavailable: "不可用",
139
+ updated: "已刷新",
140
+ unchanged: "已刷新,无变化",
141
+ failed: "刷新失败",
145
142
  };
146
- return `models.dev:${text[result.status]}(${size}${result.fetchedAt ? `,${result.fetchedAt}` : ""})`;
143
+ const lines = [
144
+ `models.dev:${text[result.status]}(${size}${result.fetchedAt ? `,${result.fetchedAt}` : ""})`,
145
+ ];
146
+ const section = (title, items) => {
147
+ if (items.length === 0)
148
+ return;
149
+ lines.push(`${title}(${items.length}):`);
150
+ for (const item of items.slice(0, limit))
151
+ lines.push(` ${item}`);
152
+ if (items.length > limit)
153
+ lines.push(` …另 ${items.length - limit} 条`);
154
+ };
155
+ if (result.diff !== undefined) {
156
+ section("新增", result.diff.added);
157
+ section("上游删除(快照里的条目保留)", result.diff.removed);
158
+ section("变化", result.diff.changed);
159
+ }
160
+ return lines.join("\n");
147
161
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 由 scripts/update-models-dev.mjs 生成,勿手改(docs/providers.md「模型元数据」)。
3
+ *
4
+ * 数据来自 models.dev(https://models.dev/api.json,github.com/anomalyco/models.dev),MIT 许可:
5
+ * Copyright (c) 2025 models.dev。完整声明见 THIRD_PARTY_NOTICES.md。
6
+ */
7
+ export declare const MODELS_DEV_SOURCES: Readonly<Record<string, string>>;