@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,316 @@
1
+ # 供应商与模型
2
+
3
+ 内置供应商、模型引用、API Key、自定义供应商与中转站、各协议的 compat 开关,以及缓存。设计依据见 [design.md](design.md) §3、§9.1。
4
+
5
+ ## 内置供应商
6
+
7
+ | id | 协议 | baseUrl | API Key 环境变量(顺序) |
8
+ | ------------ | ----------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------ |
9
+ | `anthropic` | anthropic-messages | `https://api.anthropic.com` | `ANTHROPIC_API_KEY`、`AMA_API_KEY_ANTHROPIC` |
10
+ | `openai` | openai-completions(推理模型 openai-responses) | `https://api.openai.com/v1` | `OPENAI_API_KEY`、`AMA_API_KEY_OPENAI` |
11
+ | `google` | google-generative-ai | `https://generativelanguage.googleapis.com/v1beta` | `GEMINI_API_KEY`、`GOOGLE_API_KEY`、`AMA_API_KEY_GOOGLE` |
12
+ | `deepseek` | openai-completions | `https://api.deepseek.com` | `DEEPSEEK_API_KEY`、`AMA_API_KEY_DEEPSEEK` |
13
+ | `moonshot` | openai-completions | `https://api.moonshot.cn/v1` | `MOONSHOT_API_KEY`、`KIMI_API_KEY`、`AMA_API_KEY_MOONSHOT` |
14
+ | `zhipu` | openai-completions | `https://open.bigmodel.cn/api/paas/v4` | `ZHIPU_API_KEY`、`ZAI_API_KEY`、`AMA_API_KEY_ZHIPU` |
15
+ | `dashscope` | openai-completions | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `DASHSCOPE_API_KEY`、`QWEN_API_KEY`、`AMA_API_KEY_DASHSCOPE` |
16
+ | `openrouter` | openai-completions | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY`、`AMA_API_KEY_OPENROUTER` |
17
+ | `groq` | openai-completions | `https://api.groq.com/openai/v1` | `GROQ_API_KEY`、`AMA_API_KEY_GROQ` |
18
+ | `xai` | openai-completions(目录模型 openai-responses) | `https://api.x.ai/v1` | `XAI_API_KEY`、`AMA_API_KEY_XAI` |
19
+ | `mistral` | openai-completions | `https://api.mistral.ai/v1` | `MISTRAL_API_KEY`、`AMA_API_KEY_MISTRAL` |
20
+ | `ollama` | openai-completions | `http://127.0.0.1:11434/v1` | 可无(`OLLAMA_API_KEY`) |
21
+ | `lmstudio` | openai-completions | `http://127.0.0.1:1234/v1` | 可无 |
22
+
23
+ 另有测试用供应商 `fake`(模型 `fake/echo`、`fake/reasoning`),见下文。
24
+
25
+ 协议列是供应商级缺省;目录条目可以用 `api` 覆盖(openai 的推理模型与 xai 的目录模型走
26
+ `openai-responses`,非推理的 `gpt-4.1`、`gpt-4o*` 仍走 Completions)。
27
+
28
+ ## 模型引用
29
+
30
+ `provider/model-id`,例如 `deepseek/deepseek-v4-pro`、`openrouter/anthropic/claude-sonnet-5.5`。
31
+ 不带供应商前缀时在全部目录里唯一匹配;多家同名时只看已配置 key 的供应商,仍不唯一则报错并列出
32
+ 候选。模型表为空的供应商(ollama、lmstudio、没写 `models` 的自定义供应商)与 baseUrl 指向非官方
33
+ 主机的内置供应商(见「接入中转站」)接受任意 model id。
34
+
35
+ ## API Key 发现顺序
36
+
37
+ 1. `--api-key`(只对 `--model` 指定的供应商)
38
+ 2. `--auth-file` / profile 的 `authFile`
39
+ 3. `~/.config/ama/auth.json`(权限不是 0600 时警告但照用)
40
+ 4. `config.json` 的 `providers.<id>.apiKey`:支持 `$ENV`、`${ENV}`、`!command`,`$$` 表示字面 `$`
41
+ 5. 环境变量(上表顺序;profile `authEnv: false` 时跳过)
42
+ 6. 本地服务(`requiresApiKey: false`)无 key 也能用
43
+
44
+ `auth.json` 的 `apiKey` 以 `!` 开头表示执行命令取值(10 秒超时;空输出或非零退出视为未配置)。
45
+
46
+ ## 自定义供应商
47
+
48
+ ```json
49
+ {
50
+ "providers": {
51
+ "my-proxy": {
52
+ "baseUrl": "https://proxy.example/v1",
53
+ "apiKey": "$MY_PROXY_KEY",
54
+ "models": [{ "id": "gpt-x", "contextWindow": 128000, "maxTokens": 16384, "reasoning": true }],
55
+ "compat": { "maxTokensField": "max_tokens" }
56
+ },
57
+ "deepseek": { "modelOverrides": [{ "id": "deepseek-flash", "contextWindow": 131072 }] }
58
+ }
59
+ }
60
+ ```
61
+
62
+ - `api` 缺省 `openai-completions`;自定义模型缺省 `maxTokens: 8192`、`reasoning: false`、
63
+ `input: ["text"]`;不猜 `contextWindow`(缺省关自动压缩)。
64
+ - `models[]` 同 id 整条替换、新 id 追加;`modelOverrides[]` 只改已有模型的元数据。
65
+
66
+ ## 接入中转站
67
+
68
+ 同一个中转站下,不同模型支持的协议常常不同(有的三种都行,有的只有 Chat 与 Messages,有的只有
69
+ Responses)。一个供应商就够:协议写在模型上。
70
+
71
+ ```sh
72
+ export PACKY_API_KEY=sk-...
73
+ ```
74
+
75
+ ```json
76
+ {
77
+ "providers": {
78
+ "packy": {
79
+ "baseUrl": "https://proxy.example/v1",
80
+ "apiKey": "$PACKY_API_KEY",
81
+ "models": [
82
+ { "id": "deepseek-v4-flash" },
83
+ { "id": "grok-4.7", "api": "openai-responses" },
84
+ { "id": "MiniMax-M2.7", "api": "anthropic-messages" }
85
+ ]
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ - 模型的 `api` 缺省沿用供应商的(这里是 `openai-completions`);`modelOverrides[]` 也可以改 `api`。
92
+ - 三种协议共用一个 `baseUrl`:Completions / Responses 拼 `/chat/completions`、`/responses`;
93
+ Messages 在 baseUrl 以 `/v1` 结尾时拼 `/messages`,否则 `/v1/messages`。
94
+ - 不想手写 `models`:`ama models discover packy` 列出中转站的模型(`GET {baseUrl}/models`);
95
+ `--probe` 对每个模型依次试供应商协议、completions、responses、messages 的最小请求,记第一个成功
96
+ 的(每模型最多 3 次,`--limit` 限制探测的模型数,缺省 30,执行前打印预估,401 / 403 / 429 即停);
97
+ `--write` 把结果合并进用户级 `config.json`(已有同 id 不覆盖,只写 `id` 与和供应商不同的 `api`,
98
+ 原文件备份为 `config.json.bak`)。写入的条目没有 `contextWindow`,自动压缩随之关闭,需要时手动补。
99
+
100
+ ```sh
101
+ ama models discover packy --probe --write --limit 8
102
+ ama -p "hi" --model packy/grok-4.7
103
+ ```
104
+
105
+ 零配置:内置 `openai` / `anthropic` 识别 `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL`(OpenAI SDK 与
106
+ Claude Code 的通行约定),优先级低于 config 与 auth.json 的 `baseUrl`,profile `authEnv: false` 时
107
+ 不读。baseUrl 不在官方主机时,目录外的 model id 也接受,compat 按保守缺省(不发
108
+ `prompt_cache_key`)。`ama config show` 的「供应商」节与 `ama doctor` 标出 baseUrl 来自哪个变量;
109
+ 零配置挑的缺省模型来自官方目录,中转站未必有,用 `--model` 或 `defaultModel` 指定。
110
+
111
+ ```sh
112
+ OPENAI_BASE_URL=https://proxy.example/v1 OPENAI_API_KEY=$PACKY_API_KEY ama -p "hi" --model openai/qwen3.8-flash
113
+ ```
114
+
115
+ 接好之后:`ama models check packy/<id>` 发一次最小请求确认连通;`ama models cache-probe packy/<id>` 看这个端点报不报缓存(见下节「缓存」),中转上不报缓存的模型按建议设 `compat.cacheReporting: "silent"`,状态栏就显示「未报告」而不是 0%。
116
+
117
+ ## OpenAI 兼容线的 compat
118
+
119
+ 推断顺序:保守缺省 ← 推断表(provider id,其次 baseUrl 子串)← `provider.compat` ← `model.compat`。
120
+
121
+ | 开关 | 作用 |
122
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
123
+ | `maxTokensField` | `max_tokens` 或 `max_completion_tokens` |
124
+ | `supportsDeveloperRole` | 推理模型的系统提示用 `developer` 角色 |
125
+ | `supportsUsageInStreaming` | 发 `stream_options.include_usage` |
126
+ | `supportsFinishReason` | false 时忽略 finish_reason,按内容推断停止原因 |
127
+ | `supportsReasoningEffort` | 发 `reasoning_effort` |
128
+ | `thinkingFormat` | `openai` / `openrouter`(`reasoning.effort`)/ `deepseek`、`zai`(`thinking.type`)/ `qwen`(`enable_thinking`)/ `none` |
129
+ | `thinkingTokenBudgetField` | 预算字段名(DashScope:`thinking_budget`) |
130
+ | `requiresReasoningContentOnAssistantMessages` | 推理模型的历史助手消息带 `reasoning_content`(DeepSeek) |
131
+ | `requiresToolResultName` | 工具结果消息带 `name`(Mistral) |
132
+ | `requiresAssistantAfterToolResult` | 工具结果后紧跟用户消息时插入一条助手消息 |
133
+ | `supportsMidConvoSystemMessages` | 后续系统提示补丁按位置作为 system 消息插回 |
134
+ | `cacheControlFormat` | `anthropic`:在 system、最后一个工具、最后一条 user/tool 消息上打 `cache_control` |
135
+ | `supportsStrictTools` | 对严格兼容的工具 schema 发 `strict: true` |
136
+ | `supportsStore` | 发 `store: false` |
137
+
138
+ compat 只记录**已验证**的差异;新增条目请附文档链接或真实样本。
139
+
140
+ ## Responses 与 Gemini 的 compat
141
+
142
+ | 协议 | 开关 | 作用 | 缺省 |
143
+ | ---------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
144
+ | `openai-responses` | `supportsReasoningSummary` | `reasoning.summary: "auto"`(思考块有文字) | OpenAI 官方开;xAI 与其它关 |
145
+ | `openai-responses` | `supportsStore` | 发 `store: false`;推理模型同时要 `include: ["reasoning.encrypted_content"]`,多轮回放加密 reasoning item | OpenAI 官方、xAI 开;其它关 |
146
+ | `google-generative-ai` | `supportsThoughtSignature` | 同模型回放 `thoughtSignature`(思考、文本、functionCall part) | 开 |
147
+ | `google-generative-ai` | `supportsFunctionResponseParts` | 工具结果图片放进 `functionResponse.parts`;关时另起一个 user 回合 | Gemini 3 起开;Gemini 2.x 与非 Gemini 命名关 |
148
+
149
+ - Responses:系统提示放 `instructions`;缓存字段见下节「缓存」;思考 off 只在映射表给了 off 的字串(如 `"none"`)时发
150
+ `reasoning.effort`,否则交给服务端缺省。
151
+ - Gemini:映射值是字串(或 Gemini 3 族且未映射)→ 离散 `thinkingLevel`(`LOW` / `HIGH`…);映射值是
152
+ 数字或其它模型 → `thinkingBudget`(`-1` 动态);off → `thinkingBudget: 0`。隐式缓存自动生效,
153
+ `cachedContentTokenCount` 计入 `cacheRead`。
154
+
155
+ ## 缓存
156
+
157
+ 长任务的主要用量是缓存读取:前缀一旦变化,此后每次请求都要按全价重读。ama 分三层处理缓存:**协议层**按各家写法打断点、发缓存键与保留层级,并标记响应里有没有缓存字段;**会话层**记录每次请求的前缀指纹,检测未命中、判定端点报不报缓存、在长工具运行期间保温;**展示层**是状态栏、`/session`、RPC 统计与 `ama models cache-probe`(界面怎么读见 [tui.md](tui.md)「缓存与上下文」)。
158
+
159
+ 前缀稳定由组装保证:系统提示节顺序固定、不含时间戳,工具按名排序,会话中途的变化只以 system 补丁追加在末尾([session-format.md](session-format.md)「消息」)。
160
+
161
+ ### 请求字段
162
+
163
+ | 协议 | 字段 | 条件 |
164
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
165
+ | `anthropic-messages` | 三断点 `cache_control`(最后一条 user、system 末、最后一个工具) | `cacheRetention` 不是 `none` |
166
+ | `anthropic-messages` | `ttl: "1h"` | `long` 且 `supportsLongCacheRetention`;否则按 5m |
167
+ | `openai-completions` | `prompt_cache_key = sessionId`(截 64 字符) | `sendPromptCacheKey` 且不是 `none` |
168
+ | `openai-completions` | `prompt_cache_retention: "24h"` | `long` 且 `supportsLongCacheRetention` |
169
+ | `openai-completions`(`anthropic/*` 等) | `cache_control`(`cacheControlFormat: "anthropic"`),`long` 时带 `ttl: "1h"` | 同 Anthropic |
170
+ | `openai-responses` | `prompt_cache_key` | 同 Completions |
171
+ | `openai-responses` | `prompt_cache_options: { ttl: "30m" }`,否则 `prompt_cache_retention: "24h"` | `long` 且 `supportsExplicitPromptCacheMode`;否则 `long` 且 `supportsLongCacheRetention` |
172
+ | OpenAI 两条 | 亲和头 `x-session-affinity` + 每请求 `x-client-request-id`(OpenRouter:`x-session-id`) | `sendSessionAffinityHeaders` 且有 sessionId |
173
+ | `google-generative-ai` | 无(隐式缓存) | — |
174
+ | 全部 | `toolChoice: "none"` → 各家的「禁止调用工具」写法 | 请求带工具时(摘要续写**不用**,见「压缩摘要续写」) |
175
+
176
+ 保留层级:`StreamOptions.cacheRetention` 优先;未指定时读 `AMA_CACHE_RETENTION=none|short|long`;都没有为
177
+ `short`。Anthropic 请求体最后做 TTL 顺序校验(tools → system → messages 里 5m 之后出现 1h 则全部降为 5m)。
178
+ Anthropic 的 `baseUrl` 以 `/v1` 结尾时请求 `{baseUrl}/messages`,不会拼成 `/v1/v1/messages`。
179
+
180
+ ### 兼容开关(`providers.<id>.compat` 或模型级 `compat`)
181
+
182
+ | 开关 | 作用 | 缺省 |
183
+ | --------------------------------- | ------------------------------------------------------- | ----------------------------------------- |
184
+ | `sendPromptCacheKey` | 发 `prompt_cache_key` | 请求主机是 `api.openai.com` 时开,其余关 |
185
+ | `sendSessionAffinityHeaders` | 发亲和头 | 关(含 OpenRouter,未实测) |
186
+ | `supportsLongCacheRetention` | `long` 可用(Anthropic 1h、OpenAI 24h);否则降为 short | `api.openai.com` / `api.anthropic.com` 开 |
187
+ | `supportsExplicitPromptCacheMode` | Responses 的 `prompt_cache_options`(30m) | 关 |
188
+ | `cacheReporting` | `auto` / `silent` / `reported`:强制「是否报缓存」三态 | `auto` |
189
+
190
+ 推断只看最终请求的主机名,不看 provider id:用 `OPENAI_BASE_URL` 或自定义 `baseUrl` 把 `openai` 指到中转时
191
+ 按中转处理。缺省只对官方端点开,是因为中转上实测「接受但未见收益」或「收下但不生效」(下表)。
192
+
193
+ **400 自动剥离**:端点以 400 拒收并在错误体里点名 `prompt_cache_key` / `prompt_cache_retention` /
194
+ `prompt_cache_options` / `cache_control` 时,ama 把 `provider/model` 记入进程内的剥离表,去掉这些字段重发一次
195
+ (仍只有一个终止事件),提示一次建议写哪个开关;同一进程里之后的请求直接不带。
196
+
197
+ ### usage 与 `cacheReported`
198
+
199
+ 原始 usage 里出现任一缓存字段(即使为 0)→ `Usage.cacheReported = true`,都没有 → `false`:Completions 认
200
+ `prompt_tokens_details.cached_tokens` / `cache_write_tokens`、`prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`、
201
+ 顶层 `cached_tokens`;Responses 认 `input_tokens_details.cached_tokens`;Anthropic 认
202
+ `cache_read_input_tokens` / `cache_creation_input_tokens`;Google 认 `cachedContentTokenCount`(隐式缓存未命中时
203
+ 常常不给)。字段存在但一直为 0 的端点由会话层按连续 3 次判 `silent`。
204
+
205
+ ### 模型目录 `promptCache`
206
+
207
+ 只写有公开依据的值(秒 / token):Anthropic 全部 `short 300 / long 3600`,`minTokens` 按模型 512–4096;OpenAI
208
+ 全部 `short 300 / long 86400 / minTokens 1024`;Kimi 全部 `short 300`。DeepSeek、智谱、通义、Groq、xAI、Mistral、
209
+ OpenRouter、Google 没有承诺的 TTL,留空(不保温,归因按隐式缓存 10 分钟估)。可在 `models[]` /
210
+ `modelOverrides[]` 里自填。
211
+
212
+ ### 会话层:指纹、未命中与三态
213
+
214
+ 每次真实请求在内存里记一条记录:前缀指纹(system 与工具表各取 sha256 前 16 位 hex,加 `provider/model`)、`promptTokens`(input + cacheRead + cacheWrite)、用量与发出时刻。下一次请求与上一条比对:
215
+
216
+ - **未命中**:`missed = min(上次前缀, 本次前缀) − 本次 cacheRead`,低于噪声下限(`max(1024, promptCache.minTokens, 端点缓存粒度)`)不计——有的端点按块报缓存读(DeepSeek 经中转是 2048 一块),粒度取同一端点(供应商 + 主机 + 模型)观察到的非零 cacheRead 的最大公约数,至少 2 个样本且在 128–8192 之间才采信,只在内存、进程内跨会话复用;相对比例超过随规模自适应的门槛(约 `0.10 × √(100k / 前缀)`,夹在 2%–30%),或绝对值 ≥ 20 000 才记一次。重计费金额按本条实付单价与读价之差估算,模型无价格时只有 token。
217
+ - **原因**(按顺序判定):system / 工具表指纹变了 → `prefix_changed`(`detail` 说明哪段,多半是宿主中途注册工具或 Hook 上下文变化);模型变了 → `model_changed`;间隔超过 TTL → `idle`(目录没有 TTL 的隐式缓存按 10 分钟估);两次请求之间 `task` 子任务占了间隔的 80% 以上 → `subtask`;其余 → `evicted`(服务端淘汰)。
218
+ - **不算未命中**:压缩、分支摘要、档一裁剪之后的首个请求(上下文合法地变了);前缀低于最小可缓存长度。切换模型**不**豁免。
219
+ - **三态**:按 `(provider, baseUrl 主机名, model)` 在进程内维护。`unknown`:还没有足够长的可比请求;`reported`:出现过 cacheRead 或 cacheWrite > 0;`silent`:连续 3 个可比请求(前缀 ≥ minTokens、指纹未变、间隔 < TTL)读写都是 0,或 `compat.cacheReporting: "silent"`。只有 `reported` 时显示命中率、检测未命中并保温;`unknown` / `silent` 的请求不进命中率分母,界面显示 `—` / `未报告` 而不是 0%。
220
+
221
+ `task` 子会话有自己的记录链与统计,`/session` 的「子任务」行汇总;fork 出的会话沿用根会话 id 作 `prompt_cache_key`(只是路由提示)。
222
+
223
+ ### 保温
224
+
225
+ 工具长时间运行(长测试、`task` 子任务、codemode 脚本)时,前缀可能在下一次请求前过期。保温在 TTL 到期前重放上一次真实请求(同模型、同上下文,`maxTokens: 1`),只买一次读价,把缓存续上。
226
+
227
+ | 项 | 规则 |
228
+ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
229
+ | 模式 | `off`;`streaming`(缺省,只在运行中、也就是工具执行期间);`idle`(运行结束后的空闲期也保温,适合贵模型) |
230
+ | 前提 | 端点为 `reported`;模型目录有 `promptCache.short`(TTL > 10 秒);请求的 `cacheRetention` 不是 `none`;请求体没被 `onPayload` 替换;Anthropic 开思考且思考预算随 `max_tokens` 推导的不保温 |
231
+ | 时机 | 从上一次请求**发出**时刻起算,`max(1s, min(0.9·TTL, TTL − 10s))` 后发;计时器迟到超过截止(睡眠、事件循环阻塞)直接停 |
232
+ | 经济性 | `p · missCost − warmCost ≥ cache.minSavingsUsd`(缺省 $0.05)才发,`p` 在 streaming 为 1、idle 为 0.15;缺价格不发 |
233
+ | 上限 | streaming 60 分钟、idle 30 分钟;连续 2 次保温零命中即停 |
234
+ | 取消 | 换模型、换思考级别、压缩、`/tree`、退出时取消,下一次真实请求再开始 |
235
+ | 记账 | 成功的保温追加 `usage{kind:"cache_warm"}` 条目(不进上下文),计入 `/session` 费用与 RPC 统计;事件 `cache_warm{scheduled|sent|stopped}` |
236
+
237
+ 宿主可以经 `api.cache.onWarmingDecision` 否决或强制每一次保温([host-api.md](host-api.md)「缓存保温」)。子会话缺省不保温(`cache.warmSubagents: true` 打开)。按目录价格估算,长工具运行期间几乎总是划算;空闲保温只对贵模型、长前缀划算。
238
+
239
+ ### 压缩摘要续写
240
+
241
+ 档二压缩的摘要请求不再另起一段新对话,而是在与上一次真实请求逐字节相同的前缀后面追加一条摘要指令(`cacheRetention: "short"`),所以整段历史按读价计费。续写请求**不发 `tool_choice`**:实测中转与 Kimi 在 `tool_choice: "none"` 时渲染的提示不带工具定义,前缀在工具段断开、读不到缓存;Anthropic 也写明改动 tool_choice 会让消息缓存失效。工具表照常发,「不要调用工具、只输出摘要」写在末尾指令里。响应为空、被截断、含工具调用或请求出错时,回落为独立的摘要请求(`cacheRetention: "none"`)并记 warning。
242
+
243
+ ### 配置
244
+
245
+ ```json
246
+ {
247
+ "cache": {
248
+ "warming": "streaming",
249
+ "retention": "short",
250
+ "minSavingsUsd": 0.05,
251
+ "missNotices": true,
252
+ "warmSubagents": false
253
+ }
254
+ }
255
+ ```
256
+
257
+ | 键 | 缺省 | 说明 |
258
+ | --------------- | ----------- | --------------------------------------------------------------------------------------------- |
259
+ | `warming` | `streaming` | `off` / `streaming` / `idle`;环境变量 `AMA_CACHE_WARMING` 覆盖;`/cache warm …` 本会话内切换 |
260
+ | `retention` | `short` | `none` / `short` / `long`;环境变量 `AMA_CACHE_RETENTION` 覆盖 |
261
+ | `minSavingsUsd` | `0.05` | 保温的最低期望节省(美元) |
262
+ | `missNotices` | `true` | 消息区的未命中与上下文余量提示(统计不受影响) |
263
+ | `warmSubagents` | `false` | `task` 子会话也保温 |
264
+
265
+ 整段只认用户级与 profile 的 `config.json`,项目级忽略并 warning。供应商级开关在 `providers.<id>.compat`(上文「兼容开关」),TTL 在模型的 `promptCache`。
266
+
267
+ ### `ama models cache-probe`
268
+
269
+ ```sh
270
+ ama models cache-probe <provider/id> [--tokens 2048] [--gap-ms 3000] [--json] [--yes]
271
+ ```
272
+
273
+ 用一个确定性的固定前缀(约 `--tokens` token)+ `Reply with: ok`,`maxTokens: 16`,相隔 `--gap-ms` 发两次,判定:
274
+
275
+ - `reported`:第二次 cacheRead ≥ 前缀的 50%;目录没有 `promptCache` 时建议自填 `promptCache.short` 以启用保温;
276
+ - `silent`:两次读写都是 0;建议设 `compat.cacheReporting: "silent"`。响应里有缓存字段但恒为 0 时另提示可能是写入延迟(同一中转的 kimi-k2.5 间隔 3 秒两次都是 0、间隔 8 秒第二次读满前缀),可加大 `--gap-ms` 重试;
277
+ - `inconclusive`:读到一点或只有写入,多半是缓存粒度或 TTL 问题。
278
+
279
+ 输出两次请求的 input / cacheRead / cacheWrite 与该协议读取的 usage 字段名。这是计费动作:执行前打印预估(无价格显示 `$?`),交互终端问一次 y/N,非交互环境必须带 `--yes`(否则退出 2);`--json` 时预估写 stderr,stdout 只有结果对象。
280
+
281
+ ### 中转实测(2026-10-02)
282
+
283
+ 一家同时提供 Chat / Responses / Messages 三种接口的测试中转,固定前缀约 8.6k–10.9k token,同一前缀相隔 2–3 秒
284
+ 发两次(共 38 次请求)。
285
+
286
+ | 接口 / 模型 | 发送的缓存参数 | 结果 | 第二次 cacheRead / 前缀 | usage 里的缓存字段(原始形状) |
287
+ | ------------------------------------------------- | ------------------------------------------------- | ----------------------- | -------------------------- | ----------------------------------------------------------------------------- |
288
+ | Chat · kimi-k2.5 | 无 | 200 | 8576 / 8597 | `prompt_tokens_details.cached_tokens`(首个请求为 0) |
289
+ | Chat · kimi-k2.5 | `prompt_cache_key`;再加亲和头 | 200,接受 | 8576 / 8597(无提升) | 同上 |
290
+ | Chat · deepseek-v4-flash | `prompt_cache_key` + `x-session-affinity` | 200,接受 | 9472 / 9767 | `prompt_tokens_details.cached_tokens` |
291
+ | Chat · qwen3.8-flash | 同上 | 200,接受 | 10240 / 10400 | 同上 |
292
+ | Chat · glm-5 | 同上 | 200,接受 | 8704 / 9040 | 同上 |
293
+ | Chat · MiniMax-M2.7 | 同上 | 200,接受 | 9389 / 9700 | 同上 |
294
+ | Responses · qwen3.8-flash | `prompt_cache_key` + `prompt_cache_retention` | 200,接受 | 10240 / 10432 | `input_tokens_details.cached_tokens` |
295
+ | Responses · qwen3.8-flash | `prompt_cache_options: {ttl:"30m"}` | 200,接受 | 同上 | 同上 |
296
+ | Responses · grok-4.7 | 同上两组 | 200,接受 | 1152 / 10929 | 同上 |
297
+ | Responses · deepseek-v4-flash | `prompt_cache_retention` / `prompt_cache_options` | **400** `unknown field` | — | 去掉后 200;`input_tokens_details.cached_tokens` |
298
+ | Messages · kimi-k2.5、qwen3.8-flash | `cache_control` + `ttl:"1h"` | 200,接受 | 9464 / 9476、10385 / 10400 | `cache_creation.ephemeral_5m_input_tokens` 有值、无 1h 字段:**1h 被当作 5m** |
299
+ | Messages · MiniMax-M2.7、deepseek-v4-flash、glm-5 | 同上 | 200,接受 | 9403、8192、8704 | `cache_creation_input_tokens` 恒 0,读命中照常(端点自管的隐式缓存) |
300
+
301
+ 结论与缺省值:
302
+
303
+ - `prompt_cache_key`、亲和头在中转上都被接受,但未见命中提升 → `sendPromptCacheKey`、
304
+ `sendSessionAffinityHeaders` 对非官方端点缺省关;需要时自行打开(400 剥离兜底)。
305
+ - 1h 保留在中转的 Messages 接口上被收下但按 5m 写入,Responses 的长保留字段在部分上游 400 →
306
+ `supportsLongCacheRetention` 只对官方端点缺省开,`supportsExplicitPromptCacheMode` 缺省关。
307
+ - 实测的五家在 Chat 接口上都报缓存字段(前一版调研里 DeepSeek / GLM 报 0 的现象本次未复现),它们首个
308
+ 请求的 `cached_tokens: 0` 正是「字段存在但为 0」,`cacheReported` 为 true。
309
+ - `/v1` 去重与 `toolChoice: "none"` 经 ama 协议层实发验证:Messages 请求落在 `/v1/messages`,三种接口都接受
310
+ `tool_choice: none` 且未产生工具调用。
311
+
312
+ ## 测试用 fake 供应商
313
+
314
+ `--provider fake --model fake/echo`:回显最后一条用户消息。设 `AMA_FAKE_SCRIPT=<file.json>` 后
315
+ 按脚本第 n 次调用产出文本、思考、工具调用、429、溢出、断流、延迟,脚本格式见
316
+ `src/ai/fake/fake-script.ts`,示例在 `test/fixtures/scripts/`。
package/docs/rpc.md ADDED
@@ -0,0 +1,253 @@
1
+ # RPC 协议(stdio JSONL)
2
+
3
+ `ama --mode rpc` 从 stdin 读命令、向 stdout 写响应与事件,每行一个 JSON。类型定义在 `@armadra/agent/rpc`(`src/rpc.ts`),实现在 `src/modes/rpc/`。`ama -p --output-format stream-json` 输出的事件与这里同形状。设计依据见 [design.md](design.md) §13.2。
4
+
5
+ ## 线路
6
+
7
+ - 读:只按 `\n` 切行(去掉行尾 `\r`,空行跳过),不按 U+2028 / U+2029 切;多字节 UTF-8 跨块拼接;stdin 结束时最后一段不带换行的行也算一行。
8
+ - 写:每行是一个 `JSON.stringify` 结果,U+2028 / U+2029 转义为 `\u2028` / `\u2029`,`Error` 序列化为 `{ name, message }`,bigint 转字符串,图片 base64 不截断。大行按 64 KiB 分片写出并等待背压,行与行不交错。
9
+ - stdout 只有协议行;日志、宿主通知的人读副本写 stderr。
10
+
11
+ ## 握手
12
+
13
+ 启动后先发 `hello`,再发当前会话的 `session_start`:
14
+
15
+ ```json
16
+ {"type":"hello","protocolVersion":1,"agent":"ama","version":"0.1.0","capabilities":["approvals","images","hooks"]}
17
+ {"type":"session_start","sessionId":"…","cwd":"/work","reason":"startup"}
18
+ ```
19
+
20
+ `protocolVersion` 是 `RPC_PROTOCOL_VERSION`(当前 1)。`capabilities` 列出服务端支持的能力;客户端要接审批时用 `set_client_capabilities` 声明(见「审批」)。
21
+
22
+ ## 命令与响应
23
+
24
+ 命令形状 `{ "id"?: string, "type": <命令名>, ...参数 }`。响应:
25
+
26
+ ```json
27
+ { "id": "1", "type": "response", "command": "prompt", "success": true, "data": { "disposition": "started" } }
28
+ { "id": "2", "type": "response", "command": "set_model", "success": false, "error": "…", "code": "model_not_found" }
29
+ ```
30
+
31
+ - 响应带回请求的 `id`(字符串才带)。命令并发处理:`prompt` 不阻塞后续命令,所以响应顺序不一定与请求顺序相同,用 `id` 对应。
32
+ - 失败时 `error` 是人读文本,`code` 是 `AmaError.code`(若有)。未知命令 → `code: "invalid_arguments"`。
33
+ - 一行不是合法 JSON 或缺 `type` → `{ "type": "response", "command": "parse", "success": false, "error": … }`,没有 `id`。
34
+ - 需要会话实现扩展方法的命令(下表标 †)在非 `AgentSessionImpl` 会话上返回 `code: "not_implemented"`;CLI 与 SDK 建出的会话都是 `AgentSessionImpl`。
35
+
36
+ ### 提示
37
+
38
+ | 命令 | 参数 | `data` |
39
+ | ------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
40
+ | `prompt` | `message: string`、`images?: ImageBlock[]`、`streamingBehavior?: "steer" \| "followUp"` | `{ disposition: "started" \| "queued" \| "handled" }` |
41
+ | `steer` | `message`、`images?` | 同上 |
42
+ | `follow_up` | `message`、`images?` | 同上 |
43
+ | `abort` | — | `{}`(回到空闲后应答;不清队列) |
44
+ | `clear_queue` | — | `{ steering: string[], followUp: string[] }`(被清掉的文本) |
45
+
46
+ 提示类命令**不等运行结束**:会话开始运行(`before_agent_start` / `agent_start`)、消息入队或被处理(例如斜杠命令、Hook 阻止)后立刻应答,运行进展走事件。运行中发 `prompt` 且不带 `streamingBehavior` → 失败,`code: "busy"`;带上 `steer` / `followUp` 则入队。应答发出之后的运行失败以 `{"type":"notification","level":"error","message":…}` 报告。
47
+
48
+ ### 状态
49
+
50
+ | 命令 | 参数 | `data` |
51
+ | ------------------------- | ---- | ---------------------------------------------------- |
52
+ | `get_state` | — | `SessionState`(下表) |
53
+ | `get_messages` | — | `{ messages: AgentMessage[] }`(投影后的上下文消息) |
54
+ | `get_last_assistant_text` | — | `{ text: string \| null }` |
55
+ | `get_session_stats` | — | `SessionStats`(见「会话统计」) |
56
+
57
+ `SessionState`:`isStreaming`、`isCompacting`、`isRetrying`、`model`(`{ provider, id }` 或缺省)、`thinkingLevel`、`permissionMode`、`sessionId`、`sessionFile`、`cwd`、`sessionName`、`messageCount`、`pendingMessageCount`、`steeringMode`、`followUpMode`、`autoCompaction`、`autoRetry`。
58
+
59
+ ### 模型
60
+
61
+ | 命令 | 参数 | `data` |
62
+ | ------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- |
63
+ | `set_model` | `provider: string`、`modelId: string` | `{ model: { provider, id } }` |
64
+ | `get_available_models` | — | `{ models: RpcModelInfo[] }` |
65
+ | `set_thinking_level` | `level: off \| minimal \| low \| medium \| high \| xhigh` | `{ level }` |
66
+ | `get_available_thinking_levels` | — | `{ levels: string[] }`(当前模型支持的级别;无模型时 `["off"]`) |
67
+
68
+ `RpcModelInfo`:`provider`、`id`、`name`、`hasKey`、`keySource`(`cli` / `auth-file` / `config` / `env` / `none`)、`contextWindow?`、`maxTokens`、`reasoning`、`input`(`"text"` / `"image"`)。**密钥不离开进程**:只报有无与来源。
69
+
70
+ ### 队列、压缩、重试
71
+
72
+ | 命令 | 参数 | `data` |
73
+ | ----------------------- | -------------------------------- | ------------------------------------------------------------------------------------------- |
74
+ | `set_steering_mode` † | `mode: "one-at-a-time" \| "all"` | `{ mode }` |
75
+ | `set_follow_up_mode` † | `mode` | `{ mode }` |
76
+ | `compact` | `customInstructions?: string` | `CompactionResult`:`summary`、`firstKeptEntryId`、`tokensBefore`、`tokensAfter?`、`usage?` |
77
+ | `set_auto_compaction` † | `enabled: boolean` | `{ enabled }` |
78
+ | `set_auto_retry` † | `enabled: boolean` | `{ enabled }` |
79
+ | `abort_retry` | — | `{ aborted: boolean }`(正在重试等待时中断本次运行) |
80
+
81
+ ### 会话
82
+
83
+ | 命令 | 参数 | `data` |
84
+ | --------------------- | ------------------------ | ------------------------------------------------------------------------------------------- |
85
+ | `new_session` | `parentSession?: string` | `{ sessionId, sessionFile }` |
86
+ | `switch_session` | `sessionPath: string` | 同上 |
87
+ | `fork` | `entryId: string` | 同上(从该条目之前复制出新会话文件) |
88
+ | `get_entries` † | `since?: string` | `{ entries: SessionEntry[], leafId: string \| null }`;`since` 是 entry id 游标,不含它本身 |
89
+ | `get_tree` † | — | `{ tree: SessionTreeNode[] }`(`{ entry, children, label? }`) |
90
+ | `set_session_name` † | `name: string` | `{ name }` |
91
+ | `get_fork_messages` † | — | `{ messages: { entryId, text }[] }`(活动分支上的用户消息,`fork` 的候选) |
92
+
93
+ 换会话后服务端重新订阅事件,并为新会话发 `session_start`(`reason` 为 `new` / `resume` / `fork`)。`new_session` 目前不使用 `parentSession` 参数。条目形状见 [session-format.md](session-format.md)。
94
+
95
+ ### 审批
96
+
97
+ | 命令 | 参数 | `data` |
98
+ | ------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
99
+ | `set_client_capabilities` | `capabilities: ("approvals" \| "images" \| "hooks")[]` | `{ capabilities }` |
100
+ | `permission_response` | `requestId: string`、`decision: "allow" \| "deny" \| "allow_session"` | `{ accepted: boolean }`(false = 当前没在等这个 id,已暂存,稍后被问到时生效) |
101
+
102
+ ### 工具、权限、发现
103
+
104
+ | 命令 | 参数 | `data` |
105
+ | --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
106
+ | `get_tools` | — | `{ tools: { name, description, parameters, permission, active }[] }`(注册表全部工具,`active` 表示模型当前能看到) |
107
+ | `set_active_tools` | `names: string[]` | `{ names }`(生效后的活动工具名) |
108
+ | `set_permission_mode` | `mode: plan \| default \| auto-edit \| full-auto` | `{ mode }` |
109
+ | `get_commands` | — | `{ commands: { name, description?, source: "builtin" \| "template" \| "skill" }[] }`;Skill 名写作 `skill:<名>` |
110
+ | `get_skills` | — | `{ skills: { name, description, location, … }[] }`(已发现的 Skill;`location` 是 SKILL.md 路径) |
111
+
112
+ 合计 33 条命令,名字即 `RpcCommandMap` 的键。
113
+
114
+ ## 事件
115
+
116
+ 事件就是进程内 `SessionEvent`(`src/agent/types.ts`),只有 `message_update` 在线上换成纯增量。按出现场景分组:
117
+
118
+ | 事件 | 字段 |
119
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
120
+ | `session_start` | `sessionId`、`sessionFile?`、`cwd`、`reason: startup \| resume \| new \| fork` |
121
+ | `session_changed` | `sessionId`、`sessionFile?` |
122
+ | `before_agent_start` | `prompt`(经 UserPromptSubmit Hook 与模板展开之后) |
123
+ | `agent_start` / `turn_start` / `agent_before_settle` | — |
124
+ | `turn_end` | `message`(助手消息)、`toolResults` |
125
+ | `agent_end` | `stopReason`、`willRetry` |
126
+ | `agent_settled` | `warning?`(运行彻底结束,含重试与 followUp) |
127
+ | `message_start` / `message_end` | `message`(`AgentMessage`) |
128
+ | `message_update` | `assistantMessageEvent`、`usage?`(见下) |
129
+ | `tool_execution_start` | `toolCallId`、`toolName`、`args`、`parentToolCallId?` |
130
+ | `tool_execution_update` | `toolCallId`、`toolName`、`partial`(运行中的输出文本)、`parentToolCallId?` |
131
+ | `tool_execution_end` | `toolCallId`、`toolName`、`result`、`isError`、`parentToolCallId?` |
132
+ | `queue_update` | `steering: string[]`、`followUp: string[]` |
133
+ | `compaction_start` | `trigger: threshold \| overflow \| manual` |
134
+ | `compaction_end` | `trigger`、`result?`、`aborted`、`willRetry`、`error?` |
135
+ | `auto_retry_start` | `attempt`、`maxAttempts`、`delayMs`、`errorMessage` |
136
+ | `auto_retry_end` | `success`、`attempt`、`finalError?` |
137
+ | `permission_request` | `requestId`、`toolName`、`input`、`reason: mode \| dangerous \| hook`、`hookReason?`、`timeoutMs`、`preview?` |
138
+ | `permission_resolved` | `requestId`、`decision` |
139
+ | `permission_mode_changed` | `mode` |
140
+ | `model_changed` | `model: { provider, id }` |
141
+ | `thinking_level_changed` | `level` |
142
+ | `entry_appended` | `entry`(刚落盘的会话条目) |
143
+ | `hook_executed` | `event`、`command`、`exitCode`(超时或被信号杀死为 null)、`durationMs` |
144
+ | `cache_miss` | `missedTokens`、`missedCost?`、`reason`、`detail?`、`idleMs` |
145
+ | `cache_warm` | `phase: scheduled \| sent \| stopped`、`nextWarmAt?`、`usage?`、`cost?`、`reason?` |
146
+ | `context_pressure` | `percent`、`threshold: 70 \| 90`、`remainingTokens?`、`estimatedTurnsLeft?` |
147
+
148
+ 另有非会话事件 `{"type":"notification","level":"info"|"warn"|"error","message":…}`:宿主 `ui.notify` 与应答之后的运行失败。
149
+
150
+ `parentToolCallId` 只出现在 codemode 脚本里经 `tools.*` 发起的内层调用上,值是外层 `codemode` 调用的 id;客户端据此折叠显示。内层调用不进转录。
151
+
152
+ ### `message_update` 与消息重建
153
+
154
+ 线上的 `message_update` 去掉了累计消息与 `partial`,只有增量:
155
+
156
+ ```json
157
+ {
158
+ "type": "message_update",
159
+ "assistantMessageEvent": { "type": "text_delta", "contentIndex": 0, "delta": "hello" },
160
+ "usage": { "input": 12, "output": 3, "cacheRead": 0, "cacheWrite": 0, "totalTokens": 15 }
161
+ }
162
+ ```
163
+
164
+ `assistantMessageEvent.type` 取值:`start`、`text_start` / `text_delta` / `text_end`、`thinking_start` / `thinking_delta` / `thinking_end`、`toolcall_start`(带 `id`、`name`)/ `toolcall_delta`(参数 JSON 片段)/ `toolcall_end`(带完整 `toolCall`)、`done`(`reason: stop | length | toolUse`,带最终 `message`)、`error`(`reason: aborted | error`,带最终 `message`)。客户端重建:
165
+
166
+ 1. `message_start` 给出初始助手消息(`content: []`);
167
+ 2. 按 `contentIndex` 维护内容块:`*_start` 建块,`*_delta` 追加文本(工具参数先当字符串拼接),`toolcall_end` 用完整 `toolCall` 替换该块;
168
+ 3. `usage` 是该时刻的最新用量,直接覆盖;
169
+ 4. 以 `message_end`(或 `done` / `error` 的 `message`)为准替换整条消息——重建结果只用于流式显示。
170
+
171
+ ### 会话统计
172
+
173
+ `get_session_stats` 的 `data` 是 `SessionStats`:
174
+
175
+ | 字段 | 说明 |
176
+ | ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
177
+ | `sessionId` / `sessionFile` | 会话标识 |
178
+ | `userMessages` / `assistantMessages` / `toolCalls` / `toolResults` | 计数 |
179
+ | `tokens` | `{ input, output, cacheRead, cacheWrite, total }`,含保温请求的用量 |
180
+ | `cost` | 美元;任一条消息缺成本时缺省(界面显示 `$?`) |
181
+ | `contextTokens` / `contextWindow` / `contextPercent` | 当前上下文估算、窗口与占用(0–100);模型没有窗口时缺省 |
182
+ | `cacheHitRate` | 旧口径命中率:cacheRead /(input + cacheRead + cacheWrite),全部请求进分母 |
183
+ | `cache` | `SessionCacheStats`(下例),会话层缓存控制器接线时才有 |
184
+
185
+ ```json
186
+ {
187
+ "type": "response",
188
+ "command": "get_session_stats",
189
+ "success": true,
190
+ "data": {
191
+ "tokens": { "input": 1177, "output": 64, "cacheRead": 2176, "cacheWrite": 0, "total": 3417 },
192
+ "cacheHitRate": 0.65,
193
+ "cache": {
194
+ "reporting": "reported",
195
+ "lastHitRate": 0.84,
196
+ "hitRate": 0.65,
197
+ "reBilledTokens": 0,
198
+ "reBilledUsd": 0,
199
+ "misses": { "count": 0, "byReason": {} },
200
+ "warming": { "mode": "streaming", "state": "stopped", "reason": "no_ttl" },
201
+ "contextRemainingTokens": 127077,
202
+ "estimatedTurnsLeft": 2443
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ | `cache` 字段 | 说明 |
209
+ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
210
+ | `reporting` | 当前端点(供应商、baseUrl 主机、模型)的三态:`unknown` / `reported` / `silent`;只在内存,同一进程内跨会话复用 |
211
+ | `lastHitRate` / `hitRate` | 最近一次 / 会话累计命中率(0–1);`unknown` / `silent` 时没有 `lastHitRate`,不报缓存的请求不进 `hitRate` 分母 |
212
+ | `reBilledTokens` / `reBilledUsd` | 未命中重计费合计;有无价模型参与时没有 `reBilledUsd` |
213
+ | `misses` | `count` 与 `byReason`(`prefix_changed` / `model_changed` / `idle` / `subtask` / `evicted`);统计计入全部未命中,不受界面提示门槛影响 |
214
+ | `warming` | `mode`(`off` / `streaming` / `idle`)、`state`(`inactive` / `scheduled` / `stopped`)、`phase?`、`nextWarmAt?`、停止原因 `reason?`、`sent?`、`costUsd?`、`expectedSavingsUsd?` |
215
+ | `contextRemainingTokens` / `estimatedTurnsLeft` | 上下文余量与按最近 5 回合增量估算的剩余回合 |
216
+ | `subagents` | task 子会话汇总:`count`、`hitRate?`、`reBilledTokens` |
217
+ | `granularity` | 可选:当前端点推断的缓存读分块粒度(token,非零 cacheRead 的最大公约数,≥ 2 个样本且在 128–8192 才给);未命中的噪声下限取它与 1024、`minTokens` 中的最大者 |
218
+
219
+ `tokens` / `cacheHitRate` 保持旧口径,新客户端用 `cache`。缓存事件示例:
220
+
221
+ ```json
222
+ {"type":"cache_miss","missedTokens":142000,"missedCost":0.1278,"reason":"evicted","idleMs":3}
223
+ {"type":"cache_warm","phase":"scheduled","nextWarmAt":1790000000000}
224
+ {"type":"cache_warm","phase":"sent","usage":{"input":1,"output":1,"cacheRead":12000,"cacheWrite":0,"totalTokens":12002},"cost":0.0012}
225
+ {"type":"cache_warm","phase":"stopped","reason":"no_cache_hits"}
226
+ {"type":"context_pressure","percent":71,"threshold":70,"remainingTokens":57990,"estimatedTurnsLeft":6}
227
+ ```
228
+
229
+ `cache_miss` 的 `detail` 只在 `prefix_changed` 时给出(`system` / `tools`)。`cache_warm{stopped}` 的 `reason` 取值与含义见 [tui.md](tui.md)「缓存与上下文」。`ama -p --output-format json` 的结果对象另有 `cache` 字段,形状同上。
230
+
231
+ ## 审批
232
+
233
+ 1. 工具调用需要确认时,服务端发 `permission_request`。`preview`(可选)是执行前预览:`{ kind: "bash" | "write" | "edit" | "other", lines: string[], severity: "info" | "warn" | "danger", affected?: { path, exists, bytes?, files? }[] }`,`lines` 已排好、不含颜色,可直接显示;预览只读、有上限,算不出时缺省。
234
+ 2. 客户端在此之前发过 `set_client_capabilities{capabilities:["approvals"]}` 才会被问到;否则这条 ask 无人作答 → deny。声明里去掉 `approvals` 会撤下客户端并让在等的审批全部按无人作答处理。
235
+ 3. 客户端回 `permission_response{requestId, decision}`。`allow_session` 在本会话内记住同一工具与归一化输入前缀,不落盘。先于请求到达的回答会暂存,等请求出现时使用。
236
+ 4. 回答者顺序:宿主 broker(`HostApi.approvals.setBroker`)→ RPC 客户端 → 无人作答 deny。审批串行,同一时刻只有一个在等。
237
+ 5. 超时:`timeoutMs`(缺省 600 000,即 10 分钟;环境变量 `AMA_APPROVAL_TIMEOUT_MS` 可改)内没有回答 → 服务端按 deny 处理并发 `permission_resolved`。运行被中断时同样 deny,即使客户端已经放行。
238
+
239
+ ## 退出
240
+
241
+ - stdin 关闭:不再接收命令,撤下审批(之后的 ask 按无人作答 deny),等在途命令与已开始的运行结束、响应写完,退出码 0。所以 `printf '{"type":"prompt","message":"hi"}\n' | ama --mode rpc` 能拿到完整回复;要提前停止,先发 `abort`。
242
+ - SIGINT / SIGTERM:中断当前运行后有序退出,退出码 130 / 143。
243
+ - 启动阶段的失败按 CLI 退出码([design.md](design.md) §11.3):配置错误 3、无模型或 key 4、会话错误 5、宿主 / Hook 启动失败 6、宿主 API 版本不匹配 78。
244
+
245
+ ## 示例
246
+
247
+ `test/fixtures/rpc/prompt.out.jsonl` 是一次完整往返的黄金文件(fake 供应商;`prompt` → `get_last_assistant_text`,会话 id、时间戳与路径已归一化):`hello` → `session_start` → `entry_appended`(模型、思考级别、首条 system 消息)→ `before_agent_start` → `agent_start` → `turn_start` → `prompt` 的响应 → 用户消息 → 助手消息的 `message_update` 增量 → `turn_end` → `agent_end` → `agent_before_settle` → `agent_settled` → 第二条命令的响应。改协议后由 `src/modes/rpc/rpc-mode.test.ts` 用 `UPDATE_GOLDEN=1` 更新并审阅差异。
248
+
249
+ 最小会话(stdin 关闭即撤下审批,所以管道方式只适合不需要审批的提示;要回答审批,保持 stdin 打开并先发 `set_client_capabilities`):
250
+
251
+ ```sh
252
+ printf '%s\n' '{"id":"1","type":"prompt","message":"hi"}' | ama --mode rpc --model anthropic/<model-id>
253
+ ```