@armadra/agent 0.0.0-stage → 0.2.1

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 (407) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +426 -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 +511 -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 +118 -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 +104 -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 +484 -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 +303 -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 +102 -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/registry.d.ts +83 -0
  101. package/dist/ai/providers/registry.js +305 -0
  102. package/dist/ai/sse.d.ts +32 -0
  103. package/dist/ai/sse.js +140 -0
  104. package/dist/ai/thinking.d.ts +42 -0
  105. package/dist/ai/thinking.js +101 -0
  106. package/dist/ai/types.d.ts +428 -0
  107. package/dist/ai/types.js +19 -0
  108. package/dist/bundle/ama-sandbox.cjs +324 -0
  109. package/dist/bundle/ama.cjs +25770 -0
  110. package/dist/bundle.d.ts +9 -0
  111. package/dist/bundle.js +15 -0
  112. package/dist/cli/args.d.ts +82 -0
  113. package/dist/cli/args.js +381 -0
  114. package/dist/cli/bootstrap.d.ts +18 -0
  115. package/dist/cli/bootstrap.js +398 -0
  116. package/dist/cli/compose-providers.d.ts +37 -0
  117. package/dist/cli/compose-providers.js +104 -0
  118. package/dist/cli/compose-session.d.ts +69 -0
  119. package/dist/cli/compose-session.js +370 -0
  120. package/dist/cli/compose-store.d.ts +24 -0
  121. package/dist/cli/compose-store.js +125 -0
  122. package/dist/cli/compose.d.ts +106 -0
  123. package/dist/cli/compose.js +206 -0
  124. package/dist/cli/default-model.d.ts +16 -0
  125. package/dist/cli/default-model.js +24 -0
  126. package/dist/cli/deps.d.ts +205 -0
  127. package/dist/cli/deps.js +12 -0
  128. package/dist/cli/exit-codes.d.ts +29 -0
  129. package/dist/cli/exit-codes.js +40 -0
  130. package/dist/cli/main.d.ts +26 -0
  131. package/dist/cli/main.js +165 -0
  132. package/dist/cli/runtime.d.ts +90 -0
  133. package/dist/cli/runtime.js +8 -0
  134. package/dist/cli/startup-screen.d.ts +15 -0
  135. package/dist/cli/startup-screen.js +56 -0
  136. package/dist/cli/startup-steps.d.ts +35 -0
  137. package/dist/cli/startup-steps.js +197 -0
  138. package/dist/cli/subcommands/auth.d.ts +12 -0
  139. package/dist/cli/subcommands/auth.js +91 -0
  140. package/dist/cli/subcommands/config.d.ts +44 -0
  141. package/dist/cli/subcommands/config.js +201 -0
  142. package/dist/cli/subcommands/context.d.ts +21 -0
  143. package/dist/cli/subcommands/context.js +41 -0
  144. package/dist/cli/subcommands/doctor.d.ts +11 -0
  145. package/dist/cli/subcommands/doctor.js +246 -0
  146. package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
  147. package/dist/cli/subcommands/models-cache-probe.js +190 -0
  148. package/dist/cli/subcommands/models-discover.d.ts +40 -0
  149. package/dist/cli/subcommands/models-discover.js +204 -0
  150. package/dist/cli/subcommands/models.d.ts +40 -0
  151. package/dist/cli/subcommands/models.js +144 -0
  152. package/dist/cli/subcommands/sessions.d.ts +11 -0
  153. package/dist/cli/subcommands/sessions.js +100 -0
  154. package/dist/codemode/capability.d.ts +37 -0
  155. package/dist/codemode/capability.js +55 -0
  156. package/dist/codemode/declarations.d.ts +44 -0
  157. package/dist/codemode/declarations.js +111 -0
  158. package/dist/codemode/host-side.d.ts +59 -0
  159. package/dist/codemode/host-side.js +253 -0
  160. package/dist/codemode/modes.d.ts +23 -0
  161. package/dist/codemode/modes.js +40 -0
  162. package/dist/codemode/protocol.d.ts +91 -0
  163. package/dist/codemode/protocol.js +114 -0
  164. package/dist/codemode/sandbox-entry.d.ts +25 -0
  165. package/dist/codemode/sandbox-entry.js +323 -0
  166. package/dist/codemode/store.d.ts +26 -0
  167. package/dist/codemode/store.js +53 -0
  168. package/dist/codemode/tool.d.ts +105 -0
  169. package/dist/codemode/tool.js +283 -0
  170. package/dist/compaction/branch-summary.d.ts +30 -0
  171. package/dist/compaction/branch-summary.js +79 -0
  172. package/dist/compaction/breaker.d.ts +32 -0
  173. package/dist/compaction/breaker.js +65 -0
  174. package/dist/compaction/cut-point.d.ts +23 -0
  175. package/dist/compaction/cut-point.js +70 -0
  176. package/dist/compaction/estimate.d.ts +30 -0
  177. package/dist/compaction/estimate.js +114 -0
  178. package/dist/compaction/prune-tier.d.ts +31 -0
  179. package/dist/compaction/prune-tier.js +83 -0
  180. package/dist/compaction/serialize.d.ts +20 -0
  181. package/dist/compaction/serialize.js +115 -0
  182. package/dist/compaction/summarize-tier.d.ts +111 -0
  183. package/dist/compaction/summarize-tier.js +305 -0
  184. package/dist/config/auth-file.d.ts +39 -0
  185. package/dist/config/auth-file.js +92 -0
  186. package/dist/config/context-files.d.ts +30 -0
  187. package/dist/config/context-files.js +77 -0
  188. package/dist/config/load.d.ts +45 -0
  189. package/dist/config/load.js +97 -0
  190. package/dist/config/merge.d.ts +73 -0
  191. package/dist/config/merge.js +248 -0
  192. package/dist/config/paths.d.ts +38 -0
  193. package/dist/config/paths.js +108 -0
  194. package/dist/config/profile.d.ts +27 -0
  195. package/dist/config/profile.js +65 -0
  196. package/dist/config/schema.d.ts +34 -0
  197. package/dist/config/schema.js +413 -0
  198. package/dist/config/trust.d.ts +42 -0
  199. package/dist/config/trust.js +122 -0
  200. package/dist/config/types.d.ts +172 -0
  201. package/dist/config/types.js +27 -0
  202. package/dist/config/write.d.ts +10 -0
  203. package/dist/config/write.js +25 -0
  204. package/dist/errors.d.ts +28 -0
  205. package/dist/errors.js +30 -0
  206. package/dist/hooks/config.d.ts +49 -0
  207. package/dist/hooks/config.js +102 -0
  208. package/dist/hooks/dispatcher.d.ts +41 -0
  209. package/dist/hooks/dispatcher.js +104 -0
  210. package/dist/hooks/matcher.d.ts +22 -0
  211. package/dist/hooks/matcher.js +108 -0
  212. package/dist/hooks/protocol.d.ts +33 -0
  213. package/dist/hooks/protocol.js +240 -0
  214. package/dist/hooks/runner.d.ts +30 -0
  215. package/dist/hooks/runner.js +143 -0
  216. package/dist/hooks/types.d.ts +147 -0
  217. package/dist/hooks/types.js +20 -0
  218. package/dist/host/api-impl.d.ts +75 -0
  219. package/dist/host/api-impl.js +173 -0
  220. package/dist/host/loader.d.ts +31 -0
  221. package/dist/host/loader.js +147 -0
  222. package/dist/host/types.d.ts +169 -0
  223. package/dist/host/types.js +14 -0
  224. package/dist/host.d.ts +5 -0
  225. package/dist/host.js +4 -0
  226. package/dist/index.d.ts +41 -0
  227. package/dist/index.js +28 -0
  228. package/dist/modes/commands-core.d.ts +44 -0
  229. package/dist/modes/commands-core.js +171 -0
  230. package/dist/modes/interactive/approval-dialog.d.ts +48 -0
  231. package/dist/modes/interactive/approval-dialog.js +202 -0
  232. package/dist/modes/interactive/commands.d.ts +41 -0
  233. package/dist/modes/interactive/commands.js +202 -0
  234. package/dist/modes/interactive/completion.d.ts +53 -0
  235. package/dist/modes/interactive/completion.js +157 -0
  236. package/dist/modes/interactive/interactive-mode.d.ts +46 -0
  237. package/dist/modes/interactive/interactive-mode.js +487 -0
  238. package/dist/modes/interactive/key-dispatch.d.ts +29 -0
  239. package/dist/modes/interactive/key-dispatch.js +107 -0
  240. package/dist/modes/interactive/line/line-editor.d.ts +50 -0
  241. package/dist/modes/interactive/line/line-editor.js +232 -0
  242. package/dist/modes/interactive/line/line-mode.d.ts +20 -0
  243. package/dist/modes/interactive/line/line-mode.js +216 -0
  244. package/dist/modes/interactive/line/line-render.d.ts +35 -0
  245. package/dist/modes/interactive/line/line-render.js +171 -0
  246. package/dist/modes/interactive/line/paste-state.d.ts +26 -0
  247. package/dist/modes/interactive/line/paste-state.js +72 -0
  248. package/dist/modes/interactive/message-view.d.ts +77 -0
  249. package/dist/modes/interactive/message-view.js +269 -0
  250. package/dist/modes/interactive/pickers.d.ts +42 -0
  251. package/dist/modes/interactive/pickers.js +115 -0
  252. package/dist/modes/interactive/startup-ui.d.ts +28 -0
  253. package/dist/modes/interactive/startup-ui.js +238 -0
  254. package/dist/modes/interactive/status-bar.d.ts +48 -0
  255. package/dist/modes/interactive/status-bar.js +139 -0
  256. package/dist/modes/interactive/tool-view.d.ts +95 -0
  257. package/dist/modes/interactive/tool-view.js +344 -0
  258. package/dist/modes/print/json-event.d.ts +15 -0
  259. package/dist/modes/print/json-event.js +39 -0
  260. package/dist/modes/print/print-mode.d.ts +15 -0
  261. package/dist/modes/print/print-mode.js +96 -0
  262. package/dist/modes/rpc/commands.d.ts +40 -0
  263. package/dist/modes/rpc/commands.js +258 -0
  264. package/dist/modes/rpc/jsonl.d.ts +16 -0
  265. package/dist/modes/rpc/jsonl.js +55 -0
  266. package/dist/modes/rpc/rpc-mode.d.ts +22 -0
  267. package/dist/modes/rpc/rpc-mode.js +153 -0
  268. package/dist/modes/session-report.d.ts +55 -0
  269. package/dist/modes/session-report.js +278 -0
  270. package/dist/modes/shared.d.ts +13 -0
  271. package/dist/modes/shared.js +52 -0
  272. package/dist/modes/startup-ui-text.d.ts +16 -0
  273. package/dist/modes/startup-ui-text.js +103 -0
  274. package/dist/permissions/broker.d.ts +47 -0
  275. package/dist/permissions/broker.js +107 -0
  276. package/dist/permissions/dangerous.d.ts +47 -0
  277. package/dist/permissions/dangerous.js +370 -0
  278. package/dist/permissions/pipeline.d.ts +51 -0
  279. package/dist/permissions/pipeline.js +181 -0
  280. package/dist/permissions/preview.d.ts +43 -0
  281. package/dist/permissions/preview.js +463 -0
  282. package/dist/permissions/rules.d.ts +58 -0
  283. package/dist/permissions/rules.js +249 -0
  284. package/dist/permissions/types.d.ts +100 -0
  285. package/dist/permissions/types.js +18 -0
  286. package/dist/rpc.d.ts +171 -0
  287. package/dist/rpc.js +13 -0
  288. package/dist/sdk.d.ts +98 -0
  289. package/dist/sdk.js +195 -0
  290. package/dist/session/manager.d.ts +70 -0
  291. package/dist/session/manager.js +278 -0
  292. package/dist/session/migrate.d.ts +19 -0
  293. package/dist/session/migrate.js +53 -0
  294. package/dist/session/projection.d.ts +50 -0
  295. package/dist/session/projection.js +180 -0
  296. package/dist/session/store.d.ts +47 -0
  297. package/dist/session/store.js +210 -0
  298. package/dist/session/tree.d.ts +21 -0
  299. package/dist/session/tree.js +88 -0
  300. package/dist/session/types.d.ts +199 -0
  301. package/dist/session/types.js +18 -0
  302. package/dist/skills/discover.d.ts +60 -0
  303. package/dist/skills/discover.js +160 -0
  304. package/dist/skills/expand.d.ts +35 -0
  305. package/dist/skills/expand.js +46 -0
  306. package/dist/skills/frontmatter.d.ts +27 -0
  307. package/dist/skills/frontmatter.js +157 -0
  308. package/dist/skills/index-prompt.d.ts +13 -0
  309. package/dist/skills/index-prompt.js +42 -0
  310. package/dist/skills/templates.d.ts +52 -0
  311. package/dist/skills/templates.js +177 -0
  312. package/dist/tools/bash.d.ts +49 -0
  313. package/dist/tools/bash.js +222 -0
  314. package/dist/tools/edit-fuzzy.d.ts +36 -0
  315. package/dist/tools/edit-fuzzy.js +129 -0
  316. package/dist/tools/edit.d.ts +38 -0
  317. package/dist/tools/edit.js +241 -0
  318. package/dist/tools/file-mutex.d.ts +8 -0
  319. package/dist/tools/file-mutex.js +29 -0
  320. package/dist/tools/glob.d.ts +44 -0
  321. package/dist/tools/glob.js +217 -0
  322. package/dist/tools/grep.d.ts +39 -0
  323. package/dist/tools/grep.js +179 -0
  324. package/dist/tools/ignore.d.ts +40 -0
  325. package/dist/tools/ignore.js +174 -0
  326. package/dist/tools/ls.d.ts +21 -0
  327. package/dist/tools/ls.js +96 -0
  328. package/dist/tools/output-accumulator.d.ts +48 -0
  329. package/dist/tools/output-accumulator.js +106 -0
  330. package/dist/tools/paths.d.ts +20 -0
  331. package/dist/tools/paths.js +58 -0
  332. package/dist/tools/presets.d.ts +71 -0
  333. package/dist/tools/presets.js +128 -0
  334. package/dist/tools/process-tree.d.ts +35 -0
  335. package/dist/tools/process-tree.js +125 -0
  336. package/dist/tools/read.d.ts +28 -0
  337. package/dist/tools/read.js +181 -0
  338. package/dist/tools/registry.d.ts +43 -0
  339. package/dist/tools/registry.js +106 -0
  340. package/dist/tools/shell.d.ts +38 -0
  341. package/dist/tools/shell.js +91 -0
  342. package/dist/tools/task.d.ts +29 -0
  343. package/dist/tools/task.js +107 -0
  344. package/dist/tools/todo.d.ts +28 -0
  345. package/dist/tools/todo.js +107 -0
  346. package/dist/tools/truncate.d.ts +44 -0
  347. package/dist/tools/truncate.js +175 -0
  348. package/dist/tools/types.d.ts +135 -0
  349. package/dist/tools/types.js +19 -0
  350. package/dist/tools/write.d.ts +20 -0
  351. package/dist/tools/write.js +84 -0
  352. package/dist/tui/ansi.d.ts +29 -0
  353. package/dist/tui/ansi.js +420 -0
  354. package/dist/tui/component.d.ts +38 -0
  355. package/dist/tui/component.js +13 -0
  356. package/dist/tui/components/box.d.ts +26 -0
  357. package/dist/tui/components/box.js +65 -0
  358. package/dist/tui/components/container.d.ts +14 -0
  359. package/dist/tui/components/container.js +39 -0
  360. package/dist/tui/components/editor-buffer.d.ts +67 -0
  361. package/dist/tui/components/editor-buffer.js +300 -0
  362. package/dist/tui/components/editor-paste.d.ts +29 -0
  363. package/dist/tui/components/editor-paste.js +64 -0
  364. package/dist/tui/components/editor.d.ts +114 -0
  365. package/dist/tui/components/editor.js +530 -0
  366. package/dist/tui/components/key-value.d.ts +29 -0
  367. package/dist/tui/components/key-value.js +48 -0
  368. package/dist/tui/components/loader.d.ts +35 -0
  369. package/dist/tui/components/loader.js +71 -0
  370. package/dist/tui/components/markdown.d.ts +90 -0
  371. package/dist/tui/components/markdown.js +462 -0
  372. package/dist/tui/components/meter.d.ts +29 -0
  373. package/dist/tui/components/meter.js +47 -0
  374. package/dist/tui/components/overlay.d.ts +24 -0
  375. package/dist/tui/components/overlay.js +52 -0
  376. package/dist/tui/components/select-list.d.ts +57 -0
  377. package/dist/tui/components/select-list.js +179 -0
  378. package/dist/tui/components/spacer.d.ts +11 -0
  379. package/dist/tui/components/spacer.js +16 -0
  380. package/dist/tui/components/text.d.ts +30 -0
  381. package/dist/tui/components/text.js +65 -0
  382. package/dist/tui/keybindings.d.ts +66 -0
  383. package/dist/tui/keybindings.js +120 -0
  384. package/dist/tui/keys.d.ts +38 -0
  385. package/dist/tui/keys.js +275 -0
  386. package/dist/tui/stdin-buffer.d.ts +41 -0
  387. package/dist/tui/stdin-buffer.js +206 -0
  388. package/dist/tui/terminal.d.ts +79 -0
  389. package/dist/tui/terminal.js +153 -0
  390. package/dist/tui/theme.d.ts +31 -0
  391. package/dist/tui/theme.js +199 -0
  392. package/dist/tui/tui.d.ts +103 -0
  393. package/dist/tui/tui.js +363 -0
  394. package/dist/tui/vt-screen.d.ts +46 -0
  395. package/dist/tui/vt-screen.js +251 -0
  396. package/dist/tui.d.ts +30 -0
  397. package/dist/tui.js +31 -0
  398. package/dist/version.d.ts +8 -0
  399. package/dist/version.js +24 -0
  400. package/docs/codemode.md +102 -0
  401. package/docs/hooks.md +196 -0
  402. package/docs/host-api.md +161 -0
  403. package/docs/providers.md +316 -0
  404. package/docs/rpc.md +253 -0
  405. package/docs/session-format.md +119 -0
  406. package/docs/tui.md +168 -0
  407. package/package.json +101 -4
