@armadra/agent 0.6.0 → 0.6.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 (65) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/CHANGELOG.zh-CN.md +20 -0
  3. package/dist/ai/providers/discovered-cache.d.ts +48 -0
  4. package/dist/ai/providers/discovered-cache.js +102 -0
  5. package/dist/ai/providers/model-visibility.d.ts +33 -0
  6. package/dist/ai/providers/model-visibility.js +70 -0
  7. package/dist/ai/providers/registry.d.ts +2 -0
  8. package/dist/ai/providers/registry.js +6 -1
  9. package/dist/auth/chatgpt/cli.d.ts +3 -1
  10. package/dist/auth/chatgpt/cli.js +31 -4
  11. package/dist/bundle/ama.cjs +1343 -640
  12. package/dist/cli/compose-providers.d.ts +1 -0
  13. package/dist/cli/compose-providers.js +6 -0
  14. package/dist/cli/subcommands/config-set.js +19 -11
  15. package/dist/cli/subcommands/models-discover.d.ts +2 -1
  16. package/dist/cli/subcommands/models-discover.js +17 -1
  17. package/dist/cli/subcommands/models-enable.d.ts +13 -0
  18. package/dist/cli/subcommands/models-enable.js +145 -0
  19. package/dist/cli/subcommands/models.d.ts +2 -0
  20. package/dist/cli/subcommands/models.js +10 -2
  21. package/dist/config/json-schema.js +1 -1
  22. package/dist/config/schema-w5.js +9 -2
  23. package/dist/config/settings-registry.d.ts +3 -0
  24. package/dist/config/settings-registry.js +1 -0
  25. package/dist/config/types-w5.d.ts +7 -0
  26. package/dist/config/types-w5.js +2 -0
  27. package/dist/i18n/catalog.d.ts +43 -3
  28. package/dist/i18n/messages/auth.d.ts +4 -2
  29. package/dist/i18n/messages/auth.js +14 -2
  30. package/dist/i18n/messages/cli-help.js +4 -0
  31. package/dist/i18n/messages/config-keys.d.ts +4 -0
  32. package/dist/i18n/messages/config-keys.js +6 -2
  33. package/dist/i18n/messages/config.d.ts +6 -0
  34. package/dist/i18n/messages/config.js +2 -0
  35. package/dist/i18n/messages/errors.d.ts +1 -1
  36. package/dist/i18n/messages/interactive-startup.d.ts +0 -4
  37. package/dist/i18n/messages/interactive-startup.js +0 -4
  38. package/dist/i18n/messages/interactive.d.ts +0 -4
  39. package/dist/i18n/messages/panels.d.ts +43 -0
  40. package/dist/i18n/messages/panels.js +43 -0
  41. package/dist/i18n/messages/settings.d.ts +7 -0
  42. package/dist/i18n/messages/settings.js +7 -0
  43. package/dist/i18n/messages/subcommands-config.d.ts +2 -0
  44. package/dist/i18n/messages/subcommands-config.js +2 -0
  45. package/dist/i18n/messages/subcommands-models.d.ts +34 -0
  46. package/dist/i18n/messages/subcommands-models.js +34 -0
  47. package/dist/i18n/messages/subcommands.d.ts +28 -0
  48. package/dist/i18n/messages/subcommands.js +3 -0
  49. package/dist/modes/interactive/commands.d.ts +3 -0
  50. package/dist/modes/interactive/commands.js +18 -11
  51. package/dist/modes/interactive/config-ui.js +1 -1
  52. package/dist/modes/interactive/interactive-mode.js +2 -0
  53. package/dist/modes/interactive/model-items.d.ts +45 -0
  54. package/dist/modes/interactive/model-items.js +134 -0
  55. package/dist/modes/interactive/model-picker.d.ts +57 -0
  56. package/dist/modes/interactive/model-picker.js +191 -0
  57. package/dist/modes/interactive/startup-ui.d.ts +3 -9
  58. package/dist/modes/interactive/startup-ui.js +8 -60
  59. package/dist/tui/components/select-list.d.ts +2 -0
  60. package/dist/tui/components/select-list.js +7 -0
  61. package/docs/en/providers.md +2 -0
  62. package/docs/en/tui.md +8 -1
  63. package/docs/providers.md +6 -0
  64. package/docs/tui.md +8 -1
  65. package/package.json +1 -1
@@ -12,6 +12,7 @@
12
12
  * - [W6-O] ChatGPT 登录(docs/wave6-plan.md D14):`chatgpt` 的缺省渠道按 auth.json 条目的 flavor 定(用户写了
13
13
  * `defaultChannel` 时不动);`AMA_CHATGPT_BASE_URL` 改该渠道地址;`auth.chatgpt.originator` 改 codex 渠道的
14
14
  * `originator` 头;OAuth 刷新拿到 `auth.chatgpt` 配置与同一份 env。
15
+ * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts)。
15
16
  */
16
17
  import type { ApiRegistry } from "../ai/apis/api.js";
17
18
  import { ProviderRegistry } from "../ai/providers/registry.js";
@@ -12,9 +12,12 @@
12
12
  * - [W6-O] ChatGPT 登录(docs/wave6-plan.md D14):`chatgpt` 的缺省渠道按 auth.json 条目的 flavor 定(用户写了
13
13
  * `defaultChannel` 时不动);`AMA_CHATGPT_BASE_URL` 改该渠道地址;`auth.chatgpt.originator` 改 codex 渠道的
14
14
  * `originator` 头;OAuth 刷新拿到 `auth.chatgpt` 配置与同一份 env。
15
+ * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts)。
15
16
  */
16
17
  import { loadModelsDevIndex } from "../ai/providers/models-dev-cache.js";
17
18
  import { ProviderRegistry, discoverLocalModels } from "../ai/providers/registry.js";
19
+ import { mergeDiscoveredModels } from "../ai/providers/discovered-cache.js";
20
+ import { lazyIndex } from "../ai/providers/enrich.js";
18
21
  import { readOAuthEntry } from "../auth/oauth/token-store.js";
19
22
  import { CHATGPT_BASE_URLS, CHATGPT_PROVIDER_ID } from "../auth/chatgpt/presets.js";
20
23
  export const LOCAL_PROBE_TIMEOUT_MS = 300;
@@ -104,6 +107,9 @@ export async function buildProviderRegistry(input, options = {}) {
104
107
  if (provider !== undefined)
105
108
  registry = make({ provider, apiKey: cli.apiKey });
106
109
  }
110
+ // 发现缓存(`ama auth login chatgpt` / `ama models discover chatgpt`)并入模型表为空的供应商
111
+ if (dataDir !== undefined)
112
+ mergeDiscoveredModels(registry, dataDir, lazyIndex(() => loadModelsDevIndex(dataDir)));
107
113
  const probe = options.probeLocal ?? env[NO_LOCAL_PROBE_ENV] !== "1";
108
114
  if (probe && config.defaultModel === undefined && !anyKeyConfigured(registry)) {
109
115
  await probeLocalProviders(registry, options.probeTimeoutMs ?? LOCAL_PROBE_TIMEOUT_MS);
@@ -62,16 +62,24 @@ function textRow(value) {
62
62
  function scopeLabel(scope) {
63
63
  return msg().settings.scope[scope];
64
64
  }
65
- function report(io, result, unset) {
65
+ /**
66
+ * Receipt of a write. When a higher layer (env, project, cli, profile) still hides the key, the first line shows the
67
+ * value just written and its layer, and the second line names the overriding source and the effective value.
68
+ */
69
+ function report(io, result, written) {
66
70
  const s = msg().settings;
67
- const value = formatSettingValue(result.after.value);
68
- io.stdout(`${unset
69
- ? s.unsetDone(result.key, value, scopeLabel(result.scope))
70
- : s.setDone(result.key, value, scopeLabel(result.scope))}\n`);
71
- if (result.overriddenBy !== undefined) {
72
- const by = result.after.envName ?? result.overriddenBy;
73
- io.stdout(`${s.stillOverridden(result.key, by)}\n`);
74
- }
71
+ const effective = formatSettingValue(result.after.value);
72
+ const scope = scopeLabel(result.scope);
73
+ const by = result.after.envName ?? result.overriddenBy;
74
+ if (written === undefined)
75
+ io.stdout(`${s.unsetDone(result.key, effective, scope)}\n`);
76
+ else if (by === undefined)
77
+ io.stdout(`${s.setDone(result.key, effective, scope)}\n`);
78
+ else
79
+ io.stdout(`${s.writtenTo(result.key, formatSettingValue(written), scope)}\n`);
80
+ if (by === undefined)
81
+ return;
82
+ io.stdout(`${written === undefined ? s.stillOverridden(result.key, by) : s.overriddenNow(by, effective)}\n`);
75
83
  }
76
84
  /** Confirm persisting `permission.mode full-auto`: TTY asks, otherwise `--yes` is required. */
77
85
  async function confirmBypass(io, yes, deps) {
@@ -118,7 +126,7 @@ export async function runConfigEdit(action, argv, io, deps = {}) {
118
126
  return ExitCode.Ok;
119
127
  }
120
128
  if (action === "unset") {
121
- report(io, setConfigValue({ ...input, scope, key, value: undefined }), true);
129
+ report(io, setConfigValue({ ...input, scope, key, value: undefined }), undefined);
122
130
  return ExitCode.Ok;
123
131
  }
124
132
  const raw = positionals.slice(1).join(" ");
@@ -132,6 +140,6 @@ export async function runConfigEdit(action, argv, io, deps = {}) {
132
140
  // first write to a fresh config dir: lay out config.json + config.schema.json like `ama init`
133
141
  if (scope === "user" && !existsSync(join(input.configDir, CONFIG_FILE)))
134
142
  initConfigDir(input.configDir);
135
- report(io, setConfigValue({ ...input, scope, key, value }), value === undefined);
143
+ report(io, setConfigValue({ ...input, scope, key, value }), value);
136
144
  return ExitCode.Ok;
137
145
  }
@@ -18,7 +18,8 @@
18
18
  * 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
19
19
  *
20
20
  * [W6-O] `chatgpt`:按登录的 flavor 调对应后端的模型列表(SIWC `GET /v1/models` 筛 `visibility: list`;codex
21
- * `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。
21
+ * `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
22
+ * 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
22
23
  */
23
24
  import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
24
25
  import type { ModelsAction } from "./models.js";
@@ -18,13 +18,15 @@
18
18
  * 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
19
19
  *
20
20
  * [W6-O] `chatgpt`:按登录的 flavor 调对应后端的模型列表(SIWC `GET /v1/models` 筛 `visibility: list`;codex
21
- * `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。
21
+ * `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
22
+ * 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
22
23
  */
23
24
  import { authHeaders, mergeHeaders } from "../../ai/http.js";
24
25
  import { listChatGptModels } from "../../auth/chatgpt/backend-client.js";
25
26
  import { CHATGPT_PROVIDER_ID } from "../../auth/chatgpt/presets.js";
26
27
  import { liveToken } from "../../auth/oauth/live.js";
27
28
  import { discoverLocalModels, materializeModel } from "../../ai/providers/registry.js";
29
+ import { writeDiscoveredCache } from "../../ai/providers/discovered-cache.js";
28
30
  import { withCustomDefaults } from "../../ai/providers/catalog.js";
29
31
  import { describeModelsDev, loadModelsDevIndex } from "../../ai/providers/models-dev-cache.js";
30
32
  import { matchLabel, modelsDevFields } from "../../ai/providers/models-dev.js";
@@ -197,6 +199,8 @@ async function run(ctx) {
197
199
  }
198
200
  const configured = new Map(provider.models.map((m) => [m.id, m]));
199
201
  io.stdout(m.found(provider.id, found.length, modelsUrl(provider)));
202
+ if (provider.id === CHATGPT_PROVIDER_ID)
203
+ cacheChatGpt(ctx, found, key.apiKey);
200
204
  // models.dev 只读本地(快照 ⊕ `ama models refresh` 的覆盖),不联网。
201
205
  const mdIndex = loadModelsDevIndex(ctx.level.dataDir);
202
206
  io.stdout(`${describeModelsDev(ctx.level.dataDir)}\n`);
@@ -252,6 +256,18 @@ async function run(ctx) {
252
256
  }
253
257
  return ExitCode.Ok;
254
258
  }
259
+ /** chatgpt 的发现结果写进发现缓存(选择器与注册表读它);写失败不影响列表输出。 */
260
+ function cacheChatGpt(ctx, found, apiKey) {
261
+ try {
262
+ const flavor = liveToken(apiKey)?.flavor;
263
+ const models = found.map((m) => ({ id: m.id, ...(m.name !== m.id ? { name: m.name } : {}) }));
264
+ const path = writeDiscoveredCache(ctx.level.dataDir, CHATGPT_PROVIDER_ID, { models, flavor });
265
+ ctx.io.stdout(msg().subcommands.discover.cached(path));
266
+ }
267
+ catch (error) {
268
+ ctx.io.stderr(`ama: ${error.message}\n`);
269
+ }
270
+ }
255
271
  export const DISCOVER_ACTION = {
256
272
  usage: "ama models discover <provider> [--probe] [--write] [--limit <n>] [--concurrency <n>] [--probe-timeout <ms>]",
257
273
  required: "<provider>",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `ama models enable <ref…>` / `ama models disable <ref…>` / `ama models list --enabled`:编辑与查看用户级
3
+ * `models.enabled`(模型选择器的显式清单,见 ai/providers/model-visibility.ts)。
4
+ *
5
+ * 写回走 config/edit.ts(重读目标文件、校验、`.bak` 备份、原子写);`models` 只认用户级,没有 `--project`。
6
+ * 引用要是 `provider/model[@channel]` 或 `provider/*`,供应商必须存在;模型不在模型表里只警告(中转站与
7
+ * 订阅后端接受任意 id)。清单移空时删掉这个键。
8
+ */
9
+ import type { ModelsAction, ModelsActionContext } from "./models.js";
10
+ /** `ama models list --enabled`:清单(及各项供应商状态);没设清单时列出选择器缺省显示的模型。 */
11
+ export declare function listEnabled(ctx: ModelsActionContext): Promise<number>;
12
+ export declare const ENABLE_ACTION: ModelsAction;
13
+ export declare const DISABLE_ACTION: ModelsAction;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * `ama models enable <ref…>` / `ama models disable <ref…>` / `ama models list --enabled`:编辑与查看用户级
3
+ * `models.enabled`(模型选择器的显式清单,见 ai/providers/model-visibility.ts)。
4
+ *
5
+ * 写回走 config/edit.ts(重读目标文件、校验、`.bak` 备份、原子写);`models` 只认用户级,没有 `--project`。
6
+ * 引用要是 `provider/model[@channel]` 或 `provider/*`,供应商必须存在;模型不在模型表里只警告(中转站与
7
+ * 订阅后端接受任意 id)。清单移空时删掉这个键。
8
+ */
9
+ import { accessReady, addEnabled, enabledList, providerAccess, removeEnabled, splitEnabledRef, } from "../../ai/providers/model-visibility.js";
10
+ import { readSnapshot, setConfigValue } from "../../config/edit.js";
11
+ import { MODELS_ENABLED_REF } from "../../config/types-w5.js";
12
+ import { msg } from "../../i18n/index.js";
13
+ import { UsageError } from "../args.js";
14
+ import { ExitCode } from "../exit-codes.js";
15
+ function layerInput(ctx) {
16
+ const input = {
17
+ configDir: ctx.level.configDir,
18
+ cwd: ctx.io.cwd,
19
+ env: ctx.io.env,
20
+ };
21
+ if (ctx.level.profile !== undefined) {
22
+ input.profile = { config: ctx.level.profile.config };
23
+ input.hasProfile = true;
24
+ }
25
+ return input;
26
+ }
27
+ /** 校验引用;供应商不存在返回退出码,模型不在表里只警告。 */
28
+ function checkRefs(ctx, refs) {
29
+ const m = msg().subcommands.modelsEnabled;
30
+ for (const ref of refs) {
31
+ const parts = splitEnabledRef(ref);
32
+ if (!MODELS_ENABLED_REF.test(ref) || parts === undefined)
33
+ throw new UsageError(m.badRef(ref));
34
+ const provider = ctx.registry.get(parts.provider);
35
+ if (provider === undefined) {
36
+ ctx.io.stderr(msg().subcommands.common.providerNotFound(parts.provider));
37
+ return ExitCode.NoModel;
38
+ }
39
+ if (parts.model !== "*" &&
40
+ provider.models.length > 0 &&
41
+ !provider.models.some((model) => model.id === parts.model))
42
+ ctx.io.stderr(m.unknownModel(ref));
43
+ }
44
+ return undefined;
45
+ }
46
+ function userList(input) {
47
+ return enabledList(readSnapshot(input).user?.models?.enabled);
48
+ }
49
+ async function enable(ctx) {
50
+ const m = msg().subcommands.modelsEnabled;
51
+ const refs = [...new Set(ctx.args)];
52
+ const bad = checkRefs(ctx, refs);
53
+ if (bad !== undefined)
54
+ return bad;
55
+ const input = layerInput(ctx);
56
+ const current = userList(input);
57
+ const fresh = refs.filter((ref) => !(current ?? []).includes(ref));
58
+ if (fresh.length === 0) {
59
+ ctx.io.stdout(m.alreadyListed(refs.join(", ")));
60
+ return ExitCode.Ok;
61
+ }
62
+ const result = setConfigValue({
63
+ ...input,
64
+ scope: "user",
65
+ key: "models.enabled",
66
+ value: addEnabled(current, fresh),
67
+ });
68
+ ctx.io.stdout(m.added(fresh.join(", "), result.path));
69
+ return ExitCode.Ok;
70
+ }
71
+ async function disable(ctx) {
72
+ const m = msg().subcommands.modelsEnabled;
73
+ const refs = [...new Set(ctx.args)];
74
+ for (const ref of refs)
75
+ if (!MODELS_ENABLED_REF.test(ref))
76
+ throw new UsageError(m.badRef(ref));
77
+ const input = layerInput(ctx);
78
+ const current = userList(input) ?? [];
79
+ const listed = refs.filter((ref) => current.includes(ref));
80
+ const missing = refs.filter((ref) => !current.includes(ref));
81
+ if (missing.length > 0)
82
+ ctx.io.stderr(m.notListed(missing.join(", ")));
83
+ if (listed.length === 0)
84
+ return missing.length > 0 ? ExitCode.RuntimeError : ExitCode.Ok;
85
+ const next = removeEnabled(current, listed);
86
+ const result = setConfigValue({ ...input, scope: "user", key: "models.enabled", value: next });
87
+ ctx.io.stdout(next === undefined ? m.cleared(result.path) : m.removed(listed.join(", "), result.path));
88
+ return ExitCode.Ok;
89
+ }
90
+ /** `ama models list --enabled`:清单(及各项供应商状态);没设清单时列出选择器缺省显示的模型。 */
91
+ export async function listEnabled(ctx) {
92
+ const m = msg().subcommands.modelsEnabled;
93
+ const labels = msg().panels.model;
94
+ const list = enabledList(ctx.level.merged.config.models?.enabled);
95
+ const status = async (providerId) => {
96
+ const provider = ctx.registry.get(providerId);
97
+ if (provider === undefined)
98
+ return "?";
99
+ const access = await providerAccess(ctx.registry, provider);
100
+ return access === "none" ? labels.noKey : labels[access];
101
+ };
102
+ if (list !== undefined) {
103
+ ctx.io.stdout(m.listHeader(ctx.level.userConfigPath));
104
+ for (const ref of list) {
105
+ const parts = splitEnabledRef(ref);
106
+ const provider = parts === undefined ? undefined : ctx.registry.get(parts.provider);
107
+ const known = parts === undefined ||
108
+ parts.model === "*" ||
109
+ provider === undefined ||
110
+ provider.models.length === 0 ||
111
+ provider.models.some((model) => model.id === parts.model);
112
+ const notes = [
113
+ parts === undefined ? "?" : await status(parts.provider),
114
+ ...(known ? [] : [m.notInTable]),
115
+ ];
116
+ ctx.io.stdout(` ${ref} ${notes.join(" · ")}\n`);
117
+ }
118
+ return ExitCode.Ok;
119
+ }
120
+ ctx.io.stdout(m.listUnset);
121
+ let shown = 0;
122
+ for (const provider of ctx.registry.list()) {
123
+ const access = await providerAccess(ctx.registry, provider);
124
+ if (!accessReady(access))
125
+ continue;
126
+ const label = access === "none" ? labels.noKey : labels[access];
127
+ for (const model of provider.models) {
128
+ ctx.io.stdout(` ${provider.id}/${model.id} ${label}\n`);
129
+ shown++;
130
+ }
131
+ }
132
+ if (shown === 0)
133
+ ctx.io.stdout(m.nothing);
134
+ return ExitCode.Ok;
135
+ }
136
+ export const ENABLE_ACTION = {
137
+ usage: "ama models enable <provider/model[@channel]|provider/*>…",
138
+ required: "<provider/model>",
139
+ run: enable,
140
+ };
141
+ export const DISABLE_ACTION = {
142
+ usage: "ama models disable <provider/model[@channel]|provider/*>…",
143
+ required: "<provider/model>",
144
+ run: disable,
145
+ };
@@ -9,6 +9,8 @@
9
9
  * 选项表与必填位置参数都从表里来。
10
10
  *
11
11
  * [W5-M1] `refresh`:models.dev 显式刷新(`refresh-catalog` 保留为别名)。
12
+ *
13
+ * `enable` / `disable` / `list --enabled`:模型选择器清单 `models.enabled`(models-enable.ts)。
12
14
  */
13
15
  import type { ProviderRegistryApi } from "../../ai/types.js";
14
16
  import type { CliIo, RuntimeDeps } from "../deps.js";
@@ -9,6 +9,8 @@
9
9
  * 选项表与必填位置参数都从表里来。
10
10
  *
11
11
  * [W5-M1] `refresh`:models.dev 显式刷新(`refresh-catalog` 保留为别名)。
12
+ *
13
+ * `enable` / `disable` / `list --enabled`:模型选择器清单 `models.enabled`(models-enable.ts)。
12
14
  */
13
15
  import { formatModelRef, modelRefOf } from "../../ai/providers/channels.js";
14
16
  import { describeRefresh, modelsDevCachePath, refreshModelsDev, } from "../../ai/providers/models-dev-cache.js";
@@ -18,6 +20,7 @@ import { ExitCode } from "../exit-codes.js";
18
20
  import { buildRegistry, loadUserLevel } from "./context.js";
19
21
  import { CACHE_PROBE_ACTION } from "./models-cache-probe.js";
20
22
  import { DISCOVER_ACTION } from "./models-discover.js";
23
+ import { DISABLE_ACTION, ENABLE_ACTION, listEnabled } from "./models-enable.js";
21
24
  import { metadataOf, modelFlags, sourcesLine } from "./model-meta.js";
22
25
  import { msg } from "../../i18n/index.js";
23
26
  const COMMON_VALUE_OPTIONS = ["profile", "auth-file"];
@@ -113,9 +116,12 @@ const REFRESH_ACTION = {
113
116
  /** 动作表:键是子命令名,顺序即用法文本顺序。 */
114
117
  export const MODELS_ACTIONS = Object.freeze({
115
118
  list: {
116
- usage: "ama models list [--provider <id>]",
119
+ usage: "ama models list [--provider <id>] [--enabled]",
117
120
  valueOptions: ["provider"],
118
- run: (ctx) => list(ctx.io, ctx.registry, ctx.values.get("provider")),
121
+ flagOptions: ["enabled"],
122
+ run: (ctx) => ctx.flags.has("enabled")
123
+ ? listEnabled(ctx)
124
+ : list(ctx.io, ctx.registry, ctx.values.get("provider")),
119
125
  },
120
126
  check: {
121
127
  usage: "ama models check <provider/id>",
@@ -132,6 +138,8 @@ export const MODELS_ACTIONS = Object.freeze({
132
138
  return msg().subcommands.models.refreshCatalogUsage;
133
139
  },
134
140
  },
141
+ enable: ENABLE_ACTION,
142
+ disable: DISABLE_ACTION,
135
143
  });
136
144
  export function modelsUsage() {
137
145
  return msg().subcommands.models.usage(Object.values(MODELS_ACTIONS).map((a) => a.usage));
@@ -151,7 +151,7 @@ function w5Sections() {
151
151
  },
152
152
  }),
153
153
  subagents: object({ maxConcurrent: num(1, 64), maxPending: num(0, 1024), defaultModel: str() }),
154
- models: object({ aliases: object({ fast: str(), strong: str() }) }),
154
+ models: object({ aliases: object({ fast: str(), strong: str() }), enabled: strings }),
155
155
  fallbackModel: str(),
156
156
  limits: object({ maxTurns: num(1), maxCostUsd: num(0) }),
157
157
  reminders: object({
@@ -7,7 +7,7 @@
7
7
  import { PERMISSION_MODES_STRICT_FIRST } from "../permissions/types.js";
8
8
  import { msg } from "../i18n/index.js";
9
9
  import { Checker, THINKING_LEVELS, checkSection, join } from "./checker.js";
10
- import { AGENTS_RESERVED_KEYS, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, } from "./types-w5.js";
10
+ import { AGENTS_RESERVED_KEYS, IMAGE_RESIZE_MODES, MODELS_ENABLED_REF, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, } from "./types-w5.js";
11
11
  /** 第五波新增的顶层键(schema.ts 的 CONFIG_KEYS 并入)。 */
12
12
  export const W5_CONFIG_KEYS = [
13
13
  "images",
@@ -88,7 +88,14 @@ export function validateConfigW5(c, config) {
88
88
  c.number(s, "maxPending", p, 0, 1024);
89
89
  c.string(s, "defaultModel", p);
90
90
  });
91
- checkSection(c, config, "models", ["aliases"], (s, p) => {
91
+ checkSection(c, config, "models", ["aliases", "enabled"], (s, p) => {
92
+ c.stringArray(s, "enabled", p);
93
+ const enabled = s["enabled"];
94
+ if (Array.isArray(enabled))
95
+ enabled.forEach((ref, i) => {
96
+ if (typeof ref === "string" && !MODELS_ENABLED_REF.test(ref))
97
+ c.error(join(join(p, "enabled"), i), msg().config.schema.modelsEnabled);
98
+ });
92
99
  const aliases = s["aliases"];
93
100
  const ap = join(p, "aliases");
94
101
  if (aliases === undefined || !c.object(aliases, ap))
@@ -59,6 +59,9 @@ export declare const SETTING_HINTS: readonly [{
59
59
  }, {
60
60
  readonly key: "auth.chatgpt";
61
61
  readonly hint: "auth";
62
+ }, {
63
+ readonly key: "models.enabled";
64
+ readonly hint: "modelsEnabled";
62
65
  }];
63
66
  export type SettingHintId = (typeof SETTING_HINTS)[number]["hint"];
64
67
  type Schema = Record<string, unknown>;
@@ -106,6 +106,7 @@ export const SETTING_HINTS = [
106
106
  { key: "agents.<id>", hint: "agents" },
107
107
  { key: "hooks", hint: "hooks" },
108
108
  { key: "auth.chatgpt", hint: "auth" },
109
+ { key: "models.enabled", hint: "modelsEnabled" },
109
110
  ];
110
111
  /** Schema node of a dotted key (undefined when the key is not in the schema). */
111
112
  export function schemaAt(key, root = buildConfigJsonSchema()) {
@@ -68,12 +68,19 @@ export interface SubagentsConfig {
68
68
  /** 子会话缺省模型;不设继承父会话。 */
69
69
  defaultModel?: string;
70
70
  }
71
+ /** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
72
+ export declare const MODELS_ENABLED_REF: RegExp;
71
73
  /** [W5-G] 模型别名(子 Agent 定义的 `model: fast | strong`)。 */
72
74
  export interface ModelsConfig {
73
75
  aliases?: {
74
76
  fast?: string;
75
77
  strong?: string;
76
78
  };
79
+ /**
80
+ * 模型选择器的显式清单(`provider/model[@channel]`,`provider/*` 整个供应商):设置后 `/model` 只显示清单内
81
+ * 的模型(加当前模型);不设时显示已配置 key / 登录 / 本地可达的供应商的全部模型。只认用户级。
82
+ */
83
+ enabled?: string[];
77
84
  }
78
85
  /** [W5-H1] 档一裁剪参数(其余随窗口缩放的常量不暴露)。 */
79
86
  export interface PruneConfig {
@@ -18,3 +18,5 @@ export function agentEntry(agents, agentId) {
18
18
  const entry = agents[agentId];
19
19
  return typeof entry === "object" && !Array.isArray(entry) ? entry : undefined;
20
20
  }
21
+ /** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
22
+ export const MODELS_ENABLED_REF = /^[^/\s]+\/\S+$/;
@@ -392,6 +392,19 @@ declare const en: {
392
392
  noHits: (all: boolean) => "No matches\n" | "No matches (only sessions of this directory; --all searches all)\n";
393
393
  truncated: (limit: number) => string;
394
394
  };
395
+ modelsEnabled: {
396
+ badRef: (ref: string) => string;
397
+ unknownModel: (ref: string) => string;
398
+ added: (refs: string, path: string) => string;
399
+ alreadyListed: (refs: string) => string;
400
+ removed: (refs: string, path: string) => string;
401
+ notListed: (refs: string) => string;
402
+ cleared: (path: string) => string;
403
+ listHeader: (path: string) => string;
404
+ listUnset: string;
405
+ notInTable: string;
406
+ nothing: string;
407
+ };
395
408
  configShow: {
396
409
  usage: string;
397
410
  unknownAction: (action: string) => string;
@@ -441,6 +454,7 @@ declare const en: {
441
454
  probeHeader: (count: number, order: string, requests: number, extra: number, limit: number) => string;
442
455
  unavailable: string;
443
456
  probeStopped: (reason: string) => string;
457
+ cached: (path: string) => string;
444
458
  };
445
459
  };
446
460
  interactive: {
@@ -636,8 +650,6 @@ declare const en: {
636
650
  hintFilter: (arrows: string) => string;
637
651
  hintNumbers: (arrows: string, n: number) => string;
638
652
  hint: (arrows: string) => string;
639
- local: string;
640
- noKey: string;
641
653
  justNow: string;
642
654
  minutesAgo: (n: number) => string;
643
655
  hoursAgo: (n: number) => string;
@@ -917,6 +929,27 @@ declare const en: {
917
929
  thinkingUnsupported: string;
918
930
  emptyText: string;
919
931
  };
932
+ model: {
933
+ local: string;
934
+ key: string;
935
+ oauth: string;
936
+ needsLogin: string;
937
+ noKey: string;
938
+ current: string;
939
+ listed: string;
940
+ otherChannels: (names: string) => string;
941
+ discoverHint: (provider: string) => string;
942
+ loginHint: (provider: string) => string;
943
+ footerConfigured: string;
944
+ footerAll: string;
945
+ emptyConfigured: string;
946
+ added: (ref: string) => string;
947
+ addedFirst: (ref: string) => string;
948
+ removed: (ref: string) => string;
949
+ wildcard: (ref: string, pattern: string) => string;
950
+ noKeyHint: (provider: string) => string;
951
+ needsLoginHint: (provider: string) => string;
952
+ };
920
953
  plan: {
921
954
  title: string;
922
955
  titleVersion: (version: number) => string;
@@ -1351,6 +1384,7 @@ declare const en: {
1351
1384
  "models.aliases": string;
1352
1385
  "models.aliases.fast": string;
1353
1386
  "models.aliases.strong": string;
1387
+ "models.enabled": string;
1354
1388
  fallbackModel: string;
1355
1389
  limits: string;
1356
1390
  "limits.maxTurns": string;
@@ -1377,6 +1411,7 @@ declare const en: {
1377
1411
  "subagents.defaultModel": string;
1378
1412
  "models.aliases.fast": string;
1379
1413
  "models.aliases.strong": string;
1414
+ "models.enabled": string;
1380
1415
  fallbackModel: string;
1381
1416
  "limits.maxTurns": string;
1382
1417
  "limits.maxCostUsd": string;
@@ -1402,6 +1437,7 @@ declare const en: {
1402
1437
  stringArray: string;
1403
1438
  array: string;
1404
1439
  modelsDev: string;
1440
+ modelsEnabled: string;
1405
1441
  noChannels: string;
1406
1442
  unknownChannelOf: (name: string, available: readonly string[]) => string;
1407
1443
  modelInput: string;
@@ -1937,11 +1973,12 @@ declare const en: {
1937
1973
  siwcLimitHint: string;
1938
1974
  freePlanHint: (plan: string) => string;
1939
1975
  modelHint: string;
1976
+ modelsCached: (count: number) => string;
1940
1977
  };
1941
1978
  errors: {
1942
1979
  portsBusy: (ports: string, codex: boolean) => string;
1943
1980
  timeout: string;
1944
- denied: (error: string) => string;
1981
+ denied: (error: string, codexFallback: boolean) => string;
1945
1982
  stateMismatch: string;
1946
1983
  noCode: string;
1947
1984
  invalidToken: (reason: string) => string;
@@ -2116,6 +2153,8 @@ declare const en: {
2116
2153
  setDone: (key: string, value: string, scope: string) => string;
2117
2154
  unsetDone: (key: string, value: string, scope: string) => string;
2118
2155
  stillOverridden: (key: string, source: string) => string;
2156
+ writtenTo: (key: string, value: string, scope: string) => string;
2157
+ overriddenNow: (source: string, effective: string) => string;
2119
2158
  slashUsage: string;
2120
2159
  errors: {
2121
2160
  unknownKey: (key: string) => string;
@@ -2144,6 +2183,7 @@ declare const en: {
2144
2183
  agents: string;
2145
2184
  hooks: string;
2146
2185
  auth: string;
2186
+ modelsEnabled: string;
2147
2187
  };
2148
2188
  cli: {
2149
2189
  usage: string;
@@ -45,11 +45,12 @@ export declare const en: {
45
45
  siwcLimitHint: string;
46
46
  freePlanHint: (plan: string) => string;
47
47
  modelHint: string;
48
+ modelsCached: (count: number) => string;
48
49
  };
49
50
  errors: {
50
51
  portsBusy: (ports: string, codex: boolean) => string;
51
52
  timeout: string;
52
- denied: (error: string) => string;
53
+ denied: (error: string, codexFallback: boolean) => string;
53
54
  stateMismatch: string;
54
55
  noCode: string;
55
56
  invalidToken: (reason: string) => string;
@@ -135,11 +136,12 @@ export declare const zh: {
135
136
  siwcLimitHint: string;
136
137
  freePlanHint: (plan: string) => string;
137
138
  modelHint: string;
139
+ modelsCached: (count: number) => string;
138
140
  };
139
141
  errors: {
140
142
  portsBusy: (ports: string, codex: boolean) => string;
141
143
  timeout: string;
142
- denied: (error: string) => string;
144
+ denied: (error: string, codexFallback: boolean) => string;
143
145
  stateMismatch: string;
144
146
  noCode: string;
145
147
  invalidToken: (reason: string) => string;
@@ -46,11 +46,18 @@ export const en = {
46
46
  siwcLimitHint: "Tip: set a weekly limit for ama in ChatGPT → Settings → Usage → App limits. For personal use only; never share one sign-in between several people.",
47
47
  freePlanHint: (plan) => `Note: the "${plan}" plan may not include subscription usage for ama.`,
48
48
  modelHint: "Use it with --model chatgpt/<model>; list the models your account can use with ama models discover chatgpt.",
49
+ modelsCached: (count) => `Your account can use ${count} model(s); pick one with /model or --model chatgpt/<model>.`,
49
50
  },
50
51
  errors: {
51
52
  portsBusy: (ports, codex) => `callback port ${ports} is in use: finish the other sign-in first, or use --paste${codex ? " / --device" : ""}`,
52
53
  timeout: "sign-in timed out; run the command again",
53
- denied: (error) => `sign-in was not authorized (${error})`,
54
+ denied: (error, codexFallback) => `sign-in was not authorized (${error}). Possible causes:\n` +
55
+ " 1. The authorization page was cancelled, or using your ChatGPT plan was not checked: run ama auth login chatgpt again and keep that option checked\n" +
56
+ " 2. The account or plan is not eligible: plan usage is shared with Plus / Pro only; Team / Enterprise workspaces may not offer it; the browser may be signed in to another account\n" +
57
+ " 3. Your region is not supported" +
58
+ (codexFallback
59
+ ? "\nYou can also try the fallback: ama auth login chatgpt --flavor codex"
60
+ : ""),
54
61
  stateMismatch: "the pasted URL does not belong to this sign-in (state mismatch); start again",
55
62
  noCode: "the pasted URL has no code; copy the whole address after the browser redirects",
56
63
  invalidToken: (reason) => `id_token failed verification (${reason}); sign-in rejected`,
@@ -137,11 +144,16 @@ export const zh = {
137
144
  siwcLimitHint: "提示:可在 ChatGPT → 设置 → Usage → App limits 给 ama 设周上限。仅限本人使用,不要让多人共用一个登录。",
138
145
  freePlanHint: (plan) => `注意:「${plan}」计划可能不含给 ama 用的订阅额度。`,
139
146
  modelHint: "用 --model chatgpt/<模型> 使用;账户可用的模型用 ama models discover chatgpt 查看。",
147
+ modelsCached: (count) => `账户可用 ${count} 个模型,用 /model 或 --model chatgpt/<模型> 选择。`,
140
148
  },
141
149
  errors: {
142
150
  portsBusy: (ports, codex) => `回调端口 ${ports} 被占用:请先结束其它登录,或用 --paste${codex ? " / --device" : ""}`,
143
151
  timeout: "登录超时,请重新运行命令",
144
- denied: (error) => `授权未通过(${error})`,
152
+ denied: (error, codexFallback) => `授权未通过(${error})。可能的原因:\n` +
153
+ " 1. 在授权页取消了,或没有勾选使用 ChatGPT 套餐额度:重新运行 ama auth login chatgpt 并确认勾选\n" +
154
+ " 2. 账户或套餐不符合:额度共享只对 Plus / Pro 开放;Team / Enterprise 工作空间可能未开放;浏览器可能登录了别的账户\n" +
155
+ " 3. 所在地区受限" +
156
+ (codexFallback ? "\n也可以改用备用方式:ama auth login chatgpt --flavor codex" : ""),
145
157
  stateMismatch: "粘贴的 URL 不属于这次登录(state 不符),请重新开始",
146
158
  noCode: "粘贴的 URL 里没有 code;请在浏览器跳转后复制完整地址",
147
159
  invalidToken: (reason) => `id_token 校验失败(${reason}),已拒绝这次登录`,
@@ -91,6 +91,8 @@ Subcommands
91
91
  Timeline of turns, requests, tools and subagents (text / HTML / JSON)
92
92
  ama models list [--provider <id>] List models (with source and key status)
93
93
  ama models check <provider/id> Send one minimal request to check availability
94
+ ama models enable|disable <provider/model|provider/*>…
95
+ Edit the /model list (models.enabled); list --enabled shows it
94
96
  ama models discover <provider> [--probe] [--write] [--limit N]
95
97
  List models from a relay's /v1/models, probe protocols, write the config
96
98
  ama models refresh [--provider <id>] Refresh models.dev metadata into the data directory (startup stays
@@ -206,6 +208,8 @@ export const zh = {
206
208
  回合、请求、工具与子 Agent 的时间线(文本 / HTML / JSON)
207
209
  ama models list [--provider <id>] 列出模型(含来源与 key 状态)
208
210
  ama models check <provider/id> 发一次最小请求检查可用性
211
+ ama models enable|disable <provider/model|provider/*>…
212
+ 编辑 /model 的模型清单(models.enabled);list --enabled 查看
209
213
  ama models discover <provider> [--probe] [--write] [--limit N]
210
214
  从中转 /v1/models 列出模型,探测协议并写入配置
211
215
  ama models refresh [--provider <id>] 联网刷新 models.dev 元数据到数据目录(启动不联网;