@armadra/agent 0.6.1 → 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.
Files changed (40) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/CHANGELOG.zh-CN.md +12 -0
  3. package/dist/agent/session-settings.d.ts +4 -1
  4. package/dist/agent/session-settings.js +7 -2
  5. package/dist/ai/apis/chatgpt-backend.d.ts +7 -2
  6. package/dist/ai/apis/chatgpt-backend.js +12 -4
  7. package/dist/ai/providers/discovered-cache.d.ts +22 -11
  8. package/dist/ai/providers/discovered-cache.js +56 -12
  9. package/dist/ai/providers/registry.js +7 -6
  10. package/dist/ai/types.d.ts +5 -0
  11. package/dist/auth/chatgpt/backend-client.d.ts +11 -2
  12. package/dist/auth/chatgpt/backend-client.js +38 -6
  13. package/dist/auth/chatgpt/cli.d.ts +2 -1
  14. package/dist/auth/chatgpt/cli.js +14 -7
  15. package/dist/auth/chatgpt/follow.d.ts +16 -0
  16. package/dist/auth/chatgpt/follow.js +51 -0
  17. package/dist/auth/chatgpt/presets.d.ts +8 -0
  18. package/dist/auth/chatgpt/presets.js +12 -0
  19. package/dist/auth/testing/fake-oauth.d.ts +4 -1
  20. package/dist/auth/testing/fake-oauth.js +24 -1
  21. package/dist/bundle/ama.cjs +341 -180
  22. package/dist/cli/compose-providers.d.ts +2 -1
  23. package/dist/cli/compose-providers.js +6 -3
  24. package/dist/cli/startup-steps.js +4 -1
  25. package/dist/cli/subcommands/models-discover.d.ts +4 -2
  26. package/dist/cli/subcommands/models-discover.js +40 -8
  27. package/dist/config/json-schema.js +1 -0
  28. package/dist/config/key-docs.js +7 -1
  29. package/dist/config/schema-w6.d.ts +1 -1
  30. package/dist/config/schema-w6.js +2 -1
  31. package/dist/config/types-w6.d.ts +5 -0
  32. package/dist/i18n/catalog.d.ts +7 -0
  33. package/dist/i18n/messages/auth.d.ts +12 -0
  34. package/dist/i18n/messages/auth.js +20 -0
  35. package/dist/i18n/messages/config-keys.d.ts +2 -0
  36. package/dist/i18n/messages/config-keys.js +2 -0
  37. package/dist/i18n/messages/config.d.ts +2 -0
  38. package/docs/en/providers.md +8 -4
  39. package/docs/providers.md +25 -10
  40. package/package.json +1 -1
@@ -12,7 +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)。
15
+ * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts);
16
+ * chatgpt 的缓存 flavor 与当前登录不符时视为过期、不并入。
16
17
  */
17
18
  import type { ApiRegistry } from "../ai/apis/api.js";
18
19
  import { ProviderRegistry } from "../ai/providers/registry.js";
@@ -12,7 +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)。
15
+ * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts);
16
+ * chatgpt 的缓存 flavor 与当前登录不符时视为过期、不并入。
16
17
  */
17
18
  import { loadModelsDevIndex } from "../ai/providers/models-dev-cache.js";
18
19
  import { ProviderRegistry, discoverLocalModels } from "../ai/providers/registry.js";
@@ -108,8 +109,10 @@ export async function buildProviderRegistry(input, options = {}) {
108
109
  registry = make({ provider, apiKey: cli.apiKey });
109
110
  }
110
111
  // 发现缓存(`ama auth login chatgpt` / `ama models discover chatgpt`)并入模型表为空的供应商
111
- if (dataDir !== undefined)
112
- mergeDiscoveredModels(registry, dataDir, lazyIndex(() => loadModelsDevIndex(dataDir)));
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
+ }
113
116
  const probe = options.probeLocal ?? env[NO_LOCAL_PROBE_ENV] !== "1";
114
117
  if (probe && config.defaultModel === undefined && !anyKeyConfigured(registry)) {
115
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
- ...(change.channel !== undefined ? { channel: change.channel } : {}),
135
+ ...(channel !== undefined ? { channel } : {}),
133
136
  }));
134
137
  if (found.ok)
135
138
  choice = { model: found.model, provider: found.provider };
@@ -18,8 +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=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
22
- * 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
21
+ * `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
22
+ * `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
23
+ * (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
23
24
  */
24
25
  import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
25
26
  import type { ModelsAction } from "./models.js";
@@ -31,6 +32,7 @@ export declare const PROBE_APIS: readonly Api[];
31
32
  export declare function modelsUrl(provider: ProviderData): string;
32
33
  export declare function discoverModels(provider: ProviderData, apiKey: string | undefined, options?: {
33
34
  timeoutMs?: number;
35
+ codexClientVersion?: string;
34
36
  }): Promise<Model[]>;
35
37
  export declare function probeOrder(provider: ProviderData): Api[];
36
38
  export interface ProbeOptions {
@@ -18,15 +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=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
22
- * 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
21
+ * `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
22
+ * `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
23
+ * (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
23
24
  */
24
25
  import { authHeaders, mergeHeaders } from "../../ai/http.js";
25
26
  import { listChatGptModels } from "../../auth/chatgpt/backend-client.js";
26
- import { CHATGPT_PROVIDER_ID } from "../../auth/chatgpt/presets.js";
27
+ import { CHATGPT_PROVIDER_ID, codexClientVersion } from "../../auth/chatgpt/presets.js";
27
28
  import { liveToken } from "../../auth/oauth/live.js";
28
29
  import { discoverLocalModels, materializeModel } from "../../ai/providers/registry.js";
29
- import { writeDiscoveredCache } from "../../ai/providers/discovered-cache.js";
30
+ import { thinkingMapOf, writeDiscoveredCache, } from "../../ai/providers/discovered-cache.js";
30
31
  import { withCustomDefaults } from "../../ai/providers/catalog.js";
31
32
  import { describeModelsDev, loadModelsDevIndex } from "../../ai/providers/models-dev-cache.js";
32
33
  import { matchLabel, modelsDevFields } from "../../ai/providers/models-dev.js";
@@ -65,11 +66,20 @@ export async function discoverModels(provider, apiKey, options = {}) {
65
66
  accessToken: apiKey,
66
67
  accountId: live?.accountId,
67
68
  originator: channel?.headers?.["originator"],
69
+ clientVersion: options.codexClientVersion,
68
70
  });
69
71
  return models.map((m) => {
70
72
  const model = withCustomDefaults({ id: m.id }, provider.id, provider.api);
71
73
  if (m.name !== undefined)
72
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
+ }
73
83
  return model;
74
84
  });
75
85
  }
@@ -191,7 +201,9 @@ async function run(ctx) {
191
201
  }
192
202
  let found;
193
203
  try {
194
- 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
+ });
195
207
  }
196
208
  catch (error) {
197
209
  io.stderr(m.listFailed(provider.id, error.message));
@@ -256,17 +268,37 @@ async function run(ctx) {
256
268
  }
257
269
  return ExitCode.Ok;
258
270
  }
259
- /** chatgpt 的发现结果写进发现缓存(选择器与注册表读它);写失败不影响列表输出。 */
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
+ */
260
288
  function cacheChatGpt(ctx, found, apiKey) {
289
+ const flavor = liveToken(apiKey)?.flavor;
261
290
  try {
262
- const flavor = liveToken(apiKey)?.flavor;
263
- const models = found.map((m) => ({ id: m.id, ...(m.name !== m.id ? { name: m.name } : {}) }));
291
+ const models = found.map(cacheEntry);
264
292
  const path = writeDiscoveredCache(ctx.level.dataDir, CHATGPT_PROVIDER_ID, { models, flavor });
265
293
  ctx.io.stdout(msg().subcommands.discover.cached(path));
266
294
  }
267
295
  catch (error) {
268
296
  ctx.io.stderr(`ama: ${error.message}\n`);
269
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
+ }
270
302
  }
271
303
  export const DISCOVER_ACTION = {
272
304
  usage: "ama models discover <provider> [--probe] [--write] [--limit <n>] [--concurrency <n>] [--probe-timeout <ms>]",
@@ -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 },
@@ -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: { flavor: "siwc", issuer: "https://auth.openai.com", originator: "codex_cli_rs" },
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: [] },
@@ -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;
@@ -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
  });
@@ -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
  }
@@ -1344,6 +1344,7 @@ declare const en: {
1344
1344
  "auth.chatgpt.clientId": string;
1345
1345
  "auth.chatgpt.issuer": string;
1346
1346
  "auth.chatgpt.originator": string;
1347
+ "auth.chatgpt.codexClientVersion": string;
1347
1348
  "auth.chatgpt.redirectPorts": string;
1348
1349
  skills: string;
1349
1350
  "skills.dirs": string;
@@ -1974,6 +1975,12 @@ declare const en: {
1974
1975
  freePlanHint: (plan: string) => string;
1975
1976
  modelHint: string;
1976
1977
  modelsCached: (count: number) => string;
1978
+ siwcEligibilityHint: string;
1979
+ codexNoModels: (version: string) => string;
1980
+ };
1981
+ request: {
1982
+ notEligible: string;
1983
+ flavorMismatch: (current: string, ref: string, pinned: string) => string;
1977
1984
  };
1978
1985
  errors: {
1979
1986
  portsBusy: (ports: string, codex: boolean) => string;
@@ -46,6 +46,12 @@ export declare const en: {
46
46
  freePlanHint: (plan: string) => string;
47
47
  modelHint: string;
48
48
  modelsCached: (count: number) => string;
49
+ siwcEligibilityHint: string;
50
+ codexNoModels: (version: string) => string;
51
+ };
52
+ request: {
53
+ notEligible: string;
54
+ flavorMismatch: (current: string, ref: string, pinned: string) => string;
49
55
  };
50
56
  errors: {
51
57
  portsBusy: (ports: string, codex: boolean) => string;
@@ -137,6 +143,12 @@ export declare const zh: {
137
143
  freePlanHint: (plan: string) => string;
138
144
  modelHint: string;
139
145
  modelsCached: (count: number) => string;
146
+ siwcEligibilityHint: string;
147
+ codexNoModels: (version: string) => string;
148
+ };
149
+ request: {
150
+ notEligible: string;
151
+ flavorMismatch: (current: string, ref: string, pinned: string) => string;
140
152
  };
141
153
  errors: {
142
154
  portsBusy: (ports: string, codex: boolean) => string;
@@ -47,6 +47,16 @@ export const en = {
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
49
  modelsCached: (count) => `Your account can use ${count} model(s); pick one with /model or --model chatgpt/<model>.`,
50
+ siwcEligibilityHint: "Whether this account can share its plan usage with ama is only confirmed on the first request.",
51
+ codexNoModels: (version) => `The codex backend returned no models. codexClientVersion (${version}) may be too old: set a newer Codex CLI version with ama config set auth.chatgpt.codexClientVersion <version>, then run ama models discover chatgpt.`,
52
+ },
53
+ request: {
54
+ notEligible: "this ChatGPT account cannot share plan usage with ama (403 subscription_sharing_user_not_eligible). Possible causes:\n" +
55
+ " 1. Plan: plan usage is shared with Plus / Pro only\n" +
56
+ " 2. Workspace account: Team / Enterprise / Edu workspaces may not offer it\n" +
57
+ " 3. Region restriction, or not yet rolled out during the preview (most likely when a Pro account still gets this error)\n" +
58
+ "You can sign in the other way instead: ama auth login chatgpt --flavor codex",
59
+ flavorMismatch: (current, ref, pinned) => `signed in with ${current}, but ${ref} pins the @${pinned} channel. Drop @${pinned} so the channel follows the sign-in, or run ama auth login chatgpt --flavor ${pinned}`,
50
60
  },
51
61
  errors: {
52
62
  portsBusy: (ports, codex) => `callback port ${ports} is in use: finish the other sign-in first, or use --paste${codex ? " / --device" : ""}`,
@@ -145,6 +155,16 @@ export const zh = {
145
155
  freePlanHint: (plan) => `注意:「${plan}」计划可能不含给 ama 用的订阅额度。`,
146
156
  modelHint: "用 --model chatgpt/<模型> 使用;账户可用的模型用 ama models discover chatgpt 查看。",
147
157
  modelsCached: (count) => `账户可用 ${count} 个模型,用 /model 或 --model chatgpt/<模型> 选择。`,
158
+ siwcEligibilityHint: "这个账户能否把套餐额度共享给 ama,要到首次请求时才能确认。",
159
+ codexNoModels: (version) => `codex 后端没有返回模型,可能是 codexClientVersion(${version})过旧:用 ama config set auth.chatgpt.codexClientVersion <版本> 设为较新的 Codex CLI 版本,再运行 ama models discover chatgpt。`,
160
+ },
161
+ request: {
162
+ notEligible: "这个 ChatGPT 账户不能把套餐额度共享给 ama(403 subscription_sharing_user_not_eligible)。可能的原因:\n" +
163
+ " 1. 账户套餐:额度共享只对 Plus / Pro 开放\n" +
164
+ " 2. 工作空间账户:Team / Enterprise / Edu 工作空间可能未开放\n" +
165
+ " 3. 地区受限,或预览期尚未开放(Pro 账户仍报此错时最可能是这一条)\n" +
166
+ "可以改用另一种登录方式:ama auth login chatgpt --flavor codex",
167
+ flavorMismatch: (current, ref, pinned) => `当前以 ${current} 方式登录,但 ${ref} 显式指定了 @${pinned} 渠道。去掉 @${pinned} 让渠道跟随登录方式,或运行 ama auth login chatgpt --flavor ${pinned}`,
148
168
  },
149
169
  errors: {
150
170
  portsBusy: (ports, codex) => `回调端口 ${ports} 被占用:请先结束其它登录,或用 --paste${codex ? " / --device" : ""}`,
@@ -71,6 +71,7 @@ export declare const en: {
71
71
  "auth.chatgpt.clientId": string;
72
72
  "auth.chatgpt.issuer": string;
73
73
  "auth.chatgpt.originator": string;
74
+ "auth.chatgpt.codexClientVersion": string;
74
75
  "auth.chatgpt.redirectPorts": string;
75
76
  skills: string;
76
77
  "skills.dirs": string;
@@ -214,6 +215,7 @@ export declare const zh: {
214
215
  "auth.chatgpt.clientId": string;
215
216
  "auth.chatgpt.issuer": string;
216
217
  "auth.chatgpt.originator": string;
218
+ "auth.chatgpt.codexClientVersion": string;
217
219
  "auth.chatgpt.redirectPorts": string;
218
220
  skills: string;
219
221
  "skills.dirs": string;
@@ -118,6 +118,7 @@ const keysEn = {
118
118
  "auth.chatgpt.clientId": "OAuth client id",
119
119
  "auth.chatgpt.issuer": "Authorization server (override with AMA_CHATGPT_ISSUER for tests)",
120
120
  "auth.chatgpt.originator": "originator request header for the codex flavor",
121
+ "auth.chatgpt.codexClientVersion": "Codex CLI version sent as client_version when listing codex-flavor models (the backend hides models newer than it); AMA_CHATGPT_CODEX_CLIENT_VERSION overrides",
121
122
  "auth.chatgpt.redirectPorts": "Local callback ports, tried in order; 0 = any free port",
122
123
  skills: "Skills",
123
124
  "skills.dirs": "Extra skill directories; accumulated across levels",
@@ -236,6 +237,7 @@ const keysZh = {
236
237
  "auth.chatgpt.clientId": "OAuth 客户端 id",
237
238
  "auth.chatgpt.issuer": "授权服务器(测试用 AMA_CHATGPT_ISSUER 覆盖)",
238
239
  "auth.chatgpt.originator": "codex flavor 的 originator 请求头",
240
+ "auth.chatgpt.codexClientVersion": "codex flavor 列模型时作为 client_version 发送的 Codex CLI 版本号(后端不给比它新的模型);AMA_CHATGPT_CODEX_CLIENT_VERSION 优先",
239
241
  "auth.chatgpt.redirectPorts": "本地回调端口,依次尝试;0 = 任意空闲端口",
240
242
  skills: "Skill",
241
243
  "skills.dirs": "追加的 Skill 目录;各层累加",
@@ -71,6 +71,7 @@ export declare const en: {
71
71
  "auth.chatgpt.clientId": string;
72
72
  "auth.chatgpt.issuer": string;
73
73
  "auth.chatgpt.originator": string;
74
+ "auth.chatgpt.codexClientVersion": string;
74
75
  "auth.chatgpt.redirectPorts": string;
75
76
  skills: string;
76
77
  "skills.dirs": string;
@@ -326,6 +327,7 @@ export declare const zh: {
326
327
  "auth.chatgpt.clientId": string;
327
328
  "auth.chatgpt.issuer": string;
328
329
  "auth.chatgpt.originator": string;
330
+ "auth.chatgpt.codexClientVersion": string;
329
331
  "auth.chatgpt.redirectPorts": string;
330
332
  skills: string;
331
333
  "skills.dirs": string;
@@ -170,9 +170,11 @@ ama auth logout chatgpt # siwc revokes the refresh token first, t
170
170
  | Quota | only known when exceeded (429); set a weekly cap for ama under ChatGPT → Settings → Usage → App limits | response headers, `codex.rate_limits` events, `ama auth status` queries `wham/usage` |
171
171
  | Logout | calls `revocation_endpoint`, then deletes locally | deletes locally only |
172
172
 
173
- **Model list**: `chatgpt` has no built-in model table (`chatgpt/<slug>` accepts any slug). After a successful `ama auth login chatgpt`, ama calls the model list endpoint once (siwc `GET /v1/models`, codex `GET /models`; read-only, no usage consumed; failures are silent and the output suggests `ama models discover chatgpt` instead) and caches the slugs and display names available to the account, with the flavor and a timestamp, in `<dataDir>/models/discovered/chatgpt.json`; `ama models discover chatgpt` rewrites the cache and `ama auth logout chatgpt` deletes it. When the registry is assembled the cache is merged into providers whose model table is empty, with metadata filled from the models.dev snapshot, so the `/model` picker and `ama models list` show the models; slugs missing from the cache still work with `--model chatgpt/<slug>`.
173
+ **Model list**: `chatgpt` has no built-in model table (`chatgpt/<slug>` accepts any slug). After a successful `ama auth login chatgpt`, ama deletes the old cache and calls the model list endpoint once (siwc `GET /v1/models`, codex `GET /models?client_version=…`; read-only, no usage consumed; failures are silent and the output suggests `ama models discover chatgpt` instead) and caches the slugs and display names available to the account, with the flavor and a timestamp, in `<dataDir>/models/discovered/chatgpt.json` — an empty list is written as well when no model comes back, so no cache from the other sign-in method is left behind; `ama models discover chatgpt` rewrites the cache and `ama auth logout chatgpt` deletes it. When the registry is assembled the cache is merged into providers whose model table is empty, with metadata filled from the models.dev snapshot (the context window, input modalities and reasoning efforts reported by the codex backend are cached too and used when models.dev has nothing), so the `/model` picker and `ama models list` show the models; a cache whose flavor differs from the current sign-in counts as stale and is not merged (the picker suggests discovering again). Slugs missing from the cache still work with `--model chatgpt/<slug>`.
174
174
 
175
- The default channel of `chatgpt` is chosen at assembly time from the flavor of the auth.json entry; `provider/model@siwc|codex` can name it explicitly but must match the signed-in flavor (otherwise `chatgpt_flavor_mismatch`).
175
+ **codex `client_version`**: the codex backend filters models by `client_version` (each model has a minimum client version; omitting the parameter is a 400), and ama sends a Codex CLI version (default `0.160.0`), not its own version. If codex returns no models at login or discover time, that version is most likely too old: set a newer Codex CLI version with `ama config set auth.chatgpt.codexClientVersion <version>` (user level) or the environment variable `AMA_CHATGPT_CODEX_CLIENT_VERSION` (takes precedence), then run `ama models discover chatgpt`. Inference requests carry no version.
176
+
177
+ **The channel follows the sign-in method**: the default channel of `chatgpt` is chosen at assembly time from the flavor of the auth.json entry; on top of that every request picks the channel from the flavor of the token it uses — a model reference without `@channel` automatically uses the current sign-in method's channel (endpoint, channel headers such as `originator`, and the request-body allowlist all switch), so after `ama auth logout` and signing in the other way, a running session's next request goes to the new channel without a restart; when a session is resumed, the channel recorded in it does not count as explicit either. Only an explicit `provider/model@siwc|codex` that differs from the sign-in method reports `chatgpt_flavor_mismatch` (suggesting to drop `@channel` or switch the sign-in method).
176
178
 
177
179
  **Credentials**: a `{ "type": "oauth", … }` entry in auth.json (file mode 0600); `ama auth list` only shows `oauth · <flavor> · <plan>`. The access token is refreshed automatically when less than 5 minutes remain or a request returns 401; several ama processes (several nodes on the canvas) share one auth.json, and refreshes are serialized through `auth.json.lock` (re-read after taking the lock; if another process already refreshed, its token is used), so refresh-token rotation never knocks another process out. When refreshing fails permanently the entry is marked `needsLogin` (tokens are not deleted), requests report `auth_expired`, and you sign in again with `ama auth login chatgpt` as prompted. Raw tokens, codes and id_tokens never reach logs, sessions, events or errors. Logins of other applications (Codex CLI and others) are never read or imported.
178
180
 
@@ -184,12 +186,14 @@ The default channel of `chatgpt` is chosen at assembly time from the flavor of t
184
186
  | ---------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
185
187
  | `quota_exceeded` | siwc 429 `subscription_sharing_usage_limit_exceeded`; codex 429 `usage_limit_reached` / `usage_not_included` | no retry; carries the reset time and emits `quota_update` |
186
188
  | `auth_expired` | a 401 still failing after one refresh, a permanently failed refresh, an entry marked `needsLogin` | no retry; run `ama auth login chatgpt` again |
187
- | `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | no retry, no re-login |
189
+ | `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | no retry, no re-login; see troubleshooting below |
188
190
  | (as is) | 503 and similar | the session layer's existing backoff retries |
189
191
 
192
+ **Troubleshooting `not_eligible`**: the account cannot share its plan usage with ama. Possible causes: the plan (sharing is offered to Plus / Pro only); a workspace account (Team / Enterprise / Edu may not offer it); a region restriction or a preview that has not rolled out yet — **the most likely cause when a Pro account still gets this error**. You can sign in with `ama auth login chatgpt --flavor codex` instead. A successful siwc login only means authorization passed; whether plan usage can be shared is only confirmed on the first request.
193
+
190
194
  **Usage**: subscription requests record `usage.cost = 0` with `billing: "subscription"`; `/session` lists "subscription usage" separately (requests, tokens, cache hit rate, no USD conversion) together with the latest quota; the `quota_update` event is forwarded as is over RPC and has the same name among host events.
191
195
 
192
- **Overrides** (for tests or a future own client): `auth.chatgpt.clientId` / `issuer` / `originator` / `redirectPorts` (user level and profile only), environment variables `AMA_CHATGPT_CLIENT_ID`, `AMA_CHATGPT_ISSUER`, `AMA_CHATGPT_BASE_URL` (changes the address of the current flavor's channel).
196
+ **Overrides** (for tests or a future own client): `auth.chatgpt.clientId` / `issuer` / `originator` / `codexClientVersion` / `redirectPorts` (user level and profile only), environment variables `AMA_CHATGPT_CLIENT_ID`, `AMA_CHATGPT_ISSUER`, `AMA_CHATGPT_BASE_URL` (changes the address of the current flavor's channel), `AMA_CHATGPT_CODEX_CLIENT_VERSION`.
193
197
 
194
198
  **Embedding hosts**: with a profile ama never starts an interactive login; when `chatgpt` is used and the login has expired the request reports `auth_expired`, and the host guides the user to run `ama auth login chatgpt --paste` in a terminal. Hosts never read, store or forward tokens; they only consume `quota_update` and `auth_expired` / `quota_exceeded`.
195
199
 
package/docs/providers.md CHANGED
@@ -185,14 +185,25 @@ ama auth logout chatgpt # siwc 先撤销 refresh token 再删本
185
185
  | 配额 | 只在超限(429)时可知;在 ChatGPT → 设置 → Usage → App limits 给 ama 设周上限 | 响应头、`codex.rate_limits` 事件、`ama auth status` 查 `wham/usage` |
186
186
  | 登出 | 调 `revocation_endpoint` 撤销,再删本地 | 只删本地 |
187
187
 
188
- **模型列表**:`chatgpt` 没有内置模型表(`chatgpt/<slug>` 任意接受)。`ama auth login chatgpt` 成功后自动调一次模型
189
- 列表接口(siwc `GET /v1/models`、codex `GET /models`,只读、不消耗额度;失败静默,改提示 `ama models discover chatgpt`),
190
- 把账户可用的 slug 与显示名连同 flavor、时间戳缓存到 `<dataDir>/models/discovered/chatgpt.json`;`ama models discover
188
+ **模型列表**:`chatgpt` 没有内置模型表(`chatgpt/<slug>` 任意接受)。`ama auth login chatgpt` 成功后先删旧缓存,再调一次
189
+ 模型列表接口(siwc `GET /v1/models`、codex `GET /models?client_version=…`,只读、不消耗额度;失败静默,改提示
190
+ `ama models discover chatgpt`),把账户可用的 slug 与显示名连同 flavor、时间戳缓存到
191
+ `<dataDir>/models/discovered/chatgpt.json`——返回 0 个也写空表,免得残留另一种登录方式的缓存;`ama models discover
191
192
  chatgpt` 也重写这份缓存,`ama auth logout chatgpt` 删掉它。组装注册表时缓存并入模型表为空的供应商,元数据用 models.dev
192
- 快照补全,`/model` 选择器、`ama models list` 照常列出;缓存里没有的 slug 仍可 `--model chatgpt/<slug>` 使用。
193
+ 快照补全(codex 后端另给的上下文窗口、输入模态、推理强度也存进缓存,models.dev 补不到时用它),`/model` 选择器、
194
+ `ama models list` 照常列出;缓存的 flavor 与当前登录不符时视为过期、不并入(选择器提示重新发现)。缓存里没有的 slug
195
+ 仍可 `--model chatgpt/<slug>` 使用。
193
196
 
194
- `chatgpt` 的缺省渠道在组装时按 auth.json 条目的 flavor 决定;`provider/model@siwc|codex` 可显式指定,但必须与登录的
195
- flavor 一致(否则报 `chatgpt_flavor_mismatch`)。
197
+ **codex 的 `client_version`**:codex 后端按 `client_version` 过滤模型(每个模型有最低客户端版本,不带参数报 400),
198
+ ama 发的是 Codex CLI 的版本号(缺省 `0.160.0`),而不是 ama 自己的版本。登录或 discover 时 codex 返回 0 个模型,多半是
199
+ 这个版本过旧:用 `ama config set auth.chatgpt.codexClientVersion <版本>`(用户级)或环境变量
200
+ `AMA_CHATGPT_CODEX_CLIENT_VERSION`(优先)设成较新的 Codex CLI 版本,再 `ama models discover chatgpt`。推理请求不带版本号。
201
+
202
+ **渠道跟随登录方式**:`chatgpt` 的缺省渠道在组装时按 auth.json 条目的 flavor 决定;此外每次请求按当次 token 所属的
203
+ flavor 选渠道——模型引用没写 `@渠道` 时自动用当前登录方式的渠道(端点、`originator` 等渠道头、请求体白名单一起换),
204
+ 所以运行中的会话在 `ama auth logout` 后换另一种方式登录,下一次请求就走新渠道,不用重启;恢复会话时,会话里记录的渠道
205
+ 也不算显式。只有显式写了 `provider/model@siwc|codex` 且与登录方式不符时才报 `chatgpt_flavor_mismatch`(提示去掉
206
+ `@渠道` 或换登录方式)。
196
207
 
197
208
  **凭据**:auth.json 的 `{ "type": "oauth", … }` 条目(文件 0600),`ama auth list` 只显示 `oauth · <flavor> · <计划>`。
198
209
  access token 剩余不到 5 分钟或请求返回 401 时自动刷新;多个 ama 进程(画布上的多个节点)共享一个 auth.json,刷新经
@@ -214,15 +225,19 @@ developer 消息(前缀依然稳定)。compat `toolsInNamespace: true` 时
214
225
  | ---------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------- |
215
226
  | `quota_exceeded` | siwc 429 `subscription_sharing_usage_limit_exceeded`;codex 429 `usage_limit_reached` / `usage_not_included` | 不重试;附重置时间并发 `quota_update` |
216
227
  | `auth_expired` | 401 刷新一次仍失败、刷新永久失败、条目 `needsLogin` | 不重试;重新 `ama auth login chatgpt` |
217
- | `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | 不重试、不重登 |
228
+ | `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | 不重试、不重登;排查见下 |
218
229
  | (原样) | 503 等 | 走会话层现有的退避重试 |
219
230
 
231
+ **`not_eligible` 排查**:账户不能把套餐额度共享给 ama。可能的原因:账户套餐(额度共享只对 Plus / Pro 开放);工作空间
232
+ 账户(Team / Enterprise / Edu 可能未开放);地区受限或预览期尚未开放——**Pro 账户仍报此错时最可能是这一条**。可以改用
233
+ `ama auth login chatgpt --flavor codex`。siwc 登录成功只说明授权通过,能否共享额度要到首次请求才能确认。
234
+
220
235
  **用量**:订阅请求 `usage.cost = 0` 并标 `billing: "subscription"`;`/session` 单列「订阅用量」(请求数、token、
221
236
  缓存命中率,不折算美元)与最近一次配额;事件 `quota_update`(RPC 原样转发,宿主事件同名)。
222
237
 
223
- **覆盖**(测试或将来换自有客户端用):`auth.chatgpt.clientId` / `issuer` / `originator` / `redirectPorts`(只认用户级
224
- 与 profile),环境变量 `AMA_CHATGPT_CLIENT_ID`、`AMA_CHATGPT_ISSUER`、`AMA_CHATGPT_BASE_URL`(改当前 flavor 渠道的
225
- 地址)。
238
+ **覆盖**(测试或将来换自有客户端用):`auth.chatgpt.clientId` / `issuer` / `originator` / `codexClientVersion` /
239
+ `redirectPorts`(只认用户级与 profile),环境变量 `AMA_CHATGPT_CLIENT_ID`、`AMA_CHATGPT_ISSUER`、`AMA_CHATGPT_BASE_URL`
240
+ (改当前 flavor 渠道的地址)、`AMA_CHATGPT_CODEX_CLIENT_VERSION`。
226
241
 
227
242
  **嵌入宿主**:有 profile 时 ama 不发起交互式登录;用到 `chatgpt` 而登录失效时请求报 `auth_expired`,由宿主引导用户
228
243
  在终端执行 `ama auth login chatgpt --paste`。宿主不读、不存、不转发 token,只消费 `quota_update` 与
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armadra/agent",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "A coding and coordination agent that runs standalone or embedded in Armadra",
5
5
  "type": "module",
6
6
  "keywords": [