@@ -0,0 +1,166 @@
1
+ /**
2
+ * 模型目录(设计 §3.4):`catalog/*.json` 是数据源(人工校对、PR 更新),随包携带。
3
+ *
4
+ * 运行时零依赖、bundle 为单文件,不能在运行时读 JSON 文件;`catalog-data.ts` 是由 JSON 生成
5
+ * 的字符串表(`UPDATE_CATALOG=1 pnpm vitest run src/ai/providers/catalog.test.ts` 重新生成,
6
+ * 测试守住两者一致)。本模块解析并校验它,另提供覆盖合并。
7
+ *
8
+ * 必填字段:id、name、maxTokens、reasoning;contextWindow 与 cost「宁缺不猜」——数据源里
9
+ * 不确定的就不写(缺 contextWindow → 关自动压缩并警告;缺 cost → 显示 `$?`)。`promptCache`
10
+ * (TTL 秒数与最小可缓存长度)同样只写有公开依据的值(第三波 §1.4),留空 = 不承诺。
11
+ */
12
+ import { AmaError } from "../../errors.js";
13
+ import { CATALOG_SOURCES } from "./catalog-data.js";
14
+ import { THINKING_LEVELS } from "../thinking.js";
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function isNonNegative(value) {
19
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
20
+ }
21
+ /** 校验一个模型条目,返回问题列表(空 = 合法)。 */
22
+ export function checkCatalogModel(value, path) {
23
+ const problems = [];
24
+ if (!isRecord(value))
25
+ return [`${path}: must be an object`];
26
+ if (typeof value["id"] !== "string" || value["id"].length === 0)
27
+ problems.push(`${path}.id`);
28
+ if (typeof value["name"] !== "string")
29
+ problems.push(`${path}.name`);
30
+ if (typeof value["reasoning"] !== "boolean")
31
+ problems.push(`${path}.reasoning`);
32
+ if (!isNonNegative(value["maxTokens"]) || value["maxTokens"] === 0) {
33
+ problems.push(`${path}.maxTokens`);
34
+ }
35
+ if (value["contextWindow"] !== undefined && !isNonNegative(value["contextWindow"])) {
36
+ problems.push(`${path}.contextWindow`);
37
+ }
38
+ const input = value["input"];
39
+ if (input !== undefined &&
40
+ (!Array.isArray(input) || !input.every((i) => i === "text" || i === "image"))) {
41
+ problems.push(`${path}.input`);
42
+ }
43
+ const cost = value["cost"];
44
+ if (cost !== undefined) {
45
+ if (!isRecord(cost))
46
+ problems.push(`${path}.cost`);
47
+ else {
48
+ for (const key of ["input", "output", "cacheRead", "cacheWrite"]) {
49
+ if (!isNonNegative(cost[key]))
50
+ problems.push(`${path}.cost.${key}`);
51
+ }
52
+ if (cost["tiers"] !== undefined && !Array.isArray(cost["tiers"])) {
53
+ problems.push(`${path}.cost.tiers`);
54
+ }
55
+ }
56
+ }
57
+ const promptCache = value["promptCache"];
58
+ if (promptCache !== undefined) {
59
+ if (!isRecord(promptCache))
60
+ problems.push(`${path}.promptCache`);
61
+ else {
62
+ for (const [key, seconds] of Object.entries(promptCache)) {
63
+ const known = key === "short" || key === "long" || key === "minTokens";
64
+ if (!known || !Number.isInteger(seconds) || seconds <= 0) {
65
+ problems.push(`${path}.promptCache.${key}`);
66
+ }
67
+ }
68
+ const { short, long } = promptCache;
69
+ if (typeof short === "number" && typeof long === "number" && long < short) {
70
+ problems.push(`${path}.promptCache.long < short`);
71
+ }
72
+ }
73
+ }
74
+ const map = value["thinkingLevelMap"];
75
+ if (map !== undefined) {
76
+ if (!isRecord(map))
77
+ problems.push(`${path}.thinkingLevelMap`);
78
+ else {
79
+ for (const [level, mapped] of Object.entries(map)) {
80
+ const okLevel = THINKING_LEVELS.includes(level);
81
+ const okValue = mapped === null || typeof mapped === "string" || typeof mapped === "number";
82
+ if (!okLevel || !okValue)
83
+ problems.push(`${path}.thinkingLevelMap.${level}`);
84
+ }
85
+ }
86
+ }
87
+ return problems;
88
+ }
89
+ export function parseCatalogFile(value, source) {
90
+ const problems = [];
91
+ if (!isRecord(value))
92
+ problems.push("$: must be an object");
93
+ else {
94
+ if (value["version"] !== 1)
95
+ problems.push("$.version must be 1");
96
+ if (typeof value["provider"] !== "string")
97
+ problems.push("$.provider");
98
+ if (!Array.isArray(value["models"]))
99
+ problems.push("$.models must be an array");
100
+ else {
101
+ const seen = new Set();
102
+ value["models"].forEach((model, i) => {
103
+ problems.push(...checkCatalogModel(model, `$.models[${i}]`));
104
+ const id = isRecord(model) ? model["id"] : undefined;
105
+ if (typeof id === "string") {
106
+ if (seen.has(id))
107
+ problems.push(`$.models[${i}].id duplicated: ${id}`);
108
+ seen.add(id);
109
+ }
110
+ });
111
+ }
112
+ }
113
+ if (problems.length > 0) {
114
+ throw new AmaError("config_invalid", `model catalog ${source} is invalid: ${problems.join(", ")}`);
115
+ }
116
+ return value;
117
+ }
118
+ let cache;
119
+ /** 内置目录:供应商 id → 模型条目(深拷贝,调用方可随意修改)。 */
120
+ export function loadBuiltinCatalog() {
121
+ if (!cache) {
122
+ cache = new Map();
123
+ for (const [provider, raw] of Object.entries(CATALOG_SOURCES)) {
124
+ const file = parseCatalogFile(JSON.parse(raw), `catalog/${provider}.json`);
125
+ cache.set(file.provider, file.models);
126
+ }
127
+ }
128
+ const copy = new Map();
129
+ for (const [provider, models] of cache)
130
+ copy.set(provider, structuredClone(models));
131
+ return copy;
132
+ }
133
+ /** 目录条目 → Model(补 provider / api 与缺省 input)。 */
134
+ export function toModel(entry, provider, api) {
135
+ return { ...structuredClone(entry), input: entry.input ?? ["text"], provider, api };
136
+ }
137
+ /** 自定义模型缺省:maxTokens 8192、reasoning false、input ["text"]、name = id;不猜 contextWindow。 */
138
+ export function withCustomDefaults(entry, provider, api) {
139
+ return {
140
+ ...structuredClone(entry),
141
+ name: entry.name ?? entry.id,
142
+ input: entry.input ?? ["text"],
143
+ reasoning: entry.reasoning ?? false,
144
+ maxTokens: entry.maxTokens ?? 8192,
145
+ provider,
146
+ api,
147
+ };
148
+ }
149
+ /** 只改元数据:浅合并,`compat` / `cost` / `thinkingLevelMap` / `headers` 再合并一层。 */
150
+ export function applyModelOverride(model, override) {
151
+ const { id: _id, ...rest } = override;
152
+ const next = { ...model, ...structuredClone(rest) };
153
+ if (override.compat && model.compat)
154
+ next.compat = { ...model.compat, ...override.compat };
155
+ if (override.cost && model.cost)
156
+ next.cost = { ...model.cost, ...override.cost };
157
+ if (override.headers && model.headers)
158
+ next.headers = { ...model.headers, ...override.headers };
159
+ if (override.thinkingLevelMap && model.thinkingLevelMap) {
160
+ next.thinkingLevelMap = {
161
+ ...model.thinkingLevelMap,
162
+ ...override.thinkingLevelMap,
163
+ };
164
+ }
165
+ return next;
166
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * ProviderRegistry(设计 §3.2–§3.5):内置供应商 + 目录 + config.json 合并、模型查找、
3
+ * `provider/model` 解析、key 解析、协议实现查找。实现 B0 契约 `ProviderRegistryApi`。
4
+ *
5
+ * 合并顺序:内置(builtin.ts + catalog)← config.json `providers.<id>`(字段覆盖;`headers` /
6
+ * `compat` 合并;`models[]` 同 id 整条替换、新 id 追加;`modelOverrides[]` 只改元数据;两者都可带
7
+ * 模型级 `api`,缺省沿用供应商的协议)←
8
+ * auth.json 条目的 `baseUrl`。内置 openai / anthropic 的 baseUrl 还可来自 `OPENAI_BASE_URL` /
9
+ * `ANTHROPIC_BASE_URL`(第三波 §2.3,优先级最低,`baseUrlEnv(id)` 报告是否生效)。之后「物化」每个模型:补 baseUrl / headers / compat /
10
+ * authHeader / requiresApiKey,让协议实现只看 Model 就能发请求。
11
+ *
12
+ * 模型引用:`provider/model-id`(model id 自身可含斜杠,如 `openrouter/anthropic/claude-…`);
13
+ * 前缀不是供应商 id 时整串当 model id。无供应商前缀时全局唯一匹配;多家同名时只留已配置 key
14
+ * 的;仍不唯一 → ambiguous 并列出候选。模型表为空的供应商(ollama、lmstudio、未列模型的
15
+ * 自定义供应商)与 baseUrl 被改到非官方主机的内置供应商(中转站)接受任意 model id,按自定义
16
+ * 缺省合成;后者 compat 未显式配置 `sendPromptCacheKey` 时置 false。
17
+ */
18
+ import type { AmaConfig } from "../../config/types.js";
19
+ import { ApiRegistry } from "../apis/api.js";
20
+ import type { Api, ApiImplementation, ApiKeyResolution, Model, ModelLookup, ProviderData, ProviderRegistryApi } from "../types.js";
21
+ import { type KeyResolverOptions } from "./auth.js";
22
+ export type ModelSource = "builtin" | "config" | "discovered";
23
+ export interface ProviderRegistryOptions {
24
+ config?: AmaConfig | undefined;
25
+ /** 缺省 createDefaultApiRegistry()。 */
26
+ apis?: ApiRegistry | undefined;
27
+ /** key 发现选项;`configKeys` 缺省取自 config.providers.*.apiKey。 */
28
+ keys?: KeyResolverOptions | undefined;
29
+ /** 注册 `fake` 供应商(缺省 true)。 */
30
+ includeFake?: boolean | undefined;
31
+ onWarning?: ((message: string) => void) | undefined;
32
+ }
33
+ export interface ModelEntry {
34
+ model: Model;
35
+ provider: ProviderData;
36
+ source: ModelSource;
37
+ }
38
+ /** 物化:模型补齐供应商级字段(不可变:返回新对象)。 */
39
+ export declare function materializeModel(model: Model, provider: ProviderData): Model;
40
+ export declare class ProviderRegistry implements ProviderRegistryApi {
41
+ readonly warnings: string[];
42
+ private readonly providers;
43
+ private readonly sources;
44
+ /** baseUrl 来自环境变量的供应商 → 变量名(被 config / auth.json 覆盖后删除)。 */
45
+ private readonly envBaseUrls;
46
+ /** baseUrl 指向非官方主机的内置供应商:接受目录外的 model id。 */
47
+ private readonly relayed;
48
+ private readonly apis;
49
+ private readonly keys;
50
+ private readonly onWarning;
51
+ constructor(options?: ProviderRegistryOptions);
52
+ private warn;
53
+ private applyConfig;
54
+ /** baseUrl 来自哪个环境变量(未生效返回 undefined)。 */
55
+ baseUrlEnv(providerId: string): string | undefined;
56
+ /** 内置供应商的 baseUrl 指向非官方主机(中转站)。 */
57
+ isRelayed(providerId: string): boolean;
58
+ list(): readonly ProviderData[];
59
+ get(providerId: string): ProviderData | undefined;
60
+ listModels(): ModelEntry[];
61
+ modelSource(providerId: string, modelId: string): ModelSource;
62
+ /** 追加 / 替换模型(例如本地枚举得到的 ollama 模型)。 */
63
+ addModels(providerId: string, models: readonly Model[], source?: ModelSource): void;
64
+ private synthesize;
65
+ findModel(ref: string): ModelLookup;
66
+ private similar;
67
+ hasConfiguredKey(providerId: string): boolean;
68
+ resolveApiKey(providerId: string): Promise<ApiKeyResolution>;
69
+ getApi(api: Api): ApiImplementation | undefined;
70
+ }
71
+ /** 注册表若是 ProviderRegistry(或同形状),报告 baseUrl 来自的环境变量;否则 undefined。 */
72
+ export declare function baseUrlEnvOf(registry: ProviderRegistryApi, providerId: string): string | undefined;
73
+ /**
74
+ * 模型枚举:本地服务(`ama models list --provider ollama|lmstudio`)与 `ama models discover`
75
+ * (第三波 §2.3)共用。ollama 走 `/api/tags`,其它走 OpenAI 兼容的 `GET {baseUrl}/models`;
76
+ * `url` / `headers` 由调用方给出时原样使用(远端中转要带鉴权头)。失败抛错由调用方展示。
77
+ */
78
+ export declare function discoverLocalModels(provider: ProviderData, options?: {
79
+ signal?: AbortSignal;
80
+ timeoutMs?: number;
81
+ url?: string;
82
+ headers?: Record<string, string>;
83
+ }): Promise<Model[]>;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * ProviderRegistry(设计 §3.2–§3.5):内置供应商 + 目录 + config.json 合并、模型查找、
3
+ * `provider/model` 解析、key 解析、协议实现查找。实现 B0 契约 `ProviderRegistryApi`。
4
+ *
5
+ * 合并顺序:内置(builtin.ts + catalog)← config.json `providers.<id>`(字段覆盖;`headers` /
6
+ * `compat` 合并;`models[]` 同 id 整条替换、新 id 追加;`modelOverrides[]` 只改元数据;两者都可带
7
+ * 模型级 `api`,缺省沿用供应商的协议)←
8
+ * auth.json 条目的 `baseUrl`。内置 openai / anthropic 的 baseUrl 还可来自 `OPENAI_BASE_URL` /
9
+ * `ANTHROPIC_BASE_URL`(第三波 §2.3,优先级最低,`baseUrlEnv(id)` 报告是否生效)。之后「物化」每个模型:补 baseUrl / headers / compat /
10
+ * authHeader / requiresApiKey,让协议实现只看 Model 就能发请求。
11
+ *
12
+ * 模型引用:`provider/model-id`(model id 自身可含斜杠,如 `openrouter/anthropic/claude-…`);
13
+ * 前缀不是供应商 id 时整串当 model id。无供应商前缀时全局唯一匹配;多家同名时只留已配置 key
14
+ * 的;仍不唯一 → ambiguous 并列出候选。模型表为空的供应商(ollama、lmstudio、未列模型的
15
+ * 自定义供应商)与 baseUrl 被改到非官方主机的内置供应商(中转站)接受任意 model id,按自定义
16
+ * 缺省合成;后者 compat 未显式配置 `sendPromptCacheKey` 时置 false。
17
+ */
18
+ import { ApiRegistry, createDefaultApiRegistry } from "../apis/api.js";
19
+ import { fakeProviderData } from "../fake/fake-provider.js";
20
+ import { mergeHeaders } from "../http.js";
21
+ import { ApiKeyResolver } from "./auth.js";
22
+ import { BUILTIN_PROVIDERS, fallbackEnvKey, isRelayedBaseUrl } from "./builtin.js";
23
+ import { applyModelOverride, loadBuiltinCatalog, toModel, withCustomDefaults } from "./catalog.js";
24
+ /** 物化:模型补齐供应商级字段(不可变:返回新对象)。 */
25
+ export function materializeModel(model, provider) {
26
+ const out = {
27
+ ...model,
28
+ provider: provider.id,
29
+ baseUrl: model.baseUrl ?? provider.baseUrl,
30
+ };
31
+ if (provider.headers || model.headers)
32
+ out.headers = mergeHeaders(provider.headers, model.headers);
33
+ if (provider.compat || model.compat)
34
+ out.compat = { ...provider.compat, ...model.compat };
35
+ if (provider.authHeader !== undefined)
36
+ out.authHeader = model.authHeader ?? provider.authHeader;
37
+ out.requiresApiKey = provider.requiresApiKey;
38
+ return out;
39
+ }
40
+ function configKeysOf(config) {
41
+ const keys = {};
42
+ for (const [id, provider] of Object.entries(config?.providers ?? {}))
43
+ keys[id] = provider.apiKey;
44
+ return keys;
45
+ }
46
+ export class ProviderRegistry {
47
+ warnings = [];
48
+ providers = new Map();
49
+ sources = new Map();
50
+ /** baseUrl 来自环境变量的供应商 → 变量名(被 config / auth.json 覆盖后删除)。 */
51
+ envBaseUrls = new Map();
52
+ /** baseUrl 指向非官方主机的内置供应商:接受目录外的 model id。 */
53
+ relayed = new Set();
54
+ apis;
55
+ keys;
56
+ onWarning;
57
+ constructor(options = {}) {
58
+ this.onWarning = options.onWarning;
59
+ this.apis = options.apis ?? createDefaultApiRegistry();
60
+ this.keys = new ApiKeyResolver({
61
+ ...options.keys,
62
+ configKeys: options.keys?.configKeys ?? configKeysOf(options.config),
63
+ onWarning: (message) => this.warn(message),
64
+ });
65
+ const catalog = loadBuiltinCatalog();
66
+ const env = options.keys?.useEnv === false ? {} : (options.keys?.env ?? process.env);
67
+ for (const { baseUrlEnv, ...base } of BUILTIN_PROVIDERS) {
68
+ const models = (catalog.get(base.id) ?? []).map((entry) => {
69
+ const api = entry.api ?? base.api;
70
+ this.sources.set(`${base.id}/${entry.id}`, "builtin");
71
+ return toModel(entry, base.id, api);
72
+ });
73
+ const fromEnv = baseUrlEnv !== undefined ? env[baseUrlEnv]?.trim() : undefined;
74
+ if (baseUrlEnv !== undefined && fromEnv)
75
+ this.envBaseUrls.set(base.id, baseUrlEnv);
76
+ this.providers.set(base.id, {
77
+ ...base,
78
+ ...(fromEnv ? { baseUrl: fromEnv } : {}),
79
+ models,
80
+ builtin: true,
81
+ });
82
+ }
83
+ for (const [id, config] of Object.entries(options.config?.providers ?? {})) {
84
+ this.applyConfig(id, config);
85
+ }
86
+ for (const provider of this.providers.values()) {
87
+ const baseUrl = this.keys.authEntry(provider.id)?.entry.baseUrl;
88
+ if (baseUrl) {
89
+ provider.baseUrl = baseUrl;
90
+ this.envBaseUrls.delete(provider.id);
91
+ }
92
+ if (provider.builtin && isRelayedBaseUrl(provider.id, provider.baseUrl)) {
93
+ this.relayed.add(provider.id);
94
+ if (provider.compat?.sendPromptCacheKey === undefined)
95
+ provider.compat = { ...provider.compat, sendPromptCacheKey: false };
96
+ }
97
+ }
98
+ if (options.includeFake !== false && !this.providers.has("fake")) {
99
+ const fake = fakeProviderData();
100
+ for (const model of fake.models)
101
+ this.sources.set(`fake/${model.id}`, "builtin");
102
+ this.providers.set(fake.id, fake);
103
+ }
104
+ for (const provider of this.providers.values()) {
105
+ provider.models = provider.models.map((model) => materializeModel(model, provider));
106
+ }
107
+ }
108
+ warn(message) {
109
+ this.warnings.push(message);
110
+ this.onWarning?.(message);
111
+ }
112
+ applyConfig(id, config) {
113
+ const existing = this.providers.get(id);
114
+ if (!existing && !config.baseUrl) {
115
+ this.warn(`provider "${id}" in config.json has no baseUrl; ignored`);
116
+ return;
117
+ }
118
+ const provider = existing ?? {
119
+ id,
120
+ name: config.name ?? id,
121
+ api: config.api ?? "openai-completions",
122
+ baseUrl: config.baseUrl ?? "",
123
+ envKeys: [fallbackEnvKey(id)],
124
+ models: [],
125
+ requiresApiKey: true,
126
+ builtin: false,
127
+ };
128
+ if (config.name !== undefined)
129
+ provider.name = config.name;
130
+ if (config.api !== undefined)
131
+ provider.api = config.api;
132
+ if (config.baseUrl !== undefined) {
133
+ provider.baseUrl = config.baseUrl;
134
+ this.envBaseUrls.delete(id);
135
+ }
136
+ if (config.envKeys !== undefined) {
137
+ provider.envKeys = [
138
+ ...config.envKeys,
139
+ ...provider.envKeys.filter((k) => !config.envKeys?.includes(k)),
140
+ ];
141
+ }
142
+ if (config.authHeader !== undefined)
143
+ provider.authHeader = config.authHeader;
144
+ if (config.headers)
145
+ provider.headers = mergeHeaders(provider.headers, config.headers);
146
+ if (config.compat)
147
+ provider.compat = { ...provider.compat, ...config.compat };
148
+ if (config.requiresApiKey !== undefined)
149
+ provider.requiresApiKey = config.requiresApiKey;
150
+ for (const entry of config.models ?? []) {
151
+ // 模型级 api(第三波 §2.3):同一中转下不同模型走不同协议;缺省沿用供应商的。
152
+ const model = withCustomDefaults(entry, id, entry.api ?? provider.api);
153
+ const index = provider.models.findIndex((m) => m.id === entry.id);
154
+ if (index >= 0)
155
+ provider.models[index] = model;
156
+ else
157
+ provider.models.push(model);
158
+ this.sources.set(`${id}/${entry.id}`, "config");
159
+ }
160
+ for (const override of config.modelOverrides ?? []) {
161
+ const index = provider.models.findIndex((m) => m.id === override.id);
162
+ const current = provider.models[index];
163
+ if (!current) {
164
+ this.warn(`modelOverrides: "${id}/${override.id}" not found; ignored`);
165
+ continue;
166
+ }
167
+ const next = applyModelOverride(current, override);
168
+ provider.models[index] = override.api !== undefined ? { ...next, api: override.api } : next;
169
+ }
170
+ this.providers.set(id, provider);
171
+ }
172
+ /** baseUrl 来自哪个环境变量(未生效返回 undefined)。 */
173
+ baseUrlEnv(providerId) {
174
+ return this.envBaseUrls.get(providerId);
175
+ }
176
+ /** 内置供应商的 baseUrl 指向非官方主机(中转站)。 */
177
+ isRelayed(providerId) {
178
+ return this.relayed.has(providerId);
179
+ }
180
+ list() {
181
+ return [...this.providers.values()];
182
+ }
183
+ get(providerId) {
184
+ return this.providers.get(providerId);
185
+ }
186
+ listModels() {
187
+ const out = [];
188
+ for (const provider of this.providers.values()) {
189
+ for (const model of provider.models) {
190
+ out.push({ model, provider, source: this.modelSource(provider.id, model.id) });
191
+ }
192
+ }
193
+ return out;
194
+ }
195
+ modelSource(providerId, modelId) {
196
+ return this.sources.get(`${providerId}/${modelId}`) ?? "config";
197
+ }
198
+ /** 追加 / 替换模型(例如本地枚举得到的 ollama 模型)。 */
199
+ addModels(providerId, models, source = "discovered") {
200
+ const provider = this.providers.get(providerId);
201
+ if (!provider)
202
+ return;
203
+ for (const raw of models) {
204
+ const model = materializeModel({ ...raw, provider: providerId }, provider);
205
+ const index = provider.models.findIndex((m) => m.id === model.id);
206
+ if (index >= 0)
207
+ provider.models[index] = model;
208
+ else
209
+ provider.models.push(model);
210
+ this.sources.set(`${providerId}/${model.id}`, source);
211
+ }
212
+ }
213
+ synthesize(provider, modelId) {
214
+ return materializeModel(withCustomDefaults({ id: modelId }, provider.id, provider.api), provider);
215
+ }
216
+ findModel(ref) {
217
+ const trimmed = ref.trim();
218
+ const slash = trimmed.indexOf("/");
219
+ if (slash > 0) {
220
+ const provider = this.providers.get(trimmed.slice(0, slash));
221
+ if (provider) {
222
+ const id = trimmed.slice(slash + 1);
223
+ const model = provider.models.find((m) => m.id === id);
224
+ if (model)
225
+ return { ok: true, model, provider };
226
+ if ((provider.models.length === 0 || this.relayed.has(provider.id)) && id.length > 0) {
227
+ return { ok: true, model: this.synthesize(provider, id), provider };
228
+ }
229
+ return { ok: false, reason: "not_found", candidates: this.similar(id, provider.id) };
230
+ }
231
+ }
232
+ const matches = this.listModels().filter((entry) => entry.model.id === trimmed);
233
+ if (matches.length === 1 && matches[0]) {
234
+ return { ok: true, model: matches[0].model, provider: matches[0].provider };
235
+ }
236
+ if (matches.length === 0) {
237
+ return { ok: false, reason: "not_found", candidates: this.similar(trimmed) };
238
+ }
239
+ const configured = matches.filter((entry) => this.keys.hasConfiguredKey(entry.provider));
240
+ if (configured.length === 1 && configured[0]) {
241
+ return { ok: true, model: configured[0].model, provider: configured[0].provider };
242
+ }
243
+ return {
244
+ ok: false,
245
+ reason: "ambiguous",
246
+ candidates: (configured.length > 0 ? configured : matches).map((entry) => `${entry.provider.id}/${entry.model.id}`),
247
+ };
248
+ }
249
+ similar(id, providerId) {
250
+ const needle = id.toLowerCase();
251
+ return this.listModels()
252
+ .filter((entry) => providerId === undefined || entry.provider.id === providerId)
253
+ .filter((entry) => {
254
+ const candidate = entry.model.id.toLowerCase();
255
+ return needle.length > 0 && (candidate.includes(needle) || needle.includes(candidate));
256
+ })
257
+ .slice(0, 10)
258
+ .map((entry) => `${entry.provider.id}/${entry.model.id}`);
259
+ }
260
+ hasConfiguredKey(providerId) {
261
+ const provider = this.providers.get(providerId);
262
+ return provider ? this.keys.hasConfiguredKey(provider) : false;
263
+ }
264
+ async resolveApiKey(providerId) {
265
+ const provider = this.providers.get(providerId);
266
+ if (!provider)
267
+ return { apiKey: undefined, source: "none" };
268
+ return this.keys.resolve(provider);
269
+ }
270
+ getApi(api) {
271
+ return this.apis.get(api);
272
+ }
273
+ }
274
+ /** 注册表若是 ProviderRegistry(或同形状),报告 baseUrl 来自的环境变量;否则 undefined。 */
275
+ export function baseUrlEnvOf(registry, providerId) {
276
+ const r = registry;
277
+ return typeof r.baseUrlEnv === "function" ? r.baseUrlEnv(providerId) : undefined;
278
+ }
279
+ /**
280
+ * 模型枚举:本地服务(`ama models list --provider ollama|lmstudio`)与 `ama models discover`
281
+ * (第三波 §2.3)共用。ollama 走 `/api/tags`,其它走 OpenAI 兼容的 `GET {baseUrl}/models`;
282
+ * `url` / `headers` 由调用方给出时原样使用(远端中转要带鉴权头)。失败抛错由调用方展示。
283
+ */
284
+ export async function discoverLocalModels(provider, options = {}) {
285
+ const signal = AbortSignal.any([
286
+ options.signal ?? new AbortController().signal,
287
+ AbortSignal.timeout(options.timeoutMs ?? 5000),
288
+ ]);
289
+ const base = provider.baseUrl.replace(/\/+$/, "");
290
+ const isOllama = options.url === undefined && (provider.id === "ollama" || base.includes(":11434"));
291
+ const url = options.url ?? (isOllama ? `${base.replace(/\/v1$/, "")}/api/tags` : `${base}/models`);
292
+ const response = await fetch(url, {
293
+ signal,
294
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
295
+ });
296
+ if (!response.ok)
297
+ throw new Error(`${response.status} listing models from ${url}`);
298
+ const body = (await response.json());
299
+ const ids = isOllama
300
+ ? (body.models ?? []).map((m) => m.name)
301
+ : (body.data ?? []).map((m) => m.id);
302
+ return [
303
+ ...new Set(ids.filter((id) => typeof id === "string" && id.length > 0)),
304
+ ].map((id) => withCustomDefaults({ id }, provider.id, provider.api));
305
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Server-Sent Events 解析(设计 §1.2 ai/sse.ts):event / data / id / 多行 data / 注释行 /
3
+ * CRLF、CR、LF 三种行尾 / UTF-8 跨块 / 首字节 BOM。
4
+ *
5
+ * 规则取自 WHATWG「Interpreting an event stream」:空行分派事件;`:` 开头是注释;字段名后的
6
+ * 第一个空格去掉;多条 data 以 LF 连接。为兼容不规范服务端,流结束时未以空行收尾的最后一个
7
+ * 事件也会分派。
8
+ */
9
+ export interface SseEvent {
10
+ /** `event:` 字段;缺省时为 undefined(规范上视为 "message")。 */
11
+ event: string | undefined;
12
+ data: string;
13
+ id: string | undefined;
14
+ }
15
+ export declare class SseParser {
16
+ private buffer;
17
+ private event;
18
+ private data;
19
+ private id;
20
+ private hasData;
21
+ private bomChecked;
22
+ /** 上一块以 CR 结尾:下一块开头的 LF 属于同一个行尾。 */
23
+ private pendingCr;
24
+ /** 喂入一段文本,返回其中完整分派的事件。 */
25
+ feed(chunk: string): SseEvent[];
26
+ /** 流结束:处理残留行,并分派未以空行收尾的事件。 */
27
+ flush(): SseEvent[];
28
+ private processLine;
29
+ private dispatch;
30
+ }
31
+ /** 从字节流读 SSE 事件;`signal` 中止时停止读取(调用方据此产出 aborted)。 */
32
+ export declare function readSseEvents(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<SseEvent>;