@armadra/agent 0.0.0-stage → 0.3.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 (433) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +470 -2
  4. package/dist/agent/agent.d.ts +56 -0
  5. package/dist/agent/agent.js +129 -0
  6. package/dist/agent/loop.d.ts +51 -0
  7. package/dist/agent/loop.js +158 -0
  8. package/dist/agent/queue.d.ts +25 -0
  9. package/dist/agent/queue.js +55 -0
  10. package/dist/agent/retry.d.ts +22 -0
  11. package/dist/agent/retry.js +92 -0
  12. package/dist/agent/schema.d.ts +27 -0
  13. package/dist/agent/schema.js +186 -0
  14. package/dist/agent/session-cache.d.ts +100 -0
  15. package/dist/agent/session-cache.js +514 -0
  16. package/dist/agent/session-compaction.d.ts +49 -0
  17. package/dist/agent/session-compaction.js +251 -0
  18. package/dist/agent/session-core.d.ts +103 -0
  19. package/dist/agent/session-core.js +7 -0
  20. package/dist/agent/session-run.d.ts +55 -0
  21. package/dist/agent/session-run.js +246 -0
  22. package/dist/agent/session-state.d.ts +39 -0
  23. package/dist/agent/session-state.js +119 -0
  24. package/dist/agent/session-subagent.d.ts +54 -0
  25. package/dist/agent/session-subagent.js +157 -0
  26. package/dist/agent/session-sync.d.ts +20 -0
  27. package/dist/agent/session-sync.js +111 -0
  28. package/dist/agent/session-tools.d.ts +24 -0
  29. package/dist/agent/session-tools.js +277 -0
  30. package/dist/agent/session.d.ts +117 -0
  31. package/dist/agent/session.js +485 -0
  32. package/dist/agent/system-prompt.d.ts +49 -0
  33. package/dist/agent/system-prompt.js +121 -0
  34. package/dist/agent/tool-runner.d.ts +64 -0
  35. package/dist/agent/tool-runner.js +309 -0
  36. package/dist/agent/transform.d.ts +26 -0
  37. package/dist/agent/transform.js +171 -0
  38. package/dist/agent/types.d.ts +378 -0
  39. package/dist/agent/types.js +21 -0
  40. package/dist/ai/apis/anthropic-messages.d.ts +26 -0
  41. package/dist/ai/apis/anthropic-messages.js +214 -0
  42. package/dist/ai/apis/anthropic-request.d.ts +42 -0
  43. package/dist/ai/apis/anthropic-request.js +282 -0
  44. package/dist/ai/apis/api.d.ts +28 -0
  45. package/dist/ai/apis/api.js +102 -0
  46. package/dist/ai/apis/cache-params.d.ts +48 -0
  47. package/dist/ai/apis/cache-params.js +163 -0
  48. package/dist/ai/apis/google-generative-ai.d.ts +32 -0
  49. package/dist/ai/apis/google-generative-ai.js +249 -0
  50. package/dist/ai/apis/google-request.d.ts +40 -0
  51. package/dist/ai/apis/google-request.js +255 -0
  52. package/dist/ai/apis/openai-compat.d.ts +35 -0
  53. package/dist/ai/apis/openai-compat.js +142 -0
  54. package/dist/ai/apis/openai-completions.d.ts +28 -0
  55. package/dist/ai/apis/openai-completions.js +246 -0
  56. package/dist/ai/apis/openai-request.d.ts +35 -0
  57. package/dist/ai/apis/openai-request.js +303 -0
  58. package/dist/ai/apis/openai-responses-request.d.ts +52 -0
  59. package/dist/ai/apis/openai-responses-request.js +266 -0
  60. package/dist/ai/apis/openai-responses.d.ts +28 -0
  61. package/dist/ai/apis/openai-responses.js +304 -0
  62. package/dist/ai/apis/shared.d.ts +56 -0
  63. package/dist/ai/apis/shared.js +179 -0
  64. package/dist/ai/cache/economics.d.ts +24 -0
  65. package/dist/ai/cache/economics.js +69 -0
  66. package/dist/ai/cache/fingerprint.d.ts +17 -0
  67. package/dist/ai/cache/fingerprint.js +39 -0
  68. package/dist/ai/cache/miss.d.ts +49 -0
  69. package/dist/ai/cache/miss.js +96 -0
  70. package/dist/ai/cache/reporting.d.ts +39 -0
  71. package/dist/ai/cache/reporting.js +103 -0
  72. package/dist/ai/cache/types.d.ts +94 -0
  73. package/dist/ai/cache/types.js +19 -0
  74. package/dist/ai/cache/warmer.d.ts +94 -0
  75. package/dist/ai/cache/warmer.js +224 -0
  76. package/dist/ai/context.d.ts +35 -0
  77. package/dist/ai/context.js +114 -0
  78. package/dist/ai/cost.d.ts +21 -0
  79. package/dist/ai/cost.js +60 -0
  80. package/dist/ai/event-stream.d.ts +33 -0
  81. package/dist/ai/event-stream.js +101 -0
  82. package/dist/ai/fake/fake-provider.d.ts +68 -0
  83. package/dist/ai/fake/fake-provider.js +278 -0
  84. package/dist/ai/fake/fake-script.d.ts +69 -0
  85. package/dist/ai/fake/fake-script.js +142 -0
  86. package/dist/ai/http.d.ts +54 -0
  87. package/dist/ai/http.js +212 -0
  88. package/dist/ai/json-partial.d.ts +17 -0
  89. package/dist/ai/json-partial.js +243 -0
  90. package/dist/ai/overflow.d.ts +22 -0
  91. package/dist/ai/overflow.js +61 -0
  92. package/dist/ai/providers/auth.d.ts +71 -0
  93. package/dist/ai/providers/auth.js +202 -0
  94. package/dist/ai/providers/builtin.d.ts +23 -0
  95. package/dist/ai/providers/builtin.js +148 -0
  96. package/dist/ai/providers/catalog-data.d.ts +5 -0
  97. package/dist/ai/providers/catalog-data.js +19 -0
  98. package/dist/ai/providers/catalog.d.ts +28 -0
  99. package/dist/ai/providers/catalog.js +166 -0
  100. package/dist/ai/providers/channels.d.ts +51 -0
  101. package/dist/ai/providers/channels.js +96 -0
  102. package/dist/ai/providers/enrich.d.ts +34 -0
  103. package/dist/ai/providers/enrich.js +86 -0
  104. package/dist/ai/providers/models-dev-cache.d.ts +53 -0
  105. package/dist/ai/providers/models-dev-cache.js +147 -0
  106. package/dist/ai/providers/models-dev.d.ts +99 -0
  107. package/dist/ai/providers/models-dev.js +315 -0
  108. package/dist/ai/providers/registry.d.ts +114 -0
  109. package/dist/ai/providers/registry.js +458 -0
  110. package/dist/ai/sse.d.ts +32 -0
  111. package/dist/ai/sse.js +140 -0
  112. package/dist/ai/thinking.d.ts +42 -0
  113. package/dist/ai/thinking.js +101 -0
  114. package/dist/ai/types.d.ts +448 -0
  115. package/dist/ai/types.js +19 -0
  116. package/dist/bundle/ama-sandbox.cjs +324 -0
  117. package/dist/bundle/ama.cjs +27837 -0
  118. package/dist/bundle.d.ts +9 -0
  119. package/dist/bundle.js +15 -0
  120. package/dist/cli/args.d.ts +85 -0
  121. package/dist/cli/args.js +408 -0
  122. package/dist/cli/bootstrap.d.ts +18 -0
  123. package/dist/cli/bootstrap.js +399 -0
  124. package/dist/cli/compose-providers.d.ts +37 -0
  125. package/dist/cli/compose-providers.js +107 -0
  126. package/dist/cli/compose-session.d.ts +72 -0
  127. package/dist/cli/compose-session.js +370 -0
  128. package/dist/cli/compose-store.d.ts +24 -0
  129. package/dist/cli/compose-store.js +125 -0
  130. package/dist/cli/compose.d.ts +108 -0
  131. package/dist/cli/compose.js +221 -0
  132. package/dist/cli/default-model.d.ts +16 -0
  133. package/dist/cli/default-model.js +24 -0
  134. package/dist/cli/deps.d.ts +207 -0
  135. package/dist/cli/deps.js +12 -0
  136. package/dist/cli/exit-codes.d.ts +29 -0
  137. package/dist/cli/exit-codes.js +40 -0
  138. package/dist/cli/main.d.ts +27 -0
  139. package/dist/cli/main.js +177 -0
  140. package/dist/cli/runtime.d.ts +90 -0
  141. package/dist/cli/runtime.js +8 -0
  142. package/dist/cli/startup-screen.d.ts +15 -0
  143. package/dist/cli/startup-screen.js +56 -0
  144. package/dist/cli/startup-steps.d.ts +35 -0
  145. package/dist/cli/startup-steps.js +202 -0
  146. package/dist/cli/subcommands/auth.d.ts +12 -0
  147. package/dist/cli/subcommands/auth.js +91 -0
  148. package/dist/cli/subcommands/config.d.ts +53 -0
  149. package/dist/cli/subcommands/config.js +277 -0
  150. package/dist/cli/subcommands/context.d.ts +21 -0
  151. package/dist/cli/subcommands/context.js +42 -0
  152. package/dist/cli/subcommands/doctor.d.ts +11 -0
  153. package/dist/cli/subcommands/doctor.js +246 -0
  154. package/dist/cli/subcommands/init.d.ts +6 -0
  155. package/dist/cli/subcommands/init.js +22 -0
  156. package/dist/cli/subcommands/model-meta.d.ts +16 -0
  157. package/dist/cli/subcommands/model-meta.js +54 -0
  158. package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
  159. package/dist/cli/subcommands/models-cache-probe.js +190 -0
  160. package/dist/cli/subcommands/models-discover.d.ts +46 -0
  161. package/dist/cli/subcommands/models-discover.js +232 -0
  162. package/dist/cli/subcommands/models.d.ts +40 -0
  163. package/dist/cli/subcommands/models.js +152 -0
  164. package/dist/cli/subcommands/providers-list.d.ts +8 -0
  165. package/dist/cli/subcommands/providers-list.js +97 -0
  166. package/dist/cli/subcommands/providers-plan.d.ts +79 -0
  167. package/dist/cli/subcommands/providers-plan.js +215 -0
  168. package/dist/cli/subcommands/providers.d.ts +27 -0
  169. package/dist/cli/subcommands/providers.js +433 -0
  170. package/dist/cli/subcommands/sessions.d.ts +11 -0
  171. package/dist/cli/subcommands/sessions.js +100 -0
  172. package/dist/codemode/capability.d.ts +37 -0
  173. package/dist/codemode/capability.js +55 -0
  174. package/dist/codemode/declarations.d.ts +44 -0
  175. package/dist/codemode/declarations.js +111 -0
  176. package/dist/codemode/host-side.d.ts +59 -0
  177. package/dist/codemode/host-side.js +253 -0
  178. package/dist/codemode/modes.d.ts +23 -0
  179. package/dist/codemode/modes.js +40 -0
  180. package/dist/codemode/protocol.d.ts +91 -0
  181. package/dist/codemode/protocol.js +114 -0
  182. package/dist/codemode/sandbox-entry.d.ts +25 -0
  183. package/dist/codemode/sandbox-entry.js +323 -0
  184. package/dist/codemode/store.d.ts +26 -0
  185. package/dist/codemode/store.js +53 -0
  186. package/dist/codemode/tool.d.ts +105 -0
  187. package/dist/codemode/tool.js +283 -0
  188. package/dist/compaction/branch-summary.d.ts +30 -0
  189. package/dist/compaction/branch-summary.js +79 -0
  190. package/dist/compaction/breaker.d.ts +32 -0
  191. package/dist/compaction/breaker.js +65 -0
  192. package/dist/compaction/cut-point.d.ts +23 -0
  193. package/dist/compaction/cut-point.js +70 -0
  194. package/dist/compaction/estimate.d.ts +30 -0
  195. package/dist/compaction/estimate.js +114 -0
  196. package/dist/compaction/prune-tier.d.ts +31 -0
  197. package/dist/compaction/prune-tier.js +83 -0
  198. package/dist/compaction/serialize.d.ts +20 -0
  199. package/dist/compaction/serialize.js +115 -0
  200. package/dist/compaction/summarize-tier.d.ts +111 -0
  201. package/dist/compaction/summarize-tier.js +305 -0
  202. package/dist/config/auth-file.d.ts +39 -0
  203. package/dist/config/auth-file.js +92 -0
  204. package/dist/config/context-files.d.ts +30 -0
  205. package/dist/config/context-files.js +77 -0
  206. package/dist/config/init.d.ts +30 -0
  207. package/dist/config/init.js +92 -0
  208. package/dist/config/json-schema.d.ts +14 -0
  209. package/dist/config/json-schema.js +182 -0
  210. package/dist/config/load.d.ts +45 -0
  211. package/dist/config/load.js +97 -0
  212. package/dist/config/merge.d.ts +73 -0
  213. package/dist/config/merge.js +248 -0
  214. package/dist/config/paths.d.ts +38 -0
  215. package/dist/config/paths.js +108 -0
  216. package/dist/config/profile.d.ts +27 -0
  217. package/dist/config/profile.js +65 -0
  218. package/dist/config/schema.d.ts +34 -0
  219. package/dist/config/schema.js +476 -0
  220. package/dist/config/trust.d.ts +42 -0
  221. package/dist/config/trust.js +122 -0
  222. package/dist/config/types.d.ts +190 -0
  223. package/dist/config/types.js +29 -0
  224. package/dist/config/write.d.ts +10 -0
  225. package/dist/config/write.js +25 -0
  226. package/dist/errors.d.ts +28 -0
  227. package/dist/errors.js +30 -0
  228. package/dist/hooks/config.d.ts +49 -0
  229. package/dist/hooks/config.js +102 -0
  230. package/dist/hooks/dispatcher.d.ts +41 -0
  231. package/dist/hooks/dispatcher.js +104 -0
  232. package/dist/hooks/matcher.d.ts +22 -0
  233. package/dist/hooks/matcher.js +108 -0
  234. package/dist/hooks/protocol.d.ts +33 -0
  235. package/dist/hooks/protocol.js +240 -0
  236. package/dist/hooks/runner.d.ts +30 -0
  237. package/dist/hooks/runner.js +143 -0
  238. package/dist/hooks/types.d.ts +147 -0
  239. package/dist/hooks/types.js +20 -0
  240. package/dist/host/api-impl.d.ts +75 -0
  241. package/dist/host/api-impl.js +173 -0
  242. package/dist/host/loader.d.ts +31 -0
  243. package/dist/host/loader.js +147 -0
  244. package/dist/host/types.d.ts +169 -0
  245. package/dist/host/types.js +14 -0
  246. package/dist/host.d.ts +5 -0
  247. package/dist/host.js +4 -0
  248. package/dist/index.d.ts +41 -0
  249. package/dist/index.js +28 -0
  250. package/dist/modes/commands-core.d.ts +44 -0
  251. package/dist/modes/commands-core.js +171 -0
  252. package/dist/modes/image-input.d.ts +27 -0
  253. package/dist/modes/image-input.js +78 -0
  254. package/dist/modes/interactive/approval-dialog.d.ts +48 -0
  255. package/dist/modes/interactive/approval-dialog.js +202 -0
  256. package/dist/modes/interactive/commands.d.ts +41 -0
  257. package/dist/modes/interactive/commands.js +201 -0
  258. package/dist/modes/interactive/completion.d.ts +53 -0
  259. package/dist/modes/interactive/completion.js +157 -0
  260. package/dist/modes/interactive/interactive-mode.d.ts +46 -0
  261. package/dist/modes/interactive/interactive-mode.js +491 -0
  262. package/dist/modes/interactive/key-dispatch.d.ts +29 -0
  263. package/dist/modes/interactive/key-dispatch.js +107 -0
  264. package/dist/modes/interactive/line/line-editor.d.ts +50 -0
  265. package/dist/modes/interactive/line/line-editor.js +232 -0
  266. package/dist/modes/interactive/line/line-mode.d.ts +20 -0
  267. package/dist/modes/interactive/line/line-mode.js +218 -0
  268. package/dist/modes/interactive/line/line-render.d.ts +35 -0
  269. package/dist/modes/interactive/line/line-render.js +171 -0
  270. package/dist/modes/interactive/line/paste-state.d.ts +26 -0
  271. package/dist/modes/interactive/line/paste-state.js +72 -0
  272. package/dist/modes/interactive/message-view.d.ts +77 -0
  273. package/dist/modes/interactive/message-view.js +269 -0
  274. package/dist/modes/interactive/pickers.d.ts +42 -0
  275. package/dist/modes/interactive/pickers.js +115 -0
  276. package/dist/modes/interactive/startup-ui.d.ts +33 -0
  277. package/dist/modes/interactive/startup-ui.js +268 -0
  278. package/dist/modes/interactive/status-bar.d.ts +48 -0
  279. package/dist/modes/interactive/status-bar.js +139 -0
  280. package/dist/modes/interactive/tool-view.d.ts +95 -0
  281. package/dist/modes/interactive/tool-view.js +344 -0
  282. package/dist/modes/print/json-event.d.ts +15 -0
  283. package/dist/modes/print/json-event.js +39 -0
  284. package/dist/modes/print/print-mode.d.ts +17 -0
  285. package/dist/modes/print/print-mode.js +107 -0
  286. package/dist/modes/rpc/commands.d.ts +40 -0
  287. package/dist/modes/rpc/commands.js +263 -0
  288. package/dist/modes/rpc/jsonl.d.ts +16 -0
  289. package/dist/modes/rpc/jsonl.js +55 -0
  290. package/dist/modes/rpc/rpc-mode.d.ts +22 -0
  291. package/dist/modes/rpc/rpc-mode.js +153 -0
  292. package/dist/modes/session-report.d.ts +55 -0
  293. package/dist/modes/session-report.js +278 -0
  294. package/dist/modes/shared.d.ts +13 -0
  295. package/dist/modes/shared.js +52 -0
  296. package/dist/modes/startup-ui-text.d.ts +16 -0
  297. package/dist/modes/startup-ui-text.js +103 -0
  298. package/dist/permissions/broker.d.ts +47 -0
  299. package/dist/permissions/broker.js +107 -0
  300. package/dist/permissions/dangerous.d.ts +47 -0
  301. package/dist/permissions/dangerous.js +370 -0
  302. package/dist/permissions/pipeline.d.ts +51 -0
  303. package/dist/permissions/pipeline.js +181 -0
  304. package/dist/permissions/preview.d.ts +43 -0
  305. package/dist/permissions/preview.js +463 -0
  306. package/dist/permissions/rules.d.ts +58 -0
  307. package/dist/permissions/rules.js +249 -0
  308. package/dist/permissions/types.d.ts +100 -0
  309. package/dist/permissions/types.js +18 -0
  310. package/dist/rpc.d.ts +173 -0
  311. package/dist/rpc.js +13 -0
  312. package/dist/sdk.d.ts +98 -0
  313. package/dist/sdk.js +195 -0
  314. package/dist/session/manager.d.ts +70 -0
  315. package/dist/session/manager.js +278 -0
  316. package/dist/session/migrate.d.ts +19 -0
  317. package/dist/session/migrate.js +53 -0
  318. package/dist/session/projection.d.ts +50 -0
  319. package/dist/session/projection.js +184 -0
  320. package/dist/session/store.d.ts +47 -0
  321. package/dist/session/store.js +210 -0
  322. package/dist/session/tree.d.ts +21 -0
  323. package/dist/session/tree.js +88 -0
  324. package/dist/session/types.d.ts +201 -0
  325. package/dist/session/types.js +18 -0
  326. package/dist/skills/discover.d.ts +60 -0
  327. package/dist/skills/discover.js +160 -0
  328. package/dist/skills/expand.d.ts +35 -0
  329. package/dist/skills/expand.js +46 -0
  330. package/dist/skills/frontmatter.d.ts +27 -0
  331. package/dist/skills/frontmatter.js +157 -0
  332. package/dist/skills/index-prompt.d.ts +13 -0
  333. package/dist/skills/index-prompt.js +42 -0
  334. package/dist/skills/templates.d.ts +52 -0
  335. package/dist/skills/templates.js +177 -0
  336. package/dist/tools/bash.d.ts +49 -0
  337. package/dist/tools/bash.js +222 -0
  338. package/dist/tools/edit-fuzzy.d.ts +36 -0
  339. package/dist/tools/edit-fuzzy.js +129 -0
  340. package/dist/tools/edit.d.ts +38 -0
  341. package/dist/tools/edit.js +241 -0
  342. package/dist/tools/file-mutex.d.ts +8 -0
  343. package/dist/tools/file-mutex.js +29 -0
  344. package/dist/tools/glob.d.ts +44 -0
  345. package/dist/tools/glob.js +217 -0
  346. package/dist/tools/grep.d.ts +39 -0
  347. package/dist/tools/grep.js +179 -0
  348. package/dist/tools/ignore.d.ts +40 -0
  349. package/dist/tools/ignore.js +174 -0
  350. package/dist/tools/image-file.d.ts +31 -0
  351. package/dist/tools/image-file.js +114 -0
  352. package/dist/tools/ls.d.ts +21 -0
  353. package/dist/tools/ls.js +96 -0
  354. package/dist/tools/output-accumulator.d.ts +48 -0
  355. package/dist/tools/output-accumulator.js +106 -0
  356. package/dist/tools/paths.d.ts +20 -0
  357. package/dist/tools/paths.js +58 -0
  358. package/dist/tools/presets.d.ts +71 -0
  359. package/dist/tools/presets.js +128 -0
  360. package/dist/tools/process-tree.d.ts +35 -0
  361. package/dist/tools/process-tree.js +125 -0
  362. package/dist/tools/read.d.ts +24 -0
  363. package/dist/tools/read.js +132 -0
  364. package/dist/tools/registry.d.ts +43 -0
  365. package/dist/tools/registry.js +106 -0
  366. package/dist/tools/shell.d.ts +38 -0
  367. package/dist/tools/shell.js +91 -0
  368. package/dist/tools/task.d.ts +29 -0
  369. package/dist/tools/task.js +107 -0
  370. package/dist/tools/todo.d.ts +28 -0
  371. package/dist/tools/todo.js +107 -0
  372. package/dist/tools/truncate.d.ts +44 -0
  373. package/dist/tools/truncate.js +175 -0
  374. package/dist/tools/types.d.ts +135 -0
  375. package/dist/tools/types.js +19 -0
  376. package/dist/tools/write.d.ts +20 -0
  377. package/dist/tools/write.js +84 -0
  378. package/dist/tui/ansi.d.ts +29 -0
  379. package/dist/tui/ansi.js +420 -0
  380. package/dist/tui/component.d.ts +38 -0
  381. package/dist/tui/component.js +13 -0
  382. package/dist/tui/components/box.d.ts +26 -0
  383. package/dist/tui/components/box.js +65 -0
  384. package/dist/tui/components/container.d.ts +14 -0
  385. package/dist/tui/components/container.js +39 -0
  386. package/dist/tui/components/editor-buffer.d.ts +67 -0
  387. package/dist/tui/components/editor-buffer.js +300 -0
  388. package/dist/tui/components/editor-paste.d.ts +29 -0
  389. package/dist/tui/components/editor-paste.js +64 -0
  390. package/dist/tui/components/editor.d.ts +114 -0
  391. package/dist/tui/components/editor.js +530 -0
  392. package/dist/tui/components/key-value.d.ts +29 -0
  393. package/dist/tui/components/key-value.js +48 -0
  394. package/dist/tui/components/loader.d.ts +35 -0
  395. package/dist/tui/components/loader.js +71 -0
  396. package/dist/tui/components/markdown.d.ts +90 -0
  397. package/dist/tui/components/markdown.js +462 -0
  398. package/dist/tui/components/meter.d.ts +29 -0
  399. package/dist/tui/components/meter.js +47 -0
  400. package/dist/tui/components/overlay.d.ts +24 -0
  401. package/dist/tui/components/overlay.js +52 -0
  402. package/dist/tui/components/select-list.d.ts +57 -0
  403. package/dist/tui/components/select-list.js +179 -0
  404. package/dist/tui/components/spacer.d.ts +11 -0
  405. package/dist/tui/components/spacer.js +16 -0
  406. package/dist/tui/components/text.d.ts +30 -0
  407. package/dist/tui/components/text.js +65 -0
  408. package/dist/tui/keybindings.d.ts +66 -0
  409. package/dist/tui/keybindings.js +120 -0
  410. package/dist/tui/keys.d.ts +38 -0
  411. package/dist/tui/keys.js +275 -0
  412. package/dist/tui/stdin-buffer.d.ts +41 -0
  413. package/dist/tui/stdin-buffer.js +206 -0
  414. package/dist/tui/terminal.d.ts +79 -0
  415. package/dist/tui/terminal.js +153 -0
  416. package/dist/tui/theme.d.ts +31 -0
  417. package/dist/tui/theme.js +199 -0
  418. package/dist/tui/tui.d.ts +103 -0
  419. package/dist/tui/tui.js +363 -0
  420. package/dist/tui/vt-screen.d.ts +46 -0
  421. package/dist/tui/vt-screen.js +251 -0
  422. package/dist/tui.d.ts +30 -0
  423. package/dist/tui.js +31 -0
  424. package/dist/version.d.ts +8 -0
  425. package/dist/version.js +24 -0
  426. package/docs/codemode.md +102 -0
  427. package/docs/hooks.md +196 -0
  428. package/docs/host-api.md +161 -0
  429. package/docs/providers.md +531 -0
  430. package/docs/rpc.md +253 -0
  431. package/docs/session-format.md +119 -0
  432. package/docs/tui.md +170 -0
  433. package/package.json +101 -4
@@ -0,0 +1,215 @@
1
+ /**
2
+ * `ama providers add|refresh` 的纯计算部分(docs/providers.md「一键接入」):候选渠道、模型列表的
3
+ * 协议提示、探测结果 → 模型渠道、渠道收敛、追加式合并进配置、表格。
4
+ *
5
+ * 不读写文件、不联网(`fetchModelList` 除外,它只是一次 GET);命令流程在 providers.ts。
6
+ */
7
+ import { apiShortName } from "../../ai/providers/channels.js";
8
+ import { matchLabel, } from "../../ai/providers/models-dev.js";
9
+ import { CHANNEL_NAME_PATTERN } from "../../config/types.js";
10
+ import { padToWidth, visibleWidth } from "../../tui/ansi.js";
11
+ import { UsageError } from "../args.js";
12
+ import { compactTokens } from "./model-meta.js";
13
+ export const PROBE_APIS = [
14
+ "openai-completions",
15
+ "openai-responses",
16
+ "anthropic-messages",
17
+ ];
18
+ export const DEFAULT_PREFER = ["chat", "responses", "messages"];
19
+ const API_CHOICES = [...PROBE_APIS, "google-generative-ai"];
20
+ /** `--channel name=api@baseUrl`。 */
21
+ export function parseChannelSpec(spec) {
22
+ const m = /^([^=]+)=([^@]+)@(.+)$/.exec(spec.trim());
23
+ if (!m)
24
+ throw new UsageError(`--channel 应为 <名字>=<协议>@<地址>:${spec}`);
25
+ const [, name = "", api = "", baseUrl = ""] = m;
26
+ if (!CHANNEL_NAME_PATTERN.test(name))
27
+ throw new UsageError(`渠道名不合法:${name}`);
28
+ if (!API_CHOICES.includes(api))
29
+ throw new UsageError(`--channel 的协议应为 ${API_CHOICES.join(" | ")}(收到 ${api})`);
30
+ if (!/^https?:\/\//.test(baseUrl))
31
+ throw new UsageError(`--channel 的地址应为 http(s) URL:${baseUrl}`);
32
+ return { name, api: api, baseUrl: baseUrl.replace(/\/+$/, "") };
33
+ }
34
+ /** 从 `--base-url` 推出候选渠道;`api` 给定(非 auto)时只留对应的一个。 */
35
+ export function defaultChannels(baseUrl, api) {
36
+ const base = baseUrl.replace(/\/+$/, "");
37
+ const root = base.replace(/\/v1$/, "");
38
+ const all = [
39
+ { name: "chat", api: "openai-completions", baseUrl: base },
40
+ { name: "responses", api: "openai-responses", baseUrl: base },
41
+ { name: "messages", api: "anthropic-messages", baseUrl: root },
42
+ ];
43
+ if (api === undefined || api === "auto")
44
+ return all;
45
+ if (!API_CHOICES.includes(api))
46
+ throw new UsageError(`--api 的取值应为 ${[...PROBE_APIS, "auto"].join(" | ")}(收到 ${api})`);
47
+ const one = all.find((c) => c.api === api);
48
+ return [one ?? { name: apiShortName(api), api: api, baseUrl: base }];
49
+ }
50
+ const ENDPOINT_APIS = {
51
+ openai: "openai-completions",
52
+ "openai-response": "openai-responses",
53
+ "openai-responses": "openai-responses",
54
+ responses: "openai-responses",
55
+ anthropic: "anthropic-messages",
56
+ gemini: "google-generative-ai",
57
+ };
58
+ /**
59
+ * 模型列表的提示 → 候选渠道名。没有提示(或提示里没有认识的协议)返回 undefined;有提示但候选渠道
60
+ * 一个都不支持返回 [](该模型在这些渠道上不可用)。
61
+ */
62
+ export function hintedChannels(listed, candidates) {
63
+ if (listed.endpoints === undefined || listed.endpoints.length === 0)
64
+ return undefined;
65
+ const apis = new Set(listed.endpoints.map((e) => ENDPOINT_APIS[e.toLowerCase()]).filter((a) => a !== undefined));
66
+ if (apis.size === 0)
67
+ return undefined;
68
+ return candidates.filter((c) => apis.has(c.api)).map((c) => c.name);
69
+ }
70
+ /** 按 `prefer` 排序(不在 prefer 里的保持原顺序排在后面)。 */
71
+ export function orderChannels(names, prefer) {
72
+ const rank = (n) => {
73
+ const i = prefer.indexOf(n);
74
+ return i < 0 ? prefer.length : i;
75
+ };
76
+ return [...new Set(names)].sort((a, b) => rank(a) - rank(b));
77
+ }
78
+ /** 解析 `GET /models` 的响应体。 */
79
+ export function parseModelList(body) {
80
+ const data = body?.data;
81
+ if (!Array.isArray(data))
82
+ return [];
83
+ const seen = new Set();
84
+ const out = [];
85
+ for (const item of data) {
86
+ const id = item?.id;
87
+ if (typeof id !== "string" || id === "" || seen.has(id))
88
+ continue;
89
+ seen.add(id);
90
+ const endpoints = item.supported_endpoint_types;
91
+ const listed = { id };
92
+ if (Array.isArray(endpoints))
93
+ listed.endpoints = endpoints.filter((e) => typeof e === "string");
94
+ out.push(listed);
95
+ }
96
+ return out;
97
+ }
98
+ /** 列模型用的渠道与 URL:第一个 OpenAI 系渠道 `{baseUrl}/models`;只有 Messages 时 `/v1/models`。 */
99
+ export function listingTarget(candidates) {
100
+ const openai = candidates.find((c) => c.api === "openai-completions" || c.api === "openai-responses");
101
+ const channel = openai ?? candidates[0];
102
+ if (channel === undefined)
103
+ return undefined;
104
+ const base = channel.baseUrl.replace(/\/+$/, "");
105
+ const url = channel.api === "anthropic-messages" && !/\/v1$/.test(base)
106
+ ? `${base}/v1/models`
107
+ : `${base}/models`;
108
+ return { channel, url };
109
+ }
110
+ /** 要探测的模型:`--probe-models` 给出的(保持顺序),否则按 id 字母序(不分大小写)取前 limit 个。 */
111
+ export function probeSelection(ids, limit, only) {
112
+ if (only !== undefined)
113
+ return only.filter((id) => ids.includes(id));
114
+ return [...ids].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).slice(0, limit);
115
+ }
116
+ /**
117
+ * 按请求上限截断:每个模型要试的渠道数相加超过 `max` 时丢掉后面的模型。返回保留的模型与请求数。
118
+ */
119
+ export function capRequests(selected, channelsFor, max) {
120
+ const ids = [];
121
+ let requests = 0;
122
+ for (const id of selected) {
123
+ const n = channelsFor(id).length;
124
+ if (requests + n > max)
125
+ break;
126
+ ids.push(id);
127
+ requests += n;
128
+ }
129
+ return { ids, requests, dropped: selected.length - ids.length };
130
+ }
131
+ /**
132
+ * 合并进已有的供应商配置(追加式):已有渠道定义与模型条目一字不改,只加新渠道与新模型;
133
+ * 新模型只写 `id` 与 `channels`。返回新的配置与新增数。
134
+ */
135
+ export function mergeProvider(existing, input) {
136
+ const config = structuredClone(existing ?? {});
137
+ if (input.name !== undefined && config.name === undefined)
138
+ config.name = input.name;
139
+ if (input.apiKey !== undefined && config.apiKey === undefined)
140
+ config.apiKey = input.apiKey;
141
+ const channels = config.channels ?? {};
142
+ const used = new Set(input.models.flatMap((m) => m.channels));
143
+ const addedChannels = [];
144
+ for (const c of input.channels) {
145
+ if (channels[c.name] !== undefined || !used.has(c.name))
146
+ continue;
147
+ channels[c.name] = { api: c.api, baseUrl: c.baseUrl };
148
+ addedChannels.push(c.name);
149
+ }
150
+ config.channels = channels;
151
+ if (config.defaultChannel === undefined || channels[config.defaultChannel] === undefined) {
152
+ const first = orderChannels(Object.keys(channels), input.prefer)[0];
153
+ if (first !== undefined)
154
+ config.defaultChannel = first;
155
+ }
156
+ const models = config.models ?? [];
157
+ const known = new Set(models.map((m) => m.id));
158
+ const addedModels = [];
159
+ for (const m of input.models) {
160
+ if (known.has(m.id) || m.channels.length === 0)
161
+ continue;
162
+ models.push({ id: m.id, channels: [...m.channels] });
163
+ known.add(m.id);
164
+ addedModels.push(m.id);
165
+ }
166
+ config.models = models;
167
+ return { config, addedChannels, addedModels };
168
+ }
169
+ const STATUS_TEXT = {
170
+ ok: "探测通过",
171
+ unprobed: "未探测",
172
+ failed: "探测失败,不写入",
173
+ "no-tools": "不支持工具调用,不写入",
174
+ "no-channel": "候选渠道都不支持,不写入",
175
+ existing: "已存在,未改动",
176
+ };
177
+ function cost(fields) {
178
+ return fields.cost !== undefined ? `$${fields.cost.input}/${fields.cost.output}` : "—";
179
+ }
180
+ function yesNo(value) {
181
+ return value === undefined ? "?" : value ? "是" : "否";
182
+ }
183
+ /** 表格:id、渠道、上下文、输出、图像、推理、工具、价格($/M 入/出)、状态、models.dev 匹配。 */
184
+ export function renderTable(plans) {
185
+ const head = [
186
+ "模型",
187
+ "渠道",
188
+ "上下文",
189
+ "输出",
190
+ "图像",
191
+ "推理",
192
+ "工具",
193
+ "价格$/M",
194
+ "状态",
195
+ "models.dev",
196
+ ];
197
+ const rows = plans.map((p) => [
198
+ p.id,
199
+ p.channels.length > 0 ? p.channels.join(",") : "—",
200
+ compactTokens(p.fields.contextWindow),
201
+ compactTokens(p.fields.maxTokens),
202
+ p.fields.input === undefined ? "?" : p.fields.input.includes("image") ? "是" : "否",
203
+ yesNo(p.fields.reasoning),
204
+ yesNo(p.fields.toolCall),
205
+ cost(p.fields),
206
+ STATUS_TEXT[p.status],
207
+ matchLabel(p.match),
208
+ ]);
209
+ const widths = head.map((h, i) => Math.max(visibleWidth(h), ...rows.map((r) => visibleWidth(r[i] ?? ""))));
210
+ const line = (cells) => cells
211
+ .map((cell, i) => (i === cells.length - 1 ? cell : padToWidth(cell, widths[i] ?? 0)))
212
+ .join(" ")
213
+ .trimEnd();
214
+ return [line(head), ...rows.map(line)].join("\n") + "\n";
215
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `ama providers add|list|channels|remove|refresh`(docs/providers.md「一键接入」「渠道」)。
3
+ *
4
+ * - add:只给 baseUrl 与 key,列出中转的模型(`GET /models`)、用 models.dev 补元数据、可选逐渠道
5
+ * 探测,把渠道与模型追加进用户级 config.json(先备份);key 从 stdin 读(不回显)存 auth.json,
6
+ * 或 `--key-env VAR` 写 `"apiKey": "$VAR"`。对已存在的供应商只追加,不改已有条目。
7
+ * - refresh:重拉 `/models` 与 models.dev,只追加新模型;已下架的只提示。
8
+ * - list / channels:供应商 → 渠道 → 模型数与 key 来源(从不显示 key)。remove:删配置与 auth.json 条目。
9
+ *
10
+ * 计费:`--probe` 每模型每渠道一次最小请求;执行前打印预估,TTY 问 y/N,非 TTY 必须 `--yes`。
11
+ */
12
+ import type { AmaConfig } from "../../config/types.js";
13
+ import type { CliIo, RuntimeDeps } from "../deps.js";
14
+ import { type UserLevel } from "./context.js";
15
+ export declare const DEFAULT_MAX_REQUESTS = 60;
16
+ export declare const DEFAULT_LIMIT = 30;
17
+ export declare const PROVIDERS_USAGE = "\u7528\u6CD5\uFF1Aama providers add <id> --base-url <url> [--channel <\u540D\u5B57>=<\u534F\u8BAE>@<\u5730\u5740> \u2026]\n [--api openai-completions|openai-responses|anthropic-messages|auto]\n [--key-env <VAR>] [--probe] [--limit N] [--probe-models a,b,\u2026]\n [--max-requests N] [--prefer chat,responses,messages]\n [--include-no-tools] [--yes]\n ama providers list\n ama providers channels <id>\n ama providers remove <id>\n ama providers refresh <id> [--probe] [--limit N] [--probe-models a,b,\u2026] [--max-requests N] [--yes]\n";
18
+ export interface Ctx {
19
+ io: CliIo;
20
+ deps: Pick<RuntimeDeps, "providers">;
21
+ level: UserLevel;
22
+ values: ReadonlyMap<string, string>;
23
+ flags: ReadonlySet<string>;
24
+ channelSpecs: string[];
25
+ }
26
+ export declare function userConfig(level: UserLevel): AmaConfig;
27
+ export declare function runProviders(argv: readonly string[], io: CliIo, deps: Pick<RuntimeDeps, "providers"> | undefined): Promise<number>;
@@ -0,0 +1,433 @@
1
+ /**
2
+ * `ama providers add|list|channels|remove|refresh`(docs/providers.md「一键接入」「渠道」)。
3
+ *
4
+ * - add:只给 baseUrl 与 key,列出中转的模型(`GET /models`)、用 models.dev 补元数据、可选逐渠道
5
+ * 探测,把渠道与模型追加进用户级 config.json(先备份);key 从 stdin 读(不回显)存 auth.json,
6
+ * 或 `--key-env VAR` 写 `"apiKey": "$VAR"`。对已存在的供应商只追加,不改已有条目。
7
+ * - refresh:重拉 `/models` 与 models.dev,只追加新模型;已下架的只提示。
8
+ * - list / channels:供应商 → 渠道 → 模型数与 key 来源(从不显示 key)。remove:删配置与 auth.json 条目。
9
+ *
10
+ * 计费:`--probe` 每模型每渠道一次最小请求;执行前打印预估,TTY 问 y/N,非 TTY 必须 `--yes`。
11
+ */
12
+ import { existsSync } from "node:fs";
13
+ import { createInterface } from "node:readline/promises";
14
+ import { withCustomDefaults } from "../../ai/providers/catalog.js";
15
+ import { describeRefresh, refreshModelsDev } from "../../ai/providers/models-dev-cache.js";
16
+ import { modelsDevFields } from "../../ai/providers/models-dev.js";
17
+ import { materializeModel } from "../../ai/providers/registry.js";
18
+ import { PROVIDER_ID_PATTERN, setAuthKey } from "../../config/auth-file.js";
19
+ import { loadConfigFile } from "../../config/load.js";
20
+ import { CONFIG_FILE_VERSION } from "../../config/types.js";
21
+ import { writeConfigFile } from "../../config/write.js";
22
+ import { parseSubArgs, UsageError } from "../args.js";
23
+ import { ExitCode } from "../exit-codes.js";
24
+ import { extractKey } from "./auth.js";
25
+ import { listChannels, listProviders, removeProvider } from "./providers-list.js";
26
+ import { buildRegistry, loadUserLevel } from "./context.js";
27
+ import { attempt, DISCOVER_TIMEOUT_MS, FATAL_STATUS } from "./models-discover.js";
28
+ import { DEFAULT_PREFER, capRequests, defaultChannels, hintedChannels, listingTarget, mergeProvider, orderChannels, parseChannelSpec, parseModelList, probeSelection, renderTable, } from "./providers-plan.js";
29
+ export const DEFAULT_MAX_REQUESTS = 60;
30
+ export const DEFAULT_LIMIT = 30;
31
+ export const PROVIDERS_USAGE = `用法:ama providers add <id> --base-url <url> [--channel <名字>=<协议>@<地址> …]
32
+ [--api openai-completions|openai-responses|anthropic-messages|auto]
33
+ [--key-env <VAR>] [--probe] [--limit N] [--probe-models a,b,…]
34
+ [--max-requests N] [--prefer chat,responses,messages]
35
+ [--include-no-tools] [--yes]
36
+ ama providers list
37
+ ama providers channels <id>
38
+ ama providers remove <id>
39
+ ama providers refresh <id> [--probe] [--limit N] [--probe-models a,b,…] [--max-requests N] [--yes]
40
+ `;
41
+ const VALUE_OPTIONS = [
42
+ "base-url",
43
+ "api",
44
+ "key-env",
45
+ "limit",
46
+ "probe-models",
47
+ "max-requests",
48
+ "prefer",
49
+ "name",
50
+ "profile",
51
+ "auth-file",
52
+ ];
53
+ const FLAG_OPTIONS = ["probe", "include-no-tools", "yes"];
54
+ function intValue(ctx, name, fallback) {
55
+ const raw = ctx.values.get(name);
56
+ if (raw === undefined)
57
+ return fallback;
58
+ const n = Number(raw);
59
+ if (!Number.isInteger(n) || n < 1)
60
+ throw new UsageError(`--${name} 需要正整数:${raw}`);
61
+ return n;
62
+ }
63
+ function list(raw) {
64
+ return raw
65
+ ?.split(",")
66
+ .map((s) => s.trim())
67
+ .filter((s) => s !== "");
68
+ }
69
+ export function userConfig(level) {
70
+ return structuredClone(loadConfigFile("config", level.userConfigPath)?.value ?? { version: CONFIG_FILE_VERSION });
71
+ }
72
+ async function confirm(io, question) {
73
+ io.stderr(question);
74
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
75
+ try {
76
+ return /^y(es)?$/i.test((await rl.question("继续?[y/N] ")).trim());
77
+ }
78
+ finally {
79
+ rl.close();
80
+ }
81
+ }
82
+ /** --yes / TTY 确认;非 TTY 且没有 --yes 返回 false 并提示(调用方退出 2)。 */
83
+ async function approved(ctx, what) {
84
+ if (ctx.flags.has("yes"))
85
+ return true;
86
+ if (!ctx.io.stdinIsTTY) {
87
+ ctx.io.stderr(`ama: ${what},非交互环境需加 --yes\n`);
88
+ return "usage";
89
+ }
90
+ return confirm(ctx.io, `${what}\n`);
91
+ }
92
+ /**
93
+ * `GET /models`。OpenAI 系带 `Authorization: Bearer`;Anthropic 形状的渠道先试 Bearer(中转多半只认它),
94
+ * 401 / 403 再试 `x-api-key` + `anthropic-version`(官方形状)。
95
+ */
96
+ async function fetchModelList(target, apiKey) {
97
+ const get = (headers) => fetch(target.url, {
98
+ headers: { accept: "application/json", ...headers },
99
+ signal: AbortSignal.timeout(DISCOVER_TIMEOUT_MS),
100
+ });
101
+ let response = await get(apiKey !== undefined ? { authorization: `Bearer ${apiKey}` } : {});
102
+ if ((response.status === 401 || response.status === 403) &&
103
+ apiKey !== undefined &&
104
+ target.channel.api === "anthropic-messages")
105
+ response = await get({ "x-api-key": apiKey, "anthropic-version": "2023-06-01" });
106
+ if (!response.ok)
107
+ throw new Error(`HTTP ${response.status} ${target.url}`);
108
+ return parseModelList(await response.json());
109
+ }
110
+ function channelsOfConfig(config) {
111
+ if (config.channels !== undefined)
112
+ return Object.entries(config.channels).map(([name, c]) => ({
113
+ name,
114
+ api: c.api,
115
+ baseUrl: c.baseUrl,
116
+ }));
117
+ if (config.baseUrl === undefined)
118
+ return [];
119
+ const api = config.api ?? "openai-completions";
120
+ return [{ name: "default", api, baseUrl: config.baseUrl }];
121
+ }
122
+ /** 渠道的探测用供应商数据(key 由调用方传入)。 */
123
+ function probeProvider(id, channel) {
124
+ return {
125
+ id,
126
+ name: id,
127
+ api: channel.api,
128
+ baseUrl: channel.baseUrl,
129
+ envKeys: [],
130
+ models: [],
131
+ requiresApiKey: true,
132
+ builtin: false,
133
+ };
134
+ }
135
+ /** 逐模型定渠道(含探测);返回各模型的计划与是否因鉴权 / 限流提前停止。 */
136
+ async function plan(ctx, input) {
137
+ const { io } = ctx;
138
+ const prefer = list(ctx.values.get("prefer")) ?? [...DEFAULT_PREFER];
139
+ const byId = new Map(input.listed.map((m) => [m.id, m]));
140
+ const fresh = input.listed.filter((m) => !input.existingIds.has(m.id));
141
+ const hint = (id) => {
142
+ const listed = byId.get(id);
143
+ return listed !== undefined ? hintedChannels(listed, input.candidates) : undefined;
144
+ };
145
+ const tryChannels = (id) => orderChannels(hint(id) ?? input.candidates.map((c) => c.name), prefer);
146
+ const probeable = (id) => tryChannels(id).length > 0;
147
+ const results = new Map();
148
+ let stopped;
149
+ if (input.probe) {
150
+ const only = list(ctx.values.get("probe-models"));
151
+ const selected = probeSelection(fresh.map((m) => m.id).filter(probeable), intValue(ctx, "limit", DEFAULT_LIMIT), only);
152
+ const max = intValue(ctx, "max-requests", DEFAULT_MAX_REQUESTS);
153
+ const capped = capRequests(selected, tryChannels, max);
154
+ io.stdout(`\n探测:${capped.ids.length} 个模型、${capped.requests} 次最小请求(每模型每渠道 1 次,上限 ${max})` +
155
+ `${capped.dropped > 0 ? `;另有 ${capped.dropped} 个超出上限未探测` : ""}\n`);
156
+ const ok = await approved(ctx, `将发 ${capped.requests} 次计费请求并写入 ${ctx.level.userConfigPath}`);
157
+ if (ok === "usage")
158
+ return { plans: [], cancelled: ExitCode.Usage };
159
+ if (!ok)
160
+ return { plans: [], cancelled: ExitCode.Ok };
161
+ outer: for (const id of capped.ids) {
162
+ const result = { ok: [] };
163
+ const failed = [];
164
+ results.set(id, result);
165
+ for (const name of tryChannels(id)) {
166
+ const channel = input.candidates.find((c) => c.name === name);
167
+ const provider = probeProvider(input.id, channel);
168
+ const model = materializeModel(withCustomDefaults({ id }, input.id, channel.api), provider);
169
+ const error = await attempt(input.registry, model, input.apiKey);
170
+ if (error === undefined)
171
+ result.ok.push(name);
172
+ else {
173
+ result.error = error.split("\n")[0]?.slice(0, 120) ?? error;
174
+ failed.push(`${name}:${result.error}`);
175
+ if (FATAL_STATUS.test(error)) {
176
+ stopped = error;
177
+ break outer;
178
+ }
179
+ }
180
+ }
181
+ io.stdout(` ${id} ${result.ok.length > 0 ? result.ok.join(", ") : "全部失败"}` +
182
+ `${failed.length > 0 ? `(失败 ${failed.join(";")})` : ""}\n`);
183
+ }
184
+ }
185
+ const includeNoTools = ctx.flags.has("include-no-tools");
186
+ const plans = input.listed.map((listed) => {
187
+ const match = input.index?.match(listed.id);
188
+ const fields = match !== undefined ? modelsDevFields(match.model) : {};
189
+ const base = { id: listed.id, fields, match };
190
+ if (input.existingIds.has(listed.id))
191
+ return { ...base, channels: [], status: "existing" };
192
+ const probed = results.get(listed.id);
193
+ if (probed !== undefined) {
194
+ if (probed.ok.length === 0)
195
+ return {
196
+ ...base,
197
+ channels: [],
198
+ status: "failed",
199
+ ...(probed.error ? { error: probed.error } : {}),
200
+ };
201
+ return { ...base, channels: orderChannels(probed.ok, prefer), status: "ok" };
202
+ }
203
+ const hinted = hint(listed.id);
204
+ if (hinted !== undefined && hinted.length === 0)
205
+ return { ...base, channels: [], status: "no-channel" };
206
+ const channels = hinted !== undefined
207
+ ? orderChannels(hinted, prefer)
208
+ : orderChannels(input.candidates.map((c) => c.name), prefer).slice(0, 1);
209
+ if (fields.toolCall === false && !includeNoTools)
210
+ return { ...base, channels, status: "no-tools" };
211
+ return { ...base, channels, status: "unprobed" };
212
+ });
213
+ for (const p of plans)
214
+ if (p.status === "ok" && p.fields.toolCall === false && !includeNoTools)
215
+ p.status = "no-tools";
216
+ return { plans, ...(stopped !== undefined ? { stopped } : {}) };
217
+ }
218
+ async function obtainKey(ctx, id, registry) {
219
+ const keyEnv = ctx.values.get("key-env");
220
+ if (keyEnv !== undefined) {
221
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(keyEnv))
222
+ throw new UsageError(`--key-env 不是合法的变量名:${keyEnv}`);
223
+ const value = ctx.io.env[keyEnv]?.trim();
224
+ if (!value)
225
+ ctx.io.stderr(`ama: 警告:环境变量 ${keyEnv} 未设置,列模型与探测将不带 key\n`);
226
+ return { apiKey: value || undefined, configValue: `$${keyEnv}` };
227
+ }
228
+ if (registry.get(id) !== undefined) {
229
+ const existing = await registry.resolveApiKey(id);
230
+ if (existing.apiKey !== undefined)
231
+ return { apiKey: existing.apiKey };
232
+ }
233
+ if (ctx.io.stdinIsTTY)
234
+ ctx.io.stderr(`输入 ${id} 的 API key(不回显),回车结束:`);
235
+ const key = extractKey(await ctx.io.readStdin());
236
+ if (ctx.io.stdinIsTTY)
237
+ ctx.io.stderr("\n");
238
+ if (key === "")
239
+ throw new UsageError("没有从 stdin 读到 key(或用 --key-env <VAR>)");
240
+ return { apiKey: key, store: key };
241
+ }
242
+ async function add(ctx, id, refresh) {
243
+ const { io, level } = ctx;
244
+ if (!PROVIDER_ID_PATTERN.test(id) || id.includes("@"))
245
+ throw new UsageError(`供应商 id 不合法:${id}`);
246
+ const config = userConfig(level);
247
+ const existing = config.providers?.[id];
248
+ if (refresh && existing === undefined) {
249
+ io.stderr(`ama: ${level.userConfigPath} 里没有供应商 ${id}(先 ama providers add)\n`);
250
+ return ExitCode.Usage;
251
+ }
252
+ const registry = await buildRegistry(level, io, ctx.deps);
253
+ let candidates;
254
+ if (refresh)
255
+ candidates = channelsOfConfig(existing);
256
+ else if (ctx.channelSpecs.length > 0)
257
+ candidates = ctx.channelSpecs.map(parseChannelSpec);
258
+ else {
259
+ const baseUrl = ctx.values.get("base-url");
260
+ if (baseUrl === undefined)
261
+ throw new UsageError("ama providers add 需要 --base-url <url> 或 --channel");
262
+ if (!/^https?:\/\//.test(baseUrl))
263
+ throw new UsageError(`--base-url 应为 http(s) URL:${baseUrl}`);
264
+ candidates = defaultChannels(baseUrl, ctx.values.get("api"));
265
+ }
266
+ const target = listingTarget(candidates);
267
+ if (target === undefined)
268
+ throw new UsageError(`${id} 没有可用的渠道`);
269
+ const key = await obtainKey(ctx, id, registry);
270
+ let listed;
271
+ try {
272
+ listed = await fetchModelList(target, key.apiKey);
273
+ }
274
+ catch (error) {
275
+ io.stderr(`ama: ${id} 模型列表获取失败:${error.message}\n`);
276
+ return ExitCode.RuntimeError;
277
+ }
278
+ io.stdout(`${id}:发现 ${listed.length} 个模型(${target.url})\n`);
279
+ const md = await refreshModelsDev({ dataDir: level.dataDir, env: io.env, force: refresh });
280
+ io.stdout(`${describeRefresh(md)}\n`);
281
+ if (md.warning !== undefined)
282
+ io.stderr(`ama: 警告:${md.warning}\n`);
283
+ io.stdout(`候选渠道:${candidates.map((c) => `${c.name}(${c.api} ${c.baseUrl})`).join(" · ")}\n`);
284
+ const existingIds = new Set((existing?.models ?? []).map((m) => m.id));
285
+ const result = await plan(ctx, {
286
+ id,
287
+ candidates,
288
+ listed,
289
+ index: md.index,
290
+ existingIds,
291
+ apiKey: key.apiKey,
292
+ registry,
293
+ probe: ctx.flags.has("probe"),
294
+ });
295
+ if (result.cancelled !== undefined)
296
+ return result.cancelled;
297
+ io.stdout(`\n${renderTable(result.plans)}`);
298
+ if (refresh) {
299
+ const gone = [...existingIds].filter((m) => !listed.some((l) => l.id === m));
300
+ if (gone.length > 0)
301
+ io.stdout(`上游已不再列出(未删除):${gone.join(", ")}\n`);
302
+ }
303
+ const writable = result.plans.filter((p) => p.status === "ok" || p.status === "unprobed");
304
+ const legacy = existing !== undefined && existing.channels === undefined;
305
+ const merged = legacy && refresh
306
+ ? legacyMerge(existing, writable.map((p) => p.id))
307
+ : mergeProvider(legacy ? asChannels(existing) : existing, {
308
+ ...(ctx.values.get("name") !== undefined
309
+ ? { name: ctx.values.get("name") }
310
+ : {}),
311
+ ...(key.configValue !== undefined ? { apiKey: key.configValue } : {}),
312
+ channels: candidates,
313
+ models: writable,
314
+ prefer: list(ctx.values.get("prefer")) ?? [...DEFAULT_PREFER],
315
+ });
316
+ const keyNote = key.store !== undefined ? `;key → ${level.authFile}(0600)` : "";
317
+ const summary = `\n将写入 ${level.userConfigPath}:${id} 新增渠道 ${merged.addedChannels.join(", ") || "无"},` +
318
+ `新增模型 ${merged.addedModels.length} 个${keyNote}`;
319
+ io.stdout(`${summary}\n`);
320
+ if (merged.addedModels.length === 0 &&
321
+ merged.addedChannels.length === 0 &&
322
+ key.store === undefined) {
323
+ io.stdout("没有要写入的内容\n");
324
+ return result.stopped !== undefined ? ExitCode.RuntimeError : ExitCode.Ok;
325
+ }
326
+ if (!ctx.flags.has("probe")) {
327
+ const ok = await approved(ctx, "确认写入");
328
+ if (ok === "usage")
329
+ return ExitCode.Usage;
330
+ if (!ok) {
331
+ io.stderr("ama: 已取消\n");
332
+ return ExitCode.Ok;
333
+ }
334
+ }
335
+ const providers = (config.providers ??= {});
336
+ providers[id] = merged.config;
337
+ const backup = existsSync(level.userConfigPath);
338
+ writeConfigFile(level.userConfigPath, config, { backup: true });
339
+ if (key.store !== undefined)
340
+ setAuthKey(level.authFile, id, key.store);
341
+ io.stdout(`已写入 ${level.userConfigPath}${backup ? "(原文件备份为 config.json.bak)" : ""}\n`);
342
+ const sample = result.plans.find((p) => p.status === "ok" && merged.addedModels.includes(p.id))?.id ??
343
+ merged.addedModels[0];
344
+ if (sample !== undefined)
345
+ io.stdout(`试试:ama -p "hi" --model ${id}/${sample}\n`);
346
+ if (result.stopped !== undefined) {
347
+ io.stderr(`ama: 探测提前停止(${result.stopped})\n`);
348
+ return ExitCode.RuntimeError;
349
+ }
350
+ return ExitCode.Ok;
351
+ }
352
+ /** 旧写法(无 channels)的供应商:先把供应商级 api + baseUrl 变成 `default` 渠道。 */
353
+ function asChannels(config) {
354
+ const { api, baseUrl, ...rest } = structuredClone(config);
355
+ if (baseUrl === undefined)
356
+ return rest;
357
+ return {
358
+ ...rest,
359
+ channels: { default: { api: api ?? "openai-completions", baseUrl } },
360
+ defaultChannel: "default",
361
+ };
362
+ }
363
+ /** 旧写法供应商的 refresh:只追加 `{ id }`。 */
364
+ function legacyMerge(config, ids) {
365
+ const next = structuredClone(config);
366
+ const models = (next.models ??= []);
367
+ const known = new Set(models.map((m) => m.id));
368
+ const addedModels = ids.filter((id) => !known.has(id));
369
+ for (const id of addedModels)
370
+ models.push({ id });
371
+ return { config: next, addedChannels: [], addedModels };
372
+ }
373
+ /** `--channel` 可重复:先从 argv 里取出来,其余交给 parseSubArgs。 */
374
+ function takeChannels(argv) {
375
+ const rest = [];
376
+ const specs = [];
377
+ for (let i = 0; i < argv.length; i++) {
378
+ const token = argv[i] ?? "";
379
+ if (token === "--channel") {
380
+ const value = argv[++i];
381
+ if (value === undefined || value === "")
382
+ throw new UsageError("--channel 需要一个值");
383
+ specs.push(value);
384
+ }
385
+ else if (token.startsWith("--channel="))
386
+ specs.push(token.slice("--channel=".length));
387
+ else
388
+ rest.push(token);
389
+ }
390
+ return { rest, specs };
391
+ }
392
+ export async function runProviders(argv, io, deps) {
393
+ const { rest, specs } = takeChannels(argv);
394
+ const { positionals, values, flags } = parseSubArgs(rest, VALUE_OPTIONS, FLAG_OPTIONS);
395
+ const action = positionals[0];
396
+ if (flags.has("help") || action === undefined) {
397
+ io.stdout(PROVIDERS_USAGE);
398
+ return flags.has("help") ? ExitCode.Ok : ExitCode.Usage;
399
+ }
400
+ if (deps === undefined) {
401
+ io.stderr("ama: providers 尚未装配(供应商注册表由集成批次注入)\n");
402
+ return ExitCode.RuntimeError;
403
+ }
404
+ const level = loadUserLevel(io, {
405
+ profile: values.get("profile"),
406
+ authFile: values.get("auth-file"),
407
+ });
408
+ for (const warning of level.warnings)
409
+ io.stderr(`ama: 警告:${warning}\n`);
410
+ const ctx = { io, deps, level, values, flags, channelSpecs: specs };
411
+ const target = positionals[1];
412
+ const need = () => {
413
+ if (target === undefined)
414
+ throw new UsageError(`ama providers ${action} 需要 <id>`);
415
+ if (positionals.length > 2)
416
+ throw new UsageError(`多余的参数:${positionals.slice(2).join(" ")}`);
417
+ return target;
418
+ };
419
+ switch (action) {
420
+ case "add":
421
+ return add(ctx, need(), false);
422
+ case "refresh":
423
+ return add(ctx, need(), true);
424
+ case "list":
425
+ return listProviders(ctx);
426
+ case "channels":
427
+ return listChannels(ctx, need());
428
+ case "remove":
429
+ return removeProvider(ctx, need());
430
+ default:
431
+ throw new UsageError(`未知的 providers 子命令:${action}`);
432
+ }
433
+ }