@armadra/agent 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +142 -49
  3. package/dist/agent/retry.d.ts +1 -1
  4. package/dist/agent/retry.js +2 -1
  5. package/dist/agent/session-cache.js +6 -3
  6. package/dist/agent/session-classifier.d.ts +18 -0
  7. package/dist/agent/session-classifier.js +103 -0
  8. package/dist/agent/session-core.d.ts +13 -0
  9. package/dist/agent/session-state.js +2 -1
  10. package/dist/agent/session-sync.js +9 -2
  11. package/dist/agent/session-tools.js +24 -3
  12. package/dist/agent/session.d.ts +3 -0
  13. package/dist/agent/session.js +20 -10
  14. package/dist/agent/tool-runner.js +15 -2
  15. package/dist/agent/types.d.ts +9 -1
  16. package/dist/ai/apis/anthropic-messages.js +3 -2
  17. package/dist/ai/apis/google-generative-ai.js +3 -2
  18. package/dist/ai/apis/openai-completions.js +3 -2
  19. package/dist/ai/apis/openai-responses.js +5 -3
  20. package/dist/ai/cache/fingerprint.d.ts +1 -1
  21. package/dist/ai/cache/fingerprint.js +1 -1
  22. package/dist/ai/cache/reporting.js +2 -1
  23. package/dist/ai/http.d.ts +28 -6
  24. package/dist/ai/http.js +41 -8
  25. package/dist/ai/providers/channels.d.ts +51 -0
  26. package/dist/ai/providers/channels.js +96 -0
  27. package/dist/ai/providers/enrich.d.ts +34 -0
  28. package/dist/ai/providers/enrich.js +86 -0
  29. package/dist/ai/providers/models-dev-cache.d.ts +53 -0
  30. package/dist/ai/providers/models-dev-cache.js +147 -0
  31. package/dist/ai/providers/models-dev.d.ts +99 -0
  32. package/dist/ai/providers/models-dev.js +315 -0
  33. package/dist/ai/providers/registry.d.ts +42 -4
  34. package/dist/ai/providers/registry.js +195 -34
  35. package/dist/ai/providers/suggest.d.ts +18 -0
  36. package/dist/ai/providers/suggest.js +72 -0
  37. package/dist/ai/sse.d.ts +6 -2
  38. package/dist/ai/sse.js +22 -2
  39. package/dist/ai/types.d.ts +30 -3
  40. package/dist/bundle/ama.cjs +17893 -10698
  41. package/dist/cli/args.d.ts +23 -5
  42. package/dist/cli/args.js +113 -13
  43. package/dist/cli/bootstrap.js +28 -3
  44. package/dist/cli/codemode-notice.d.ts +20 -0
  45. package/dist/cli/codemode-notice.js +55 -0
  46. package/dist/cli/compose-providers.js +3 -0
  47. package/dist/cli/compose-session.d.ts +8 -0
  48. package/dist/cli/compose-session.js +32 -1
  49. package/dist/cli/compose-store.d.ts +1 -1
  50. package/dist/cli/compose-store.js +3 -1
  51. package/dist/cli/compose.d.ts +17 -5
  52. package/dist/cli/compose.js +53 -19
  53. package/dist/cli/default-model.d.ts +38 -1
  54. package/dist/cli/default-model.js +96 -9
  55. package/dist/cli/deps.d.ts +38 -0
  56. package/dist/cli/exit-codes.d.ts +2 -0
  57. package/dist/cli/exit-codes.js +3 -0
  58. package/dist/cli/fake-visibility.d.ts +13 -0
  59. package/dist/cli/fake-visibility.js +26 -0
  60. package/dist/cli/from-prompt.d.ts +18 -0
  61. package/dist/cli/from-prompt.js +49 -0
  62. package/dist/cli/main.d.ts +10 -2
  63. package/dist/cli/main.js +88 -2
  64. package/dist/cli/proxy.d.ts +51 -0
  65. package/dist/cli/proxy.js +135 -0
  66. package/dist/cli/startup-screen.d.ts +29 -0
  67. package/dist/cli/startup-screen.js +47 -0
  68. package/dist/cli/startup-steps.d.ts +1 -1
  69. package/dist/cli/startup-steps.js +20 -13
  70. package/dist/cli/subcommands/config.d.ts +29 -4
  71. package/dist/cli/subcommands/config.js +188 -25
  72. package/dist/cli/subcommands/context.js +5 -1
  73. package/dist/cli/subcommands/doctor.js +12 -1
  74. package/dist/cli/subcommands/init.d.ts +6 -0
  75. package/dist/cli/subcommands/init.js +23 -0
  76. package/dist/cli/subcommands/model-meta.d.ts +16 -0
  77. package/dist/cli/subcommands/model-meta.js +54 -0
  78. package/dist/cli/subcommands/models-cache-probe.js +1 -1
  79. package/dist/cli/subcommands/models-discover.d.ts +18 -6
  80. package/dist/cli/subcommands/models-discover.js +87 -52
  81. package/dist/cli/subcommands/models.js +26 -18
  82. package/dist/cli/subcommands/probe-runner.d.ts +96 -0
  83. package/dist/cli/subcommands/probe-runner.js +264 -0
  84. package/dist/cli/subcommands/providers-list.d.ts +8 -0
  85. package/dist/cli/subcommands/providers-list.js +97 -0
  86. package/dist/cli/subcommands/providers-plan.d.ts +79 -0
  87. package/dist/cli/subcommands/providers-plan.js +215 -0
  88. package/dist/cli/subcommands/providers-probe.d.ts +34 -0
  89. package/dist/cli/subcommands/providers-probe.js +87 -0
  90. package/dist/cli/subcommands/providers.d.ts +28 -0
  91. package/dist/cli/subcommands/providers.js +436 -0
  92. package/dist/cli/subcommands/sessions-export.d.ts +10 -0
  93. package/dist/cli/subcommands/sessions-export.js +59 -0
  94. package/dist/cli/subcommands/sessions-search.d.ts +13 -0
  95. package/dist/cli/subcommands/sessions-search.js +103 -0
  96. package/dist/cli/subcommands/sessions.d.ts +3 -2
  97. package/dist/cli/subcommands/sessions.js +22 -1
  98. package/dist/cli/subcommands/stats.d.ts +17 -0
  99. package/dist/cli/subcommands/stats.js +198 -0
  100. package/dist/cli/system-prompt-arg.d.ts +11 -0
  101. package/dist/cli/system-prompt-arg.js +34 -0
  102. package/dist/codemode/modes.d.ts +4 -11
  103. package/dist/codemode/modes.js +5 -27
  104. package/dist/codemode/tool.d.ts +18 -14
  105. package/dist/codemode/tool.js +71 -28
  106. package/dist/config/init.d.ts +34 -0
  107. package/dist/config/init.js +99 -0
  108. package/dist/config/json-schema.d.ts +15 -0
  109. package/dist/config/json-schema.js +215 -0
  110. package/dist/config/key-docs.d.ts +22 -0
  111. package/dist/config/key-docs.js +105 -0
  112. package/dist/config/merge.d.ts +9 -8
  113. package/dist/config/merge.js +22 -7
  114. package/dist/config/schema.d.ts +1 -1
  115. package/dist/config/schema.js +97 -15
  116. package/dist/config/types.d.ts +57 -11
  117. package/dist/config/types.js +19 -1
  118. package/dist/modes/commands-core.js +6 -5
  119. package/dist/modes/image-input.d.ts +27 -0
  120. package/dist/modes/image-input.js +78 -0
  121. package/dist/modes/interactive/approval-dialog.d.ts +21 -5
  122. package/dist/modes/interactive/approval-dialog.js +106 -27
  123. package/dist/modes/interactive/commands.d.ts +11 -2
  124. package/dist/modes/interactive/commands.js +58 -19
  125. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  126. package/dist/modes/interactive/interactive-mode.js +70 -82
  127. package/dist/modes/interactive/key-dispatch.d.ts +3 -1
  128. package/dist/modes/interactive/key-dispatch.js +5 -6
  129. package/dist/modes/interactive/line/line-mode.d.ts +1 -0
  130. package/dist/modes/interactive/line/line-mode.js +9 -5
  131. package/dist/modes/interactive/line/line-render.d.ts +4 -0
  132. package/dist/modes/interactive/line/line-render.js +28 -6
  133. package/dist/modes/interactive/message-view.d.ts +48 -9
  134. package/dist/modes/interactive/message-view.js +238 -44
  135. package/dist/modes/interactive/panels.d.ts +18 -0
  136. package/dist/modes/interactive/panels.js +143 -0
  137. package/dist/modes/interactive/pickers.d.ts +23 -2
  138. package/dist/modes/interactive/pickers.js +48 -15
  139. package/dist/modes/interactive/run-indicator.d.ts +51 -0
  140. package/dist/modes/interactive/run-indicator.js +189 -0
  141. package/dist/modes/interactive/startup-header.d.ts +40 -0
  142. package/dist/modes/interactive/startup-header.js +169 -0
  143. package/dist/modes/interactive/startup-ui.d.ts +7 -2
  144. package/dist/modes/interactive/startup-ui.js +41 -11
  145. package/dist/modes/interactive/status-bar.d.ts +18 -15
  146. package/dist/modes/interactive/status-bar.js +98 -56
  147. package/dist/modes/interactive/tool-summary.d.ts +46 -0
  148. package/dist/modes/interactive/tool-summary.js +218 -0
  149. package/dist/modes/interactive/tool-view.d.ts +48 -15
  150. package/dist/modes/interactive/tool-view.js +203 -145
  151. package/dist/modes/print/print-mode.d.ts +33 -4
  152. package/dist/modes/print/print-mode.js +128 -8
  153. package/dist/modes/rpc/commands.js +12 -2
  154. package/dist/permissions/auto-safe.d.ts +60 -0
  155. package/dist/permissions/auto-safe.js +529 -0
  156. package/dist/permissions/classifier.d.ts +64 -0
  157. package/dist/permissions/classifier.js +184 -0
  158. package/dist/permissions/dangerous.d.ts +5 -0
  159. package/dist/permissions/dangerous.js +1 -1
  160. package/dist/permissions/modes.d.ts +30 -0
  161. package/dist/permissions/modes.js +78 -0
  162. package/dist/permissions/pipeline.d.ts +31 -4
  163. package/dist/permissions/pipeline.js +196 -6
  164. package/dist/permissions/protected.d.ts +19 -0
  165. package/dist/permissions/protected.js +74 -0
  166. package/dist/permissions/rules.js +3 -0
  167. package/dist/permissions/types.d.ts +50 -3
  168. package/dist/permissions/types.js +3 -0
  169. package/dist/rpc.d.ts +2 -0
  170. package/dist/sdk.d.ts +9 -3
  171. package/dist/sdk.js +10 -2
  172. package/dist/session/export.d.ts +32 -0
  173. package/dist/session/export.js +187 -0
  174. package/dist/session/projection.js +5 -1
  175. package/dist/session/redact.d.ts +15 -0
  176. package/dist/session/redact.js +55 -0
  177. package/dist/session/reuse.d.ts +33 -0
  178. package/dist/session/reuse.js +86 -0
  179. package/dist/session/scan.d.ts +34 -0
  180. package/dist/session/scan.js +140 -0
  181. package/dist/session/search.d.ts +52 -0
  182. package/dist/session/search.js +211 -0
  183. package/dist/session/stats-aggregate.d.ts +63 -0
  184. package/dist/session/stats-aggregate.js +163 -0
  185. package/dist/session/stats-index.d.ts +26 -0
  186. package/dist/session/stats-index.js +91 -0
  187. package/dist/session/stats-scan.d.ts +54 -0
  188. package/dist/session/stats-scan.js +236 -0
  189. package/dist/session/types.d.ts +2 -0
  190. package/dist/tools/image-file.d.ts +31 -0
  191. package/dist/tools/image-file.js +114 -0
  192. package/dist/tools/presets.d.ts +35 -8
  193. package/dist/tools/presets.js +56 -17
  194. package/dist/tools/read.d.ts +4 -8
  195. package/dist/tools/read.js +15 -64
  196. package/dist/tui/component.d.ts +8 -2
  197. package/dist/tui/component.js +3 -1
  198. package/dist/tui/components/box.d.ts +6 -1
  199. package/dist/tui/components/box.js +16 -6
  200. package/dist/tui/components/card.d.ts +23 -0
  201. package/dist/tui/components/card.js +37 -0
  202. package/dist/tui/components/editor-history.d.ts +6 -0
  203. package/dist/tui/components/editor-history.js +45 -0
  204. package/dist/tui/components/editor-paste.d.ts +1 -1
  205. package/dist/tui/components/editor-paste.js +4 -4
  206. package/dist/tui/components/editor.d.ts +11 -5
  207. package/dist/tui/components/editor.js +52 -58
  208. package/dist/tui/components/key-value.d.ts +3 -0
  209. package/dist/tui/components/key-value.js +16 -6
  210. package/dist/tui/components/loader.d.ts +37 -7
  211. package/dist/tui/components/loader.js +84 -21
  212. package/dist/tui/components/markdown.d.ts +5 -1
  213. package/dist/tui/components/markdown.js +45 -21
  214. package/dist/tui/components/meter.d.ts +3 -3
  215. package/dist/tui/components/meter.js +13 -11
  216. package/dist/tui/components/select-list.d.ts +23 -1
  217. package/dist/tui/components/select-list.js +76 -13
  218. package/dist/tui/glyphs.d.ts +68 -0
  219. package/dist/tui/glyphs.js +114 -0
  220. package/dist/tui/theme.d.ts +30 -6
  221. package/dist/tui/theme.js +103 -17
  222. package/dist/tui.d.ts +4 -2
  223. package/dist/tui.js +3 -1
  224. package/docs/codemode.md +23 -9
  225. package/docs/hooks.md +10 -10
  226. package/docs/permissions.md +148 -0
  227. package/docs/providers.md +241 -3
  228. package/docs/rpc.md +38 -38
  229. package/docs/session-format.md +3 -2
  230. package/docs/sessions.md +134 -0
  231. package/docs/tui.md +140 -61
  232. package/package.json +3 -1
@@ -3,23 +3,78 @@
3
3
  * 每一项标出来自哪一层(default / user / profile / project),并给出将使用的模型与原因
4
4
  * (`config.defaultModel` 或零配置选择)。只读;config 里的字面量 apiKey 只显示种类。[B6]
5
5
  *
6
+ * 缺省值来自 config/key-docs.ts 的 DISPLAY_DEFAULTS(含 DEFAULT_CONFIG 不写的 cache / codemode 等段),
7
+ * `codemode.mode` 不写时显示跟随预设的结果与原因;`--tools-preset` / `--codemode` 作为 cli 层叠加;
8
+ * 预设写的是旧名(codemode)时显示规范名并提示。
9
+ *
6
10
  * [W3-B12] 「供应商」节:config 里出现的供应商与 baseUrl 来自环境变量(`OPENAI_BASE_URL` /
7
11
  * `ANTHROPIC_BASE_URL`)的内置供应商,列出协议、生效 baseUrl 与来源、config 里每个模型的协议
8
12
  * (模型级 `api` 生效后的值)。
9
13
  */
14
+ import { metadataOf, modelFlags, sourcesLine } from "./model-meta.js";
10
15
  import { loadConfigFile } from "../../config/load.js";
11
- import { DEFAULT_CONFIG, PROFILE_DEFAULTS, mergeProjectAndCli } from "../../config/merge.js";
12
- import { CONFIG_FILE, projectFile } from "../../config/paths.js";
16
+ import { PROFILE_DEFAULTS, cliOverridesToConfig, mergeConfig, mergeProjectAndCli, } from "../../config/merge.js";
17
+ import { DISPLAY_DEFAULTS } from "../../config/key-docs.js";
18
+ import { AUTH_FILE, CONFIG_FILE, HOOKS_FILE, projectFile, resolveConfigDir, resolveDataDir, } from "../../config/paths.js";
19
+ import { spawnSync } from "node:child_process";
20
+ import { existsSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { modelsDevCachePath } from "../../ai/providers/models-dev-cache.js";
23
+ import { initConfigDir } from "../../config/init.js";
24
+ import { CONFIG_SCHEMA_FILE } from "../../config/json-schema.js";
25
+ import { CODEMODE_MODES, TOOLS_PRESET_ALIASES, TOOLS_PRESET_INPUTS, canonicalPreset, } from "../../config/types.js";
26
+ import { codemodeAvailability, detectSandboxCapability } from "../../codemode/capability.js";
13
27
  import { baseUrlEnvOf } from "../../ai/providers/registry.js";
14
28
  import { classifyKeyValue } from "../../config/auth-file.js";
15
- import { effectiveCodemodeMode, resolvePreset } from "../../tools/presets.js";
29
+ import { CODEMODE_TOOL, resolveCodemodeMode, resolvePreset } from "../../tools/presets.js";
16
30
  import { builtinTools } from "../../tools/registry.js";
17
31
  import { parseSubArgs, UsageError } from "../args.js";
18
- import { pickDefaultModel } from "../default-model.js";
32
+ import { noModelGuidance, pickDefaultModel } from "../default-model.js";
19
33
  import { ExitCode } from "../exit-codes.js";
20
34
  import { buildRegistry, loadUserLevel } from "./context.js";
21
35
  export const CONFIG_USAGE = `用法:ama config show [--json] [--profile <文件>] [--auth-file <文件>]
36
+ [--tools-preset <名>] [--codemode off|on|only]
37
+ ama config path 配置目录、数据目录与各文件路径
38
+ ama config edit 用 $VISUAL / $EDITOR 打开 config.json(没有编辑器时打印路径)
22
39
  `;
40
+ /** `ama config path`:目录与文件路径,标出是否存在。 */
41
+ function showPaths(io) {
42
+ const configDir = resolveConfigDir({ env: io.env });
43
+ const dataDir = resolveDataDir({ env: io.env });
44
+ const mark = (path) => `${path}${existsSync(path) ? "" : " (不存在)"}`;
45
+ const lines = [
46
+ `配置目录 ${mark(configDir)}`,
47
+ ` config.json ${mark(join(configDir, CONFIG_FILE))}`,
48
+ ` config.schema.json ${mark(join(configDir, CONFIG_SCHEMA_FILE))}`,
49
+ ` auth.json ${mark(join(configDir, AUTH_FILE))}`,
50
+ ` hooks.json ${mark(join(configDir, HOOKS_FILE))}`,
51
+ `数据目录 ${mark(dataDir)}`,
52
+ ` sessions/ ${mark(join(dataDir, "sessions"))}`,
53
+ ` models-dev.json ${mark(modelsDevCachePath(dataDir))}`,
54
+ `项目级 ${mark(projectFile(io.cwd, CONFIG_FILE))}`,
55
+ ];
56
+ io.stdout(`${lines.join("\n")}\n`);
57
+ return ExitCode.Ok;
58
+ }
59
+ /** `ama config edit`:不存在先 init;`$VISUAL` / `$EDITOR`(可带参数)打开,没有就打印路径。 */
60
+ function editConfig(io) {
61
+ const configDir = resolveConfigDir({ env: io.env });
62
+ const path = join(configDir, CONFIG_FILE);
63
+ if (!existsSync(path))
64
+ initConfigDir(configDir);
65
+ const editor = (io.env["VISUAL"] ?? io.env["EDITOR"] ?? "").trim();
66
+ if (editor === "") {
67
+ io.stdout(`${path}\n(没有设置 $VISUAL / $EDITOR,请用编辑器打开上面的文件)\n`);
68
+ return ExitCode.Ok;
69
+ }
70
+ const quoted = process.platform === "win32" ? `"${path}"` : `'${path.replace(/'/g, `'\\''`)}'`;
71
+ const result = spawnSync(`${editor} ${quoted}`, { stdio: "inherit", shell: true });
72
+ if (result.error !== undefined || result.status !== 0) {
73
+ io.stderr(`ama: 编辑器退出异常(${editor});文件在 ${path}\n`);
74
+ return ExitCode.RuntimeError;
75
+ }
76
+ return ExitCode.Ok;
77
+ }
23
78
  function isObject(value) {
24
79
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
80
  }
@@ -58,13 +113,16 @@ export async function describeModel(config, registry) {
58
113
  return { ref: undefined, reason: "注册表未装配" };
59
114
  const picked = await pickDefaultModel(registry);
60
115
  if (picked === undefined)
61
- return { ref: undefined, reason: "没有可用模型:ama auth set <provider> 或设置对应环境变量" };
116
+ return { ref: undefined, reason: noModelGuidance(registry) };
62
117
  const ref = `${picked.provider.id}/${picked.model.id}`;
63
118
  if (picked.via === "local")
64
- return { ref, reason: `零配置:本地 ${picked.provider.id} 可达` };
119
+ return { ref, reason: `零配置:本地 ${picked.provider.id} 可达;${picked.rule}` };
65
120
  const key = await registry.resolveApiKey(picked.provider.id);
66
121
  const origin = key.origin !== undefined ? ` ${key.origin}` : "";
67
- return { ref, reason: `零配置:${picked.provider.id} 有 key(${key.source}${origin})` };
122
+ return {
123
+ ref,
124
+ reason: `零配置:${picked.provider.id} 有 key(${key.source}${origin});${picked.rule}`,
125
+ };
68
126
  }
69
127
  /** config 里出现的供应商 + baseUrl 来自环境变量的内置供应商。 */
70
128
  export function describeProviders(config, registry) {
@@ -82,7 +140,27 @@ export function describeProviders(config, registry) {
82
140
  api: provider.api,
83
141
  baseUrl: provider.baseUrl,
84
142
  ...(env !== undefined ? { baseUrlEnv: env } : {}),
85
- models: provider.models.filter((m) => ids.has(m.id)).map((m) => ({ id: m.id, api: m.api })),
143
+ ...(provider.channels !== undefined
144
+ ? {
145
+ channels: provider.channels.map((c) => ({
146
+ name: c.name,
147
+ api: c.api,
148
+ baseUrl: c.baseUrl,
149
+ })),
150
+ }
151
+ : {}),
152
+ models: provider.models
153
+ .filter((m) => ids.has(m.id))
154
+ .map((m) => {
155
+ const sources = sourcesLine(metadataOf(registry, provider.id, m.id));
156
+ return {
157
+ id: m.id,
158
+ api: m.api,
159
+ ...(m.channels !== undefined ? { channels: [...m.channels] } : {}),
160
+ flags: modelFlags(m),
161
+ ...(sources !== undefined ? { sources } : {}),
162
+ };
163
+ }),
86
164
  });
87
165
  }
88
166
  return out;
@@ -94,13 +172,18 @@ function providerLines(providers) {
94
172
  for (const p of providers) {
95
173
  const env = p.baseUrlEnv !== undefined ? `(baseUrl 来自环境变量 ${p.baseUrlEnv})` : "";
96
174
  lines.push(` ${p.id} ${p.api} ${p.baseUrl}${env}`);
97
- for (const m of p.models)
98
- lines.push(` ${p.id}/${m.id} ${m.api}`);
175
+ for (const c of p.channels ?? [])
176
+ lines.push(` @${c.name} ${c.api} ${c.baseUrl}`);
177
+ for (const m of p.models) {
178
+ lines.push(` ${p.id}/${m.id} ${m.api} ${m.flags}`);
179
+ if (m.sources !== undefined)
180
+ lines.push(` ${m.sources}`);
181
+ }
99
182
  }
100
183
  return lines;
101
184
  }
102
- function layersOf(level, project, effective) {
103
- const layers = [{ name: "default", label: "内置缺省", value: DEFAULT_CONFIG }];
185
+ function layersOf(level, project, effective, cli) {
186
+ const layers = [{ name: "default", label: "内置缺省", value: DISPLAY_DEFAULTS }];
104
187
  const user = loadConfigFile("config", level.userConfigPath)?.value;
105
188
  if (user !== undefined)
106
189
  layers.push({ name: "user", label: level.userConfigPath, value: user });
@@ -123,6 +206,8 @@ function layersOf(level, project, effective) {
123
206
  }
124
207
  layers.push({ name: "project", label: ".ama/config.json", value: unflatten(accepted) });
125
208
  }
209
+ if (cli !== undefined && Object.keys(cli).length > 0)
210
+ layers.push({ name: "cli", label: "命令行", value: cli });
126
211
  return layers;
127
212
  }
128
213
  function unflatten(flat) {
@@ -149,51 +234,129 @@ export function sourcesOf(effective, layers) {
149
234
  }
150
235
  return sources;
151
236
  }
237
+ /** codemode 生效模式与原因(跟随预设时写明预设与运行时 Node 是否隔离网络)。 */
238
+ export function describeCodemode(config, nodeVersion) {
239
+ const capability = detectSandboxCapability(nodeVersion);
240
+ const resolved = resolveCodemodeMode(config, capability.strict);
241
+ const sandbox = capability.strict
242
+ ? `Node ${capability.nodeMajor} 网络已隔离`
243
+ : `Node ${capability.nodeMajor} < 25 网络未隔离`;
244
+ const reason = resolved.source === "config"
245
+ ? `codemode.mode 显式设置(${sandbox})`
246
+ : `跟随预设 ${resolved.preset}(${sandbox})`;
247
+ const out = {
248
+ mode: resolved.mode,
249
+ source: resolved.source,
250
+ reason,
251
+ strict: capability.strict,
252
+ };
253
+ if (resolved.mode !== "off") {
254
+ const availability = codemodeAvailability(config.codemode?.requireStrict, capability);
255
+ if (!availability.available)
256
+ out.unavailable = availability.warning;
257
+ }
258
+ return out;
259
+ }
260
+ /** 各层里把 tools.preset 写成旧名(别名)的:`user(写的是旧名 codemode)`。 */
261
+ function presetAliasNotes(layers) {
262
+ const notes = [];
263
+ for (const layer of layers) {
264
+ const preset = layer.value?.tools?.preset;
265
+ if (preset !== undefined && Object.hasOwn(TOOLS_PRESET_ALIASES, preset))
266
+ notes.push(`${layer.name}(${layer.label})的 tools.preset 写的是旧名 ${preset},规范名 ${canonicalPreset(preset)}`);
267
+ }
268
+ return notes;
269
+ }
270
+ function choice(name, value, all) {
271
+ if (value === undefined)
272
+ return undefined;
273
+ if (all.includes(value))
274
+ return value;
275
+ throw new UsageError(`--${name} 的取值应为 ${all.join(" | ")}(收到 ${value})`);
276
+ }
152
277
  export async function runConfig(argv, io, deps) {
153
- const { positionals, values, flags } = parseSubArgs(argv, ["profile", "auth-file"], ["json"]);
278
+ const { positionals, values, flags } = parseSubArgs(argv, ["profile", "auth-file", "tools-preset", "codemode"], ["json"]);
154
279
  const action = positionals[0] ?? "show";
155
280
  if (flags.has("help")) {
156
281
  io.stdout(CONFIG_USAGE);
157
282
  return ExitCode.Ok;
158
283
  }
284
+ if (action === "path")
285
+ return showPaths(io);
286
+ if (action === "edit")
287
+ return editConfig(io);
159
288
  if (action !== "show")
160
289
  throw new UsageError(`未知的 config 子命令:${action}`);
290
+ const cli = {};
291
+ const presetFlag = choice("tools-preset", values.get("tools-preset"), TOOLS_PRESET_INPUTS);
292
+ if (presetFlag !== undefined)
293
+ cli.toolsPreset = presetFlag;
294
+ const codemodeFlag = choice("codemode", values.get("codemode"), CODEMODE_MODES);
295
+ if (codemodeFlag !== undefined)
296
+ cli.codemode = codemodeFlag;
161
297
  const level = loadUserLevel(io, {
162
298
  profile: values.get("profile"),
163
299
  authFile: values.get("auth-file"),
164
300
  });
165
301
  const project = loadConfigFile("config", projectFile(io.cwd, CONFIG_FILE))?.value;
166
- const merged = mergeProjectAndCli(level.merged, project, undefined);
302
+ const merged = mergeProjectAndCli(level.merged, project, cli);
167
303
  const config = merged.config;
168
- const layers = layersOf(level, project, config);
169
- const sources = sourcesOf(config, layers);
304
+ const layers = layersOf(level, project, config, cliOverridesToConfig(cli));
305
+ // 展示:生效配置补上 DEFAULT_CONFIG 不写的缺省(cache、codemode.inlineBudget 等),来源 default
306
+ const shown = mergeConfig(DISPLAY_DEFAULTS, config);
307
+ const sources = sourcesOf(shown, layers);
170
308
  const registry = deps === undefined ? undefined : await buildRegistry(level, io, deps);
171
309
  const model = await describeModel(config, registry);
172
310
  const providers = describeProviders(config, registry);
311
+ const codemode = describeCodemode(config);
173
312
  const builtin = new Set(builtinTools().map((tool) => tool.name));
174
- const preset = resolvePreset({ config, available: (name) => builtin.has(name) });
313
+ if (codemode.mode !== "off" && codemode.unavailable === undefined)
314
+ builtin.add(CODEMODE_TOOL);
315
+ const preset = resolvePreset({
316
+ config,
317
+ available: (name) => builtin.has(name),
318
+ strict: codemode.strict,
319
+ });
320
+ const notes = presetAliasNotes(layers);
175
321
  const warnings = [...level.warnings, ...merged.warnings];
322
+ const rows = [...flatten(shown)].map(([path, value]) => ({
323
+ path,
324
+ value,
325
+ source: sources.get(path) ?? "default",
326
+ }));
327
+ if (config.codemode?.mode === undefined)
328
+ rows.push({
329
+ path: "codemode.mode",
330
+ value: codemode.mode,
331
+ source: `default(${codemode.reason})`,
332
+ });
333
+ rows.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
176
334
  if (flags.has("json")) {
177
- const entries = [...flatten(config)].map(([path, value]) => ({
335
+ const entries = rows.map(({ path, value, source }) => ({
178
336
  path,
179
337
  value: path.endsWith(".apiKey") ? display(path, value) : value,
180
- source: sources.get(path),
338
+ source,
181
339
  }));
182
- io.stdout(`${JSON.stringify({ model, providers, tools: preset.builtin, codemode: effectiveCodemodeMode(config), entries, layers: layers.map((l) => ({ name: l.name, file: l.label })), warnings }, null, 2)}\n`);
340
+ io.stdout(`${JSON.stringify({ model, providers, tools: preset.builtin, preset: preset.preset, codemode, entries, layers: layers.map((l) => ({ name: l.name, file: l.label })), notes, warnings }, null, 2)}\n`);
183
341
  return ExitCode.Ok;
184
342
  }
185
- const lines = ["生效配置(来源:default 内置 ← user ← profile ← project 只能收紧)"];
343
+ const lines = ["生效配置(来源:default 内置 ← user ← profile ← project 只能收紧 ← cli)"];
186
344
  for (const layer of layers.slice(1))
187
345
  lines.push(` ${layer.name}:${layer.label}`);
188
346
  lines.push("");
189
- const rows = [...flatten(config)].map(([path, value]) => [`${path} = ${display(path, value)}`, sources.get(path) ?? "default"]);
190
- const width = Math.min(60, Math.max(...rows.map(([text]) => text.length)));
191
- for (const [text, source] of rows)
192
- lines.push(` ${text.padEnd(width)} ${source}`);
347
+ const texts = rows.map((row) => [`${row.path} = ${display(row.path, row.value)}`, row.source]);
348
+ const width = Math.min(60, Math.max(...texts.map(([text]) => (text ?? "").length)));
349
+ for (const [text, source] of texts)
350
+ lines.push(` ${(text ?? "").padEnd(width)} ${source}`);
193
351
  lines.push(...providerLines(providers));
194
352
  lines.push("");
195
353
  lines.push(`模型:${model.ref ?? "(无)"} ${model.reason}`);
196
354
  lines.push(`工具:${preset.builtin.join(", ")}(预设 ${preset.preset},codemode ${preset.codemode})`);
355
+ lines.push(`codemode:${codemode.mode} ${codemode.reason}`);
356
+ if (codemode.unavailable !== undefined)
357
+ lines.push(` ${codemode.unavailable}`);
358
+ for (const note of notes)
359
+ lines.push(`提示:${note}`);
197
360
  for (const warning of [...warnings, ...preset.warnings])
198
361
  lines.push(`警告:${warning}`);
199
362
  io.stdout(`${lines.join("\n")}\n`);
@@ -7,6 +7,7 @@ import { loadConfigFile } from "../../config/load.js";
7
7
  import { mergeBaseLayers } from "../../config/merge.js";
8
8
  import { CONFIG_FILE, resolveConfigDir, resolveDataDir, userFile } from "../../config/paths.js";
9
9
  import { loadProfile } from "../../config/profile.js";
10
+ import { hideFakeProvider } from "../fake-visibility.js";
10
11
  export function loadUserLevel(io, options = {}) {
11
12
  const configDir = resolveConfigDir({ env: io.env });
12
13
  const dataDir = resolveDataDir({ env: io.env });
@@ -32,10 +33,13 @@ export function loadUserLevel(io, options = {}) {
32
33
  return result;
33
34
  }
34
35
  export async function buildRegistry(level, io, deps) {
35
- return deps.providers.create({
36
+ // 列表类子命令(doctor / models list / providers list / config show)缺省不列 fake
37
+ const registry = await deps.providers.create({
36
38
  config: level.merged.config,
37
39
  cwd: io.cwd,
38
40
  authFile: level.authFile,
39
41
  authEnv: level.profile?.authEnv ?? true,
42
+ dataDir: level.dataDir,
40
43
  });
44
+ return hideFakeProvider(registry, io.env);
41
45
  }
@@ -18,7 +18,8 @@ import { loadHookConfigs } from "../../hooks/config.js";
18
18
  import { AMA_VERSION } from "../../version.js";
19
19
  import { parseSubArgs } from "../args.js";
20
20
  import { ExitCode } from "../exit-codes.js";
21
- import { describeModel } from "./config.js";
21
+ import { describeProxy, inspectProxy } from "../proxy.js";
22
+ import { describeCodemode, describeModel } from "./config.js";
22
23
  import { buildRegistry, loadUserLevel } from "./context.js";
23
24
  export const DOCTOR_USAGE = `用法:ama doctor [--profile <文件>] [--auth-file <文件>] [--trust | --no-trust]
24
25
  `;
@@ -184,6 +185,11 @@ function terminalSection(report, io) {
184
185
  const tui = io.stdinIsTTY && io.stdoutIsTTY && env["TERM"] !== "dumb";
185
186
  report.item(`缺省界面:${tui ? "终端界面" : "行式(--no-tui 等价)"}`);
186
187
  }
188
+ function proxySection(report, io) {
189
+ report.section("代理");
190
+ for (const line of describeProxy(inspectProxy(io.env)))
191
+ report.item(line);
192
+ }
187
193
  export async function runDoctor(argv, io, deps) {
188
194
  const { values, flags } = parseSubArgs(argv, ["profile", "auth-file"], ["trust", "no-trust"]);
189
195
  if (flags.has("help")) {
@@ -223,6 +229,10 @@ export async function runDoctor(argv, io, deps) {
223
229
  for (const warning of merged.warnings)
224
230
  report.item(` 收紧:${warning}`);
225
231
  report.item(`有效权限模式:${merged.config.permission?.mode ?? "default"}`);
232
+ const codemode = describeCodemode(merged.config);
233
+ report.item(`codemode:${codemode.mode}(${codemode.reason})`);
234
+ if (codemode.unavailable !== undefined)
235
+ report.item(` ${codemode.unavailable}`);
226
236
  }
227
237
  catch {
228
238
  // probeLine 已报告
@@ -240,6 +250,7 @@ export async function runDoctor(argv, io, deps) {
240
250
  await keySection(report, io, level, deps);
241
251
  hookSection(report, io, level, trusted);
242
252
  report.item(`用户级 hooks.json:${userFile(level, HOOKS_FILE)}`);
253
+ proxySection(report, io);
243
254
  terminalSection(report, io);
244
255
  io.stdout(report.text());
245
256
  return report.problems > 0 ? ExitCode.Config : ExitCode.Ok;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `ama init [--force]`(docs/providers.md「配置目录」):建配置目录与缺省文件,逐个报告状态。
3
+ */
4
+ import type { CliIo } from "../deps.js";
5
+ export declare const INIT_USAGE = "\u7528\u6CD5\uFF1Aama init [--force] \u5EFA\u914D\u7F6E\u76EE\u5F55\uFF080700\uFF09\u4E0E config.json\u3001config.schema.json\n \u5DF2\u6709\u7684 config.json \u4E0D\u8986\u76D6\uFF08--force \u5148\u5907\u4EFD\u4E3A .bak \u518D\u91CD\u5199\uFF09\uFF1B\u4E0D\u521B\u5EFA auth.json\n";
6
+ export declare function runInit(argv: readonly string[], io: CliIo): number;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `ama init [--force]`(docs/providers.md「配置目录」):建配置目录与缺省文件,逐个报告状态。
3
+ */
4
+ import { INIT_NEXT_STEPS, describeInit, initConfigDir } from "../../config/init.js";
5
+ import { resolveConfigDir } from "../../config/paths.js";
6
+ import { parseSubArgs, UsageError } from "../args.js";
7
+ import { ExitCode } from "../exit-codes.js";
8
+ export const INIT_USAGE = `用法:ama init [--force] 建配置目录(0700)与 config.json、config.schema.json
9
+ 已有的 config.json 不覆盖(--force 先备份为 .bak 再重写);不创建 auth.json
10
+ `;
11
+ export function runInit(argv, io) {
12
+ const { positionals, flags } = parseSubArgs(argv, [], ["force"]);
13
+ if (flags.has("help")) {
14
+ io.stdout(INIT_USAGE);
15
+ return ExitCode.Ok;
16
+ }
17
+ if (positionals.length > 0)
18
+ throw new UsageError(`多余的参数:${positionals.join(" ")}`);
19
+ const result = initConfigDir(resolveConfigDir({ env: io.env }), { force: flags.has("force") });
20
+ io.stdout(describeInit(result));
21
+ io.stdout(`\n${INIT_NEXT_STEPS.join("\n")}\n`);
22
+ return ExitCode.Ok;
23
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 模型元数据的一行描述(`ama models list`、`ama config show`、`ama providers`):上下文、输出、思考、
3
+ * 图片、渠道,以及每个字段来自哪里(config / 目录 / models.dev / 缺省)与 models.dev 匹配。
4
+ */
5
+ import { type ModelMetadata } from "../../ai/providers/enrich.js";
6
+ import type { Model, ProviderRegistryApi } from "../../ai/types.js";
7
+ export declare function compactTokens(n: number | undefined): string;
8
+ /** 注册表若是 ProviderRegistry(或同形状),取模型元数据来源;否则 undefined。 */
9
+ export declare function metadataOf(registry: ProviderRegistryApi, providerId: string, modelId: string): ModelMetadata | undefined;
10
+ /** `ctx 262k · out 64k · 思考 · 图片 · 渠道 chat,messages`。 */
11
+ export declare function modelFlags(model: Model): string;
12
+ /**
13
+ * 来源说明:全部来自内置目录时返回 undefined(不刷屏);否则
14
+ * `来源 ctx models.dev · out config · 图片 models.dev · …;models.dev 原厂 moonshotai/kimi-k2.5`。
15
+ */
16
+ export declare function sourcesLine(metadata: ModelMetadata | undefined): string | undefined;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 模型元数据的一行描述(`ama models list`、`ama config show`、`ama providers`):上下文、输出、思考、
3
+ * 图片、渠道,以及每个字段来自哪里(config / 目录 / models.dev / 缺省)与 models.dev 匹配。
4
+ */
5
+ import { sourceText } from "../../ai/providers/enrich.js";
6
+ import { matchLabel } from "../../ai/providers/models-dev.js";
7
+ export function compactTokens(n) {
8
+ if (n === undefined)
9
+ return "?";
10
+ if (n >= 1_000_000)
11
+ return `${Math.round(n / 100_000) / 10}M`;
12
+ if (n >= 1000)
13
+ return `${Math.round(n / 1000)}k`;
14
+ return String(n);
15
+ }
16
+ /** 注册表若是 ProviderRegistry(或同形状),取模型元数据来源;否则 undefined。 */
17
+ export function metadataOf(registry, providerId, modelId) {
18
+ const r = registry;
19
+ return typeof r.modelMetadata === "function" ? r.modelMetadata(providerId, modelId) : undefined;
20
+ }
21
+ /** `ctx 262k · out 64k · 思考 · 图片 · 渠道 chat,messages`。 */
22
+ export function modelFlags(model) {
23
+ return [
24
+ `ctx ${compactTokens(model.contextWindow)}`,
25
+ `out ${compactTokens(model.maxTokens)}`,
26
+ model.reasoning ? "思考" : undefined,
27
+ model.input.includes("image") ? "图片" : undefined,
28
+ model.channels !== undefined ? `渠道 ${model.channels.join(",")}` : undefined,
29
+ ]
30
+ .filter((x) => x !== undefined)
31
+ .join(" · ");
32
+ }
33
+ /**
34
+ * 来源说明:全部来自内置目录时返回 undefined(不刷屏);否则
35
+ * `来源 ctx models.dev · out config · 图片 models.dev · …;models.dev 原厂 moonshotai/kimi-k2.5`。
36
+ */
37
+ export function sourcesLine(metadata) {
38
+ if (metadata === undefined)
39
+ return undefined;
40
+ const values = Object.values(metadata.sources);
41
+ if (values.every((s) => s === "catalog" || s === "default") && !metadata.looked)
42
+ return undefined;
43
+ const label = {
44
+ contextWindow: "ctx",
45
+ maxTokens: "out",
46
+ input: "图片",
47
+ reasoning: "思考",
48
+ cost: "价格",
49
+ };
50
+ const parts = Object.keys(label).map((field) => `${label[field]} ${sourceText(metadata.sources[field])}`);
51
+ const match = metadata.looked ? `;models.dev ${matchLabel(metadata.match)}` : "";
52
+ const tools = metadata.toolCall === false ? ";不支持工具调用" : "";
53
+ return `来源 ${parts.join(" · ")}${match}${tools}`;
54
+ }
@@ -103,7 +103,7 @@ async function run(ctx) {
103
103
  return ExitCode.NoModel;
104
104
  }
105
105
  const { model, provider } = found;
106
- const key = await registry.resolveApiKey(provider.id);
106
+ const key = await registry.resolveApiKey(provider.id, model.channel);
107
107
  if (key.apiKey === undefined && provider.requiresApiKey) {
108
108
  io.stderr(`ama: ${provider.id} 没有 API key(ama auth set ${provider.id})\n`);
109
109
  return ExitCode.NoModel;
@@ -5,18 +5,21 @@
5
5
  * 带供应商的鉴权头,打印 id 列表并标出已配置的条目。
6
6
  *
7
7
  * `--probe`:同一中转下不同模型支持的协议不同,对每个 id 依次试供应商协议 → completions →
8
- * responses → messages(去重),各发一次最小请求(`ama models check` 同款,maxTokens 16),
9
- * 记第一个成功的协议;每模型最多 3 次请求,`--limit`(缺省 30)限制探测的模型数,执行前打印
10
- * 预估;401 / 403 / 429 立即停止(key 无效或被限流,继续只会浪费请求)。
8
+ * responses → messages(去重),各发一次最小请求(maxTokens 16,见到首个流事件即判可用并断开),
9
+ * 记第一个成功的协议;模型之间并发(`--concurrency`,缺省 6),单次超时 `--probe-timeout`(缺省
10
+ * 15 s)。每模型最多 3 次请求,`--limit`(缺省 30)限制探测的模型数,执行前打印预估;401 / 403 立即
11
+ * 停止,429 降并发并重试一次、仍 429 停止(见 probe-runner.ts)。
11
12
  *
12
13
  * `--write`:新条目合并进用户级 config.json 的 `providers.<id>.models`——已有同 id 不覆盖;只写
13
14
  * `id` 与探到的 `api`(与供应商协议相同时省略);带 `--probe` 时只写探测成功的模型。不猜
14
15
  * `contextWindow`(自动压缩随之关闭,输出 warning)。写前备份为 `config.json.bak`。
16
+ *
17
+ * models.dev:列表之后按需刷新缓存(24 小时内不重拉),每个模型标出上下文、输出、图片、工具调用与
18
+ * 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
15
19
  */
16
20
  import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
17
21
  import type { ModelsAction } from "./models.js";
18
22
  export declare const DISCOVER_TIMEOUT_MS = 15000;
19
- export declare const PROBE_TIMEOUT_MS = 30000;
20
23
  export declare const DEFAULT_PROBE_LIMIT = 30;
21
24
  /** 中转常见的三种协议,按此顺序探测(供应商协议排最前)。 */
22
25
  export declare const PROBE_APIS: readonly Api[];
@@ -28,13 +31,22 @@ export declare function discoverModels(provider: ProviderData, apiKey: string |
28
31
  export declare function probeOrder(provider: ProviderData): Api[];
29
32
  export interface ProbeOptions {
30
33
  timeoutMs?: number;
34
+ /** 同时在途的模型数(每个模型内按协议顺序依次试);缺省 6。 */
35
+ concurrency?: number;
36
+ /** 测试用:429 重试前的退避。 */
37
+ retryDelayMs?: number;
31
38
  /** 每次请求后回调。 */
32
39
  onAttempt?: (id: string, api: Api, error: string | undefined) => void;
33
- /** 每个模型探完回调(进度输出)。 */
40
+ /** 每个模型探完立即回调(按完成顺序,进度用)。 */
41
+ onSettled?: () => void;
42
+ /** 每个模型探完回调,按模型顺序(不按完成顺序)。 */
34
43
  onResult?: (id: string, api: Api | undefined) => void;
35
44
  /** 因鉴权失败或限流提前停止时回调。 */
36
45
  onStop?: (reason: string) => void;
37
46
  }
38
- /** 返回已探测模型 → 第一个成功的协议(都失败为 undefined);超出 `limit` 或提前停止的不在表里。 */
47
+ /**
48
+ * 返回已探测模型 → 第一个成功的协议(都失败为 undefined);超出 `limit` 或因提前停止没探完的不在表里。
49
+ * 模型之间并发(有界),同一模型内按协议顺序依次试,第一个成功即停。
50
+ */
39
51
  export declare function probeModelApis(registry: ProviderRegistryApi, provider: ProviderData, ids: string[], limit: number, options?: ProbeOptions): Promise<Map<string, Api | undefined>>;
40
52
  export declare const DISCOVER_ACTION: ModelsAction;