@armadra/agent 0.6.0 → 0.6.2
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.
- package/CHANGELOG.md +43 -0
- package/CHANGELOG.zh-CN.md +32 -0
- package/dist/agent/session-settings.d.ts +4 -1
- package/dist/agent/session-settings.js +7 -2
- package/dist/ai/apis/chatgpt-backend.d.ts +7 -2
- package/dist/ai/apis/chatgpt-backend.js +12 -4
- package/dist/ai/providers/discovered-cache.d.ts +59 -0
- package/dist/ai/providers/discovered-cache.js +146 -0
- package/dist/ai/providers/model-visibility.d.ts +33 -0
- package/dist/ai/providers/model-visibility.js +70 -0
- package/dist/ai/providers/registry.d.ts +2 -0
- package/dist/ai/providers/registry.js +13 -7
- package/dist/ai/types.d.ts +5 -0
- package/dist/auth/chatgpt/backend-client.d.ts +11 -2
- package/dist/auth/chatgpt/backend-client.js +38 -6
- package/dist/auth/chatgpt/cli.d.ts +4 -1
- package/dist/auth/chatgpt/cli.js +40 -6
- package/dist/auth/chatgpt/follow.d.ts +16 -0
- package/dist/auth/chatgpt/follow.js +51 -0
- package/dist/auth/chatgpt/presets.d.ts +8 -0
- package/dist/auth/chatgpt/presets.js +12 -0
- package/dist/auth/testing/fake-oauth.d.ts +4 -1
- package/dist/auth/testing/fake-oauth.js +24 -1
- package/dist/bundle/ama.cjs +1663 -799
- package/dist/cli/compose-providers.d.ts +2 -0
- package/dist/cli/compose-providers.js +9 -0
- package/dist/cli/startup-steps.js +4 -1
- package/dist/cli/subcommands/config-set.js +19 -11
- package/dist/cli/subcommands/models-discover.d.ts +4 -1
- package/dist/cli/subcommands/models-discover.js +51 -3
- package/dist/cli/subcommands/models-enable.d.ts +13 -0
- package/dist/cli/subcommands/models-enable.js +145 -0
- package/dist/cli/subcommands/models.d.ts +2 -0
- package/dist/cli/subcommands/models.js +10 -2
- package/dist/config/json-schema.js +2 -1
- package/dist/config/key-docs.js +7 -1
- package/dist/config/schema-w5.js +9 -2
- package/dist/config/schema-w6.d.ts +1 -1
- package/dist/config/schema-w6.js +2 -1
- package/dist/config/settings-registry.d.ts +3 -0
- package/dist/config/settings-registry.js +1 -0
- package/dist/config/types-w5.d.ts +7 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types-w6.d.ts +5 -0
- package/dist/i18n/catalog.d.ts +50 -3
- package/dist/i18n/messages/auth.d.ts +16 -2
- package/dist/i18n/messages/auth.js +34 -2
- package/dist/i18n/messages/cli-help.js +4 -0
- package/dist/i18n/messages/config-keys.d.ts +6 -0
- package/dist/i18n/messages/config-keys.js +8 -2
- package/dist/i18n/messages/config.d.ts +8 -0
- package/dist/i18n/messages/config.js +2 -0
- package/dist/i18n/messages/errors.d.ts +1 -1
- package/dist/i18n/messages/interactive-startup.d.ts +0 -4
- package/dist/i18n/messages/interactive-startup.js +0 -4
- package/dist/i18n/messages/interactive.d.ts +0 -4
- package/dist/i18n/messages/panels.d.ts +43 -0
- package/dist/i18n/messages/panels.js +43 -0
- package/dist/i18n/messages/settings.d.ts +7 -0
- package/dist/i18n/messages/settings.js +7 -0
- package/dist/i18n/messages/subcommands-config.d.ts +2 -0
- package/dist/i18n/messages/subcommands-config.js +2 -0
- package/dist/i18n/messages/subcommands-models.d.ts +34 -0
- package/dist/i18n/messages/subcommands-models.js +34 -0
- package/dist/i18n/messages/subcommands.d.ts +28 -0
- package/dist/i18n/messages/subcommands.js +3 -0
- package/dist/modes/interactive/commands.d.ts +3 -0
- package/dist/modes/interactive/commands.js +18 -11
- package/dist/modes/interactive/config-ui.js +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -0
- package/dist/modes/interactive/model-items.d.ts +45 -0
- package/dist/modes/interactive/model-items.js +134 -0
- package/dist/modes/interactive/model-picker.d.ts +57 -0
- package/dist/modes/interactive/model-picker.js +191 -0
- package/dist/modes/interactive/startup-ui.d.ts +3 -9
- package/dist/modes/interactive/startup-ui.js +8 -60
- package/dist/tui/components/select-list.d.ts +2 -0
- package/dist/tui/components/select-list.js +7 -0
- package/docs/en/providers.md +9 -3
- package/docs/en/tui.md +8 -1
- package/docs/providers.md +27 -6
- package/docs/tui.md +8 -1
- package/package.json +1 -1
|
@@ -12,6 +12,8 @@
|
|
|
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);
|
|
16
|
+
* chatgpt 的缓存 flavor 与当前登录不符时视为过期、不并入。
|
|
15
17
|
*/
|
|
16
18
|
import type { ApiRegistry } from "../ai/apis/api.js";
|
|
17
19
|
import { ProviderRegistry } from "../ai/providers/registry.js";
|
|
@@ -12,9 +12,13 @@
|
|
|
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);
|
|
16
|
+
* chatgpt 的缓存 flavor 与当前登录不符时视为过期、不并入。
|
|
15
17
|
*/
|
|
16
18
|
import { loadModelsDevIndex } from "../ai/providers/models-dev-cache.js";
|
|
17
19
|
import { ProviderRegistry, discoverLocalModels } from "../ai/providers/registry.js";
|
|
20
|
+
import { mergeDiscoveredModels } from "../ai/providers/discovered-cache.js";
|
|
21
|
+
import { lazyIndex } from "../ai/providers/enrich.js";
|
|
18
22
|
import { readOAuthEntry } from "../auth/oauth/token-store.js";
|
|
19
23
|
import { CHATGPT_BASE_URLS, CHATGPT_PROVIDER_ID } from "../auth/chatgpt/presets.js";
|
|
20
24
|
export const LOCAL_PROBE_TIMEOUT_MS = 300;
|
|
@@ -104,6 +108,11 @@ export async function buildProviderRegistry(input, options = {}) {
|
|
|
104
108
|
if (provider !== undefined)
|
|
105
109
|
registry = make({ provider, apiKey: cli.apiKey });
|
|
106
110
|
}
|
|
111
|
+
// 发现缓存(`ama auth login chatgpt` / `ama models discover chatgpt`)并入模型表为空的供应商
|
|
112
|
+
if (dataDir !== undefined) {
|
|
113
|
+
const flavor = readOAuthEntry(input.authFile, CHATGPT_PROVIDER_ID)?.flavor;
|
|
114
|
+
mergeDiscoveredModels(registry, dataDir, lazyIndex(() => loadModelsDevIndex(dataDir)), (id) => (id === CHATGPT_PROVIDER_ID ? flavor : undefined));
|
|
115
|
+
}
|
|
107
116
|
const probe = options.probeLocal ?? env[NO_LOCAL_PROBE_ENV] !== "1";
|
|
108
117
|
if (probe && config.defaultModel === undefined && !anyKeyConfigured(registry)) {
|
|
109
118
|
await probeLocalProviders(registry, options.probeTimeoutMs ?? LOCAL_PROBE_TIMEOUT_MS);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { describeLookupFailure } from "../ai/providers/suggest.js";
|
|
6
6
|
import { formatModelRef } from "../ai/providers/channels.js";
|
|
7
|
+
import { CHATGPT_PROVIDER_ID } from "../auth/chatgpt/presets.js";
|
|
7
8
|
import { statSync } from "node:fs";
|
|
8
9
|
import { isAbsolute, resolve } from "node:path";
|
|
9
10
|
import { AmaError, StartupError, isAmaError } from "../errors.js";
|
|
@@ -126,10 +127,12 @@ export async function resolveModel(args, registry, manager, defaultModel, deps,
|
|
|
126
127
|
else {
|
|
127
128
|
const change = lastEntry(manager, (e) => e.type === "model_change");
|
|
128
129
|
if (change?.type === "model_change") {
|
|
130
|
+
// ChatGPT 的渠道跟随当前登录方式:会话里记的渠道不当显式渠道(可能是换登录前的)
|
|
131
|
+
const channel = change.provider === CHATGPT_PROVIDER_ID ? undefined : change.channel;
|
|
129
132
|
const found = registry.findModel(formatModelRef({
|
|
130
133
|
provider: change.provider,
|
|
131
134
|
id: change.modelId,
|
|
132
|
-
...(
|
|
135
|
+
...(channel !== undefined ? { channel } : {}),
|
|
133
136
|
}));
|
|
134
137
|
if (found.ok)
|
|
135
138
|
choice = { model: found.model, provider: found.provider };
|
|
@@ -62,16 +62,24 @@ function textRow(value) {
|
|
|
62
62
|
function scopeLabel(scope) {
|
|
63
63
|
return msg().settings.scope[scope];
|
|
64
64
|
}
|
|
65
|
-
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
io.stdout(`${s.
|
|
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 }),
|
|
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
|
|
143
|
+
report(io, setConfigValue({ ...input, scope, key, value }), value);
|
|
136
144
|
return ExitCode.Ok;
|
|
137
145
|
}
|
|
@@ -18,7 +18,9 @@
|
|
|
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
|
|
21
|
+
* `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
|
|
22
|
+
* `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
|
|
23
|
+
* (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
|
|
22
24
|
*/
|
|
23
25
|
import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
|
|
24
26
|
import type { ModelsAction } from "./models.js";
|
|
@@ -30,6 +32,7 @@ export declare const PROBE_APIS: readonly Api[];
|
|
|
30
32
|
export declare function modelsUrl(provider: ProviderData): string;
|
|
31
33
|
export declare function discoverModels(provider: ProviderData, apiKey: string | undefined, options?: {
|
|
32
34
|
timeoutMs?: number;
|
|
35
|
+
codexClientVersion?: string;
|
|
33
36
|
}): Promise<Model[]>;
|
|
34
37
|
export declare function probeOrder(provider: ProviderData): Api[];
|
|
35
38
|
export interface ProbeOptions {
|
|
@@ -18,13 +18,16 @@
|
|
|
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
|
|
21
|
+
* `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
|
|
22
|
+
* `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
|
|
23
|
+
* (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
|
|
22
24
|
*/
|
|
23
25
|
import { authHeaders, mergeHeaders } from "../../ai/http.js";
|
|
24
26
|
import { listChatGptModels } from "../../auth/chatgpt/backend-client.js";
|
|
25
|
-
import { CHATGPT_PROVIDER_ID } from "../../auth/chatgpt/presets.js";
|
|
27
|
+
import { CHATGPT_PROVIDER_ID, codexClientVersion } from "../../auth/chatgpt/presets.js";
|
|
26
28
|
import { liveToken } from "../../auth/oauth/live.js";
|
|
27
29
|
import { discoverLocalModels, materializeModel } from "../../ai/providers/registry.js";
|
|
30
|
+
import { thinkingMapOf, writeDiscoveredCache, } from "../../ai/providers/discovered-cache.js";
|
|
28
31
|
import { withCustomDefaults } from "../../ai/providers/catalog.js";
|
|
29
32
|
import { describeModelsDev, loadModelsDevIndex } from "../../ai/providers/models-dev-cache.js";
|
|
30
33
|
import { matchLabel, modelsDevFields } from "../../ai/providers/models-dev.js";
|
|
@@ -63,11 +66,20 @@ export async function discoverModels(provider, apiKey, options = {}) {
|
|
|
63
66
|
accessToken: apiKey,
|
|
64
67
|
accountId: live?.accountId,
|
|
65
68
|
originator: channel?.headers?.["originator"],
|
|
69
|
+
clientVersion: options.codexClientVersion,
|
|
66
70
|
});
|
|
67
71
|
return models.map((m) => {
|
|
68
72
|
const model = withCustomDefaults({ id: m.id }, provider.id, provider.api);
|
|
69
73
|
if (m.name !== undefined)
|
|
70
74
|
model.name = m.name;
|
|
75
|
+
if (m.contextWindow !== undefined)
|
|
76
|
+
model.contextWindow = m.contextWindow;
|
|
77
|
+
if (m.input !== undefined)
|
|
78
|
+
model.input = m.input;
|
|
79
|
+
if (m.reasoningLevels?.some((x) => x !== "none") === true) {
|
|
80
|
+
model.reasoning = true;
|
|
81
|
+
model.thinkingLevelMap = thinkingMapOf(m.reasoningLevels);
|
|
82
|
+
}
|
|
71
83
|
return model;
|
|
72
84
|
});
|
|
73
85
|
}
|
|
@@ -189,7 +201,9 @@ async function run(ctx) {
|
|
|
189
201
|
}
|
|
190
202
|
let found;
|
|
191
203
|
try {
|
|
192
|
-
found = await discoverModels(provider, key.apiKey
|
|
204
|
+
found = await discoverModels(provider, key.apiKey, {
|
|
205
|
+
codexClientVersion: codexClientVersion(ctx.level.merged.config.auth?.chatgpt, io.env),
|
|
206
|
+
});
|
|
193
207
|
}
|
|
194
208
|
catch (error) {
|
|
195
209
|
io.stderr(m.listFailed(provider.id, error.message));
|
|
@@ -197,6 +211,8 @@ async function run(ctx) {
|
|
|
197
211
|
}
|
|
198
212
|
const configured = new Map(provider.models.map((m) => [m.id, m]));
|
|
199
213
|
io.stdout(m.found(provider.id, found.length, modelsUrl(provider)));
|
|
214
|
+
if (provider.id === CHATGPT_PROVIDER_ID)
|
|
215
|
+
cacheChatGpt(ctx, found, key.apiKey);
|
|
200
216
|
// models.dev 只读本地(快照 ⊕ `ama models refresh` 的覆盖),不联网。
|
|
201
217
|
const mdIndex = loadModelsDevIndex(ctx.level.dataDir);
|
|
202
218
|
io.stdout(`${describeModelsDev(ctx.level.dataDir)}\n`);
|
|
@@ -252,6 +268,38 @@ async function run(ctx) {
|
|
|
252
268
|
}
|
|
253
269
|
return ExitCode.Ok;
|
|
254
270
|
}
|
|
271
|
+
/** 发现得到的模型 → 缓存条目(后端给的元数据一并写入;推理强度由映射表还原)。 */
|
|
272
|
+
function cacheEntry(m) {
|
|
273
|
+
const entry = { id: m.id };
|
|
274
|
+
if (m.name !== m.id)
|
|
275
|
+
entry.name = m.name;
|
|
276
|
+
if (m.contextWindow !== undefined)
|
|
277
|
+
entry.contextWindow = m.contextWindow;
|
|
278
|
+
if (m.input.length !== 1 || m.input[0] !== "text")
|
|
279
|
+
entry.input = [...m.input];
|
|
280
|
+
if (m.reasoning && m.thinkingLevelMap !== undefined)
|
|
281
|
+
entry.reasoningLevels = Object.entries(m.thinkingLevelMap).flatMap(([level, value]) => typeof value !== "string" ? [] : level === "off" ? ["none"] : [level]);
|
|
282
|
+
return entry;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* chatgpt 的发现结果写进发现缓存(选择器与注册表读它;0 个也写,免得残留另一 flavor 的缓存);写失败不影响
|
|
286
|
+
* 列表输出。codex 返回 0 个时提示 codexClientVersion 可能过旧。
|
|
287
|
+
*/
|
|
288
|
+
function cacheChatGpt(ctx, found, apiKey) {
|
|
289
|
+
const flavor = liveToken(apiKey)?.flavor;
|
|
290
|
+
try {
|
|
291
|
+
const models = found.map(cacheEntry);
|
|
292
|
+
const path = writeDiscoveredCache(ctx.level.dataDir, CHATGPT_PROVIDER_ID, { models, flavor });
|
|
293
|
+
ctx.io.stdout(msg().subcommands.discover.cached(path));
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
ctx.io.stderr(`ama: ${error.message}\n`);
|
|
297
|
+
}
|
|
298
|
+
if (found.length === 0 && flavor === "codex") {
|
|
299
|
+
const version = codexClientVersion(ctx.level.merged.config.auth?.chatgpt, ctx.io.env);
|
|
300
|
+
ctx.io.stderr(`${msg().auth.login.codexNoModels(version)}\n`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
255
303
|
export const DISCOVER_ACTION = {
|
|
256
304
|
usage: "ama models discover <provider> [--probe] [--write] [--limit <n>] [--concurrency <n>] [--probe-timeout <ms>]",
|
|
257
305
|
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
|
-
|
|
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({
|
|
@@ -180,6 +180,7 @@ function w6Sections() {
|
|
|
180
180
|
clientId: str(),
|
|
181
181
|
issuer: str(),
|
|
182
182
|
originator: str(),
|
|
183
|
+
codexClientVersion: str(),
|
|
183
184
|
redirectPorts: {
|
|
184
185
|
type: "array",
|
|
185
186
|
items: { type: "integer", minimum: 0, maximum: 65535 },
|
package/dist/config/key-docs.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* DISPLAY_DEFAULTS 相同,DISPLAY_DEFAULTS 本身通过 `validateConfig`。
|
|
11
11
|
*/
|
|
12
12
|
import { DEFAULT_IDLE_TIMEOUT_MS } from "../ai/http.js";
|
|
13
|
+
import { DEFAULT_CODEX_CLIENT_VERSION } from "../auth/chatgpt/presets.js";
|
|
13
14
|
import { messagesFor, msg } from "../i18n/index.js";
|
|
14
15
|
import { DEFAULT_INLINE_BUDGET } from "../codemode/declarations.js";
|
|
15
16
|
import { DEFAULT_CONFIG, mergeConfig } from "./merge.js";
|
|
@@ -33,7 +34,12 @@ const IMPLICIT_DEFAULTS = {
|
|
|
33
34
|
subagents: "read",
|
|
34
35
|
},
|
|
35
36
|
auth: {
|
|
36
|
-
chatgpt: {
|
|
37
|
+
chatgpt: {
|
|
38
|
+
flavor: "siwc",
|
|
39
|
+
issuer: "https://auth.openai.com",
|
|
40
|
+
originator: "codex_cli_rs",
|
|
41
|
+
codexClientVersion: DEFAULT_CODEX_CLIENT_VERSION,
|
|
42
|
+
},
|
|
37
43
|
},
|
|
38
44
|
checkpoints: { ...DEFAULT_CHECKPOINTS_CONFIG },
|
|
39
45
|
sandbox: { enabled: "auto", bash: "off", network: "deny", writable: [] },
|
package/dist/config/schema-w5.js
CHANGED
|
@@ -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))
|
|
@@ -9,7 +9,7 @@ export declare const W6_CONFIG_KEYS: readonly ["memory", "auth"];
|
|
|
9
9
|
/** 已有 `ui` 段新增的键。 */
|
|
10
10
|
export declare const W6_UI_KEYS: readonly ["language", "replyLanguage", "agentBar"];
|
|
11
11
|
export declare const MEMORY_KEYS: readonly ["enabled", "scopes", "indexMaxBytes", "fileMaxBytes", "maxFiles", "subagents"];
|
|
12
|
-
export declare const CHATGPT_AUTH_KEYS: readonly ["flavor", "clientId", "issuer", "originator", "redirectPorts"];
|
|
12
|
+
export declare const CHATGPT_AUTH_KEYS: readonly ["flavor", "clientId", "issuer", "originator", "codexClientVersion", "redirectPorts"];
|
|
13
13
|
export declare function checkUiW6(c: Checker, ui: Obj, path: string): void;
|
|
14
14
|
/** 第六波顶层段的校验(validateConfig 末尾调用)。 */
|
|
15
15
|
export declare function validateConfigW6(c: Checker, config: Obj): void;
|
package/dist/config/schema-w6.js
CHANGED
|
@@ -24,6 +24,7 @@ export const CHATGPT_AUTH_KEYS = [
|
|
|
24
24
|
"clientId",
|
|
25
25
|
"issuer",
|
|
26
26
|
"originator",
|
|
27
|
+
"codexClientVersion",
|
|
27
28
|
"redirectPorts",
|
|
28
29
|
];
|
|
29
30
|
export function checkUiW6(c, ui, path) {
|
|
@@ -63,7 +64,7 @@ export function validateConfigW6(c, config) {
|
|
|
63
64
|
return;
|
|
64
65
|
c.keys(chatgpt, cp, CHATGPT_AUTH_KEYS);
|
|
65
66
|
c.oneOf(chatgpt, "flavor", cp, CHATGPT_FLAVORS);
|
|
66
|
-
for (const key of ["clientId", "issuer", "originator"])
|
|
67
|
+
for (const key of ["clientId", "issuer", "originator", "codexClientVersion"])
|
|
67
68
|
c.string(chatgpt, key, cp);
|
|
68
69
|
checkPorts(c, chatgpt, "redirectPorts", cp);
|
|
69
70
|
});
|
|
@@ -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 {
|
package/dist/config/types-w5.js
CHANGED
|
@@ -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+$/;
|
|
@@ -53,6 +53,11 @@ export interface ChatGptAuthConfig {
|
|
|
53
53
|
issuer?: string;
|
|
54
54
|
/** codex flavor 的 `originator` 请求头,缺省 codex_cli_rs。 */
|
|
55
55
|
originator?: string;
|
|
56
|
+
/**
|
|
57
|
+
* codex flavor 模型列表的 `client_version`(Codex CLI 版本号,后端按它过滤模型),缺省见
|
|
58
|
+
* `DEFAULT_CODEX_CLIENT_VERSION`;`AMA_CHATGPT_CODEX_CLIENT_VERSION` 优先。
|
|
59
|
+
*/
|
|
60
|
+
codexClientVersion?: string;
|
|
56
61
|
/** 本地回调端口(依次尝试;0 = 任意空闲端口)。 */
|
|
57
62
|
redirectPorts?: number[];
|
|
58
63
|
}
|