@armadra/agent 0.6.2 → 0.6.3

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 (130) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/CHANGELOG.zh-CN.md +44 -0
  3. package/README.md +168 -575
  4. package/README.zh-CN.md +165 -596
  5. package/dist/agent/session.d.ts +6 -0
  6. package/dist/agent/session.js +8 -0
  7. package/dist/agent/subagent-background.d.ts +75 -0
  8. package/dist/agent/subagent-background.js +209 -0
  9. package/dist/agent/subagent-registry.d.ts +24 -5
  10. package/dist/agent/subagent-registry.js +64 -92
  11. package/dist/agent/types-w5.d.ts +11 -1
  12. package/dist/agent/types.d.ts +5 -0
  13. package/dist/agents/builtin.js +0 -1
  14. package/dist/agents/external.js +0 -1
  15. package/dist/agents/parse.js +4 -3
  16. package/dist/agents/result.d.ts +7 -1
  17. package/dist/agents/result.js +20 -1
  18. package/dist/agents/task-control.d.ts +13 -2
  19. package/dist/agents/task-record.d.ts +13 -1
  20. package/dist/agents/task-record.js +32 -0
  21. package/dist/agents/types.d.ts +5 -1
  22. package/dist/ai/providers/discovered-cache.d.ts +10 -4
  23. package/dist/ai/providers/discovered-cache.js +30 -14
  24. package/dist/auth/chatgpt/backend-client.d.ts +6 -5
  25. package/dist/auth/chatgpt/backend-client.js +8 -7
  26. package/dist/bundle/ama.cjs +1691 -500
  27. package/dist/cli/compose-agents.d.ts +2 -1
  28. package/dist/cli/compose-agents.js +11 -2
  29. package/dist/cli/subcommands/models-discover.d.ts +1 -0
  30. package/dist/cli/subcommands/models-discover.js +12 -2
  31. package/dist/config/json-schema.js +9 -2
  32. package/dist/config/key-docs.js +2 -2
  33. package/dist/config/merge.d.ts +1 -1
  34. package/dist/config/merge.js +20 -3
  35. package/dist/config/schema-w5.js +4 -2
  36. package/dist/config/schema.js +2 -0
  37. package/dist/config/settings-registry.js +4 -0
  38. package/dist/config/types-w5.d.ts +10 -0
  39. package/dist/config/types-w5.js +2 -0
  40. package/dist/config/types.d.ts +3 -1
  41. package/dist/git/info.d.ts +19 -0
  42. package/dist/git/info.js +64 -8
  43. package/dist/i18n/catalog.d.ts +45 -8
  44. package/dist/i18n/messages/agents.d.ts +56 -0
  45. package/dist/i18n/messages/agents.js +58 -2
  46. package/dist/i18n/messages/config-keys.d.ts +6 -0
  47. package/dist/i18n/messages/config-keys.js +16 -10
  48. package/dist/i18n/messages/config.d.ts +6 -0
  49. package/dist/i18n/messages/interactive-startup.d.ts +0 -16
  50. package/dist/i18n/messages/interactive-startup.js +0 -16
  51. package/dist/i18n/messages/interactive.d.ts +23 -16
  52. package/dist/i18n/messages/interactive.js +25 -0
  53. package/dist/i18n/messages/print.d.ts +4 -0
  54. package/dist/i18n/messages/print.js +4 -0
  55. package/dist/i18n/messages/report.js +4 -4
  56. package/dist/i18n/messages/settings.d.ts +6 -0
  57. package/dist/i18n/messages/settings.js +6 -0
  58. package/dist/i18n/messages/subcommands-config.d.ts +4 -0
  59. package/dist/i18n/messages/subcommands-config.js +4 -0
  60. package/dist/i18n/messages/subcommands.d.ts +4 -0
  61. package/dist/index.d.ts +1 -0
  62. package/dist/modes/commands-core.js +8 -4
  63. package/dist/modes/interactive/agent-bar.d.ts +3 -1
  64. package/dist/modes/interactive/agent-bar.js +12 -5
  65. package/dist/modes/interactive/agent-ui.d.ts +21 -3
  66. package/dist/modes/interactive/agent-ui.js +81 -12
  67. package/dist/modes/interactive/agent-view.d.ts +4 -0
  68. package/dist/modes/interactive/agent-view.js +14 -1
  69. package/dist/modes/interactive/approval-dock.d.ts +51 -0
  70. package/dist/modes/interactive/approval-dock.js +112 -0
  71. package/dist/modes/interactive/approval-ui.d.ts +43 -0
  72. package/dist/modes/interactive/approval-ui.js +64 -0
  73. package/dist/modes/interactive/commands.js +4 -1
  74. package/dist/modes/interactive/event-notices.d.ts +6 -1
  75. package/dist/modes/interactive/event-notices.js +7 -1
  76. package/dist/modes/interactive/interactive-mode.d.ts +4 -2
  77. package/dist/modes/interactive/interactive-mode.js +33 -38
  78. package/dist/modes/interactive/key-dispatch.d.ts +21 -4
  79. package/dist/modes/interactive/key-dispatch.js +60 -8
  80. package/dist/modes/interactive/line/line-mode.js +5 -0
  81. package/dist/modes/interactive/run-indicator.d.ts +15 -0
  82. package/dist/modes/interactive/run-indicator.js +37 -5
  83. package/dist/modes/interactive/session-events.js +5 -1
  84. package/dist/modes/interactive/startup-header.d.ts +29 -14
  85. package/dist/modes/interactive/startup-header.js +93 -59
  86. package/dist/modes/interactive/startup-logo.d.ts +83 -0
  87. package/dist/modes/interactive/startup-logo.js +183 -0
  88. package/dist/modes/interactive/status-area.d.ts +18 -0
  89. package/dist/modes/interactive/status-area.js +67 -1
  90. package/dist/modes/interactive/status-bar.d.ts +9 -1
  91. package/dist/modes/interactive/status-bar.js +43 -11
  92. package/dist/modes/interactive/status-line.d.ts +2 -0
  93. package/dist/modes/interactive/status-line.js +11 -6
  94. package/dist/modes/interactive/status-quota.d.ts +44 -0
  95. package/dist/modes/interactive/status-quota.js +135 -0
  96. package/dist/modes/interactive/subagent-view.d.ts +1 -0
  97. package/dist/modes/interactive/subagent-view.js +8 -0
  98. package/dist/modes/interactive/task-background.d.ts +31 -0
  99. package/dist/modes/interactive/task-background.js +68 -0
  100. package/dist/modes/interactive/tool-view.d.ts +7 -1
  101. package/dist/modes/interactive/tool-view.js +24 -1
  102. package/dist/modes/print/print-mode.d.ts +11 -0
  103. package/dist/modes/print/print-mode.js +36 -1
  104. package/dist/modes/rpc/commands.d.ts +2 -1
  105. package/dist/modes/rpc/commands.js +9 -1
  106. package/dist/rpc.d.ts +13 -0
  107. package/dist/rpc.js +3 -0
  108. package/dist/tools/task-ctl.d.ts +2 -0
  109. package/dist/tools/task-ctl.js +7 -2
  110. package/dist/tools/task.d.ts +11 -0
  111. package/dist/tools/task.js +20 -2
  112. package/dist/tui/components/editor.d.ts +2 -0
  113. package/dist/tui/components/editor.js +4 -0
  114. package/dist/tui/components/loader.d.ts +5 -1
  115. package/dist/tui/components/loader.js +18 -5
  116. package/dist/tui/keybindings.d.ts +8 -3
  117. package/dist/tui/keybindings.js +8 -3
  118. package/docs/agents.md +52 -28
  119. package/docs/en/host-api.md +5 -1
  120. package/docs/en/providers.md +1 -1
  121. package/docs/en/rpc.md +19 -7
  122. package/docs/en/sessions.md +3 -1
  123. package/docs/en/tui.md +84 -66
  124. package/docs/host-api.md +5 -1
  125. package/docs/providers.md +4 -2
  126. package/docs/rpc.md +19 -7
  127. package/docs/sessions.md +2 -1
  128. package/docs/tui-design.md +42 -29
  129. package/docs/tui.md +67 -49
  130. package/package.json +1 -1
@@ -80,6 +80,16 @@ export interface SubagentEndEvent {
80
80
  changed: boolean;
81
81
  };
82
82
  }
83
+ /**
84
+ * [W7-B1] 前台任务转后台(docs/agents-concurrency-plan.md §2.5):`user` = 人(TUI `Ctrl+B` 等),
85
+ * `timeout` = `subagents.autoBackgroundAfterMs` 到时,`host` = 宿主 / SDK(`session.backgroundTask`)。
86
+ */
87
+ export interface SubagentBackgroundEvent {
88
+ type: "subagent_background";
89
+ taskId: string;
90
+ parentToolCallId: string;
91
+ reason: "user" | "timeout" | "host";
92
+ }
83
93
  export interface PlanStep {
84
94
  /** `S1`、`S2` …;编号列表时按序号生成。 */
85
95
  id: string;
@@ -161,7 +171,7 @@ export type SessionEventW5 =
161
171
  /** 流式中 ≤ 2 Hz(`ui.animation: false` 时不发);数据从 `getStats().telemetry` 取。 */
162
172
  {
163
173
  type: "telemetry_tick";
164
- } | SubagentStartEvent | SubagentUpdateEvent | SubagentEndEvent | PlanProposedEvent | PlanResolvedEvent | TodoUpdatedEvent | LimitReachedEvent | ModelFallbackEvent | BackgroundJobEvent;
174
+ } | SubagentStartEvent | SubagentUpdateEvent | SubagentEndEvent | SubagentBackgroundEvent | PlanProposedEvent | PlanResolvedEvent | TodoUpdatedEvent | LimitReachedEvent | ModelFallbackEvent | BackgroundJobEvent;
165
175
  /** 外部 Agent 记账(`custom{ama.agent-usage}` 汇总,§5.4)。 */
166
176
  export interface ExternalAgentStats {
167
177
  byAgent: Record<string, {
@@ -397,6 +397,11 @@ export interface AgentSession {
397
397
  steering: string[];
398
398
  followUp: string[];
399
399
  };
400
+ /**
401
+ * [W7-B1] 阻塞中的前台子 Agent 任务转后台(不给 taskId = 全部;docs/agents-concurrency-plan.md §2.5)。
402
+ * `reason` 缺省 `"host"`(宿主 / SDK);TUI 的人工操作传 `"user"`。返回被转后台或被打断等待的 taskId。
403
+ */
404
+ backgroundTask(taskId?: string, reason?: "user" | "timeout" | "host"): string[];
400
405
  subscribe(listener: (event: SessionEvent) => void): () => void;
401
406
  compact(instructions?: string): Promise<CompactionResult>;
402
407
  fork(entryId: string): Promise<AgentSession>;
@@ -17,7 +17,6 @@ function builtin(name, description, prompt, permissionMode) {
17
17
  model: "inherit",
18
18
  maxTurns: 30,
19
19
  isolation: "none",
20
- background: false,
21
20
  runner: "ama",
22
21
  prompt,
23
22
  source: "builtin",
@@ -48,7 +48,6 @@ function definition(name, runner, description, source) {
48
48
  model: "inherit",
49
49
  maxTurns: 30,
50
50
  isolation: "none",
51
- background: false,
52
51
  // 驱动表 id(gemini 等)与宿主 id 不在 AgentRunnerSpec 的字面量里,线上照样是字符串
53
52
  runner: runner,
54
53
  prompt: "",
@@ -85,8 +85,8 @@ export function parseAgentDefinition(text, filePath, source) {
85
85
  const isolation = data["isolation"] ?? "none";
86
86
  if (isolation !== "none" && isolation !== "worktree")
87
87
  return fail("isolation must be none or worktree");
88
- const background = data["background"] ?? false;
89
- if (typeof background !== "boolean")
88
+ const background = data["background"];
89
+ if (background !== undefined && typeof background !== "boolean")
90
90
  return fail("background must be true or false");
91
91
  if (runner !== "ama") {
92
92
  const ignored = ["tools", "disallowed-tools", "permission-mode"].filter((key) => data[key] !== undefined);
@@ -100,12 +100,13 @@ export function parseAgentDefinition(text, filePath, source) {
100
100
  model: model.trim(),
101
101
  maxTurns,
102
102
  isolation,
103
- background,
104
103
  runner,
105
104
  prompt: fm.body.trim(),
106
105
  source,
107
106
  filePath,
108
107
  };
108
+ if (background !== undefined)
109
+ agent.background = background;
109
110
  if (tools !== undefined)
110
111
  agent.tools = tools;
111
112
  if (disallowedTools !== undefined)
@@ -28,5 +28,11 @@ export interface NotificationInput {
28
28
  report: string;
29
29
  }
30
30
  export declare function taskNotification(input: NotificationInput): string;
31
+ export declare const NOTIFY_HINT = "A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.";
32
+ export declare function startedText(taskId: string, agent: string): string;
33
+ /** 前台转后台的工具结果(§2.5.3):固定英文;task 工具在前面加 `[task tN] `。 */
34
+ export declare function backgroundedText(reason: "user" | "timeout" | "host", afterMs: number): string;
35
+ /** `task_ctl wait` 被转后台打断时的文案(§2.5.4)。 */
36
+ export declare function waitDetachedText(taskId: string): string;
31
37
  /** 系统提示 rules 节的一句(task 工具的 promptGuidelines;会话开始即固定)。 */
32
- export declare const TASK_NOTIFICATION_RULE = "A <task-notification> message is a background task's report, not the user speaking.";
38
+ export declare const TASK_NOTIFICATION_RULE: string;
@@ -63,5 +63,24 @@ export function taskNotification(input) {
63
63
  attrs.push(`outputFile="${attr(input.outputFile)}"`);
64
64
  return `<task-notification ${attrs.join(" ")}>\n${input.report}\n</task-notification>`;
65
65
  }
66
+ // [W7-B1] 后台任务的固定英文文案(docs/agents-concurrency-plan.md §2.5;工具结果不进缓存前缀,但要
67
+ // 稳定,便于测试与模型学习)。
68
+ export const NOTIFY_HINT = "A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.";
69
+ export function startedText(taskId, agent) {
70
+ return `Started background task ${taskId} (agent ${agent}). ${NOTIFY_HINT}`;
71
+ }
72
+ /** 前台转后台的工具结果(§2.5.3):固定英文;task 工具在前面加 `[task tN] `。 */
73
+ export function backgroundedText(reason, afterMs) {
74
+ const head = reason === "timeout"
75
+ ? `Still running after ${Math.round(afterMs / 1000)}s and moved to the background`
76
+ : "Moved to the background by the user";
77
+ return `${head}; it was not interrupted. ${NOTIFY_HINT} Do not wait for it unless the user asks.`;
78
+ }
79
+ /** `task_ctl wait` 被转后台打断时的文案(§2.5.4)。 */
80
+ export function waitDetachedText(taskId) {
81
+ return (`Task ${taskId} is still running and was moved to the background by the user; do not wait ` +
82
+ "again — a <task-notification> arrives when it finishes.");
83
+ }
66
84
  /** 系统提示 rules 节的一句(task 工具的 promptGuidelines;会话开始即固定)。 */
67
- export const TASK_NOTIFICATION_RULE = "A <task-notification> message is a background task's report, not the user speaking.";
85
+ export const TASK_NOTIFICATION_RULE = "A <task-notification> is a background task's report, not the user speaking; never sleep or " +
86
+ "poll for it.";
@@ -8,9 +8,20 @@ import type { SubagentResult, TaskRegistryView } from "../tools/types.js";
8
8
  export interface TaskControl extends TaskRegistryView {
9
9
  /** 运行中是已有输出,结束后是最终文本。 */
10
10
  output(taskId: string): string | undefined;
11
- /** 等结束或超时(超时返回 undefined);未知 taskId 抛 `task_not_found`。 */
12
- wait(taskId: string, timeoutMs: number): Promise<SubagentResult | undefined>;
11
+ /**
12
+ * 等结束、超时或 `signal` 触发(后两者返回 undefined);未知 taskId 抛 `task_not_found`。
13
+ * [W7-B1] 不给 signal 时由 `background()` 打断(见 `detachSignal`)。
14
+ */
15
+ wait(taskId: string, timeoutMs: number, options?: {
16
+ signal?: AbortSignal;
17
+ }): Promise<SubagentResult | undefined>;
13
18
  stop(taskId: string): Promise<SubagentResult | undefined>;
19
+ /** [W7-B1] 前台任务转后台(不给 taskId = 全部);返回被转后台或被打断等待的 taskId。 */
20
+ background(taskId?: string): string[];
21
+ /** [W7-B1] 该任务的转后台信号:`background()` 触发后 abort(下次取到新的)。 */
22
+ detachSignal(taskId: string): AbortSignal;
23
+ /** [W7-B1] 正在阻塞父回合的任务(前台运行中、或有 `task_ctl wait` 在等)。 */
24
+ blocking(): string[];
14
25
  }
15
26
  export declare function registerTaskControl(sessionId: string, control: TaskControl): void;
16
27
  export declare function unregisterTaskControl(sessionId: string, control: TaskControl): void;
@@ -3,7 +3,7 @@
3
3
  * `custom{ama.task}` 快照的写入形状与 resume 重建(docs/wave5-plan.md §7.4–§7.5)。[W5-G]
4
4
  * 编排在 agent/subagent-registry.ts。
5
5
  */
6
- import type { SubagentUpdateEvent } from "../agent/types-w5.js";
6
+ import type { SessionTaskStats, SubagentStartEvent, SubagentUpdateEvent } from "../agent/types-w5.js";
7
7
  import type { MessageOrigin } from "../ai/types.js";
8
8
  import type { SessionEvent } from "../agent/types.js";
9
9
  import type { TraceExternalTurnData } from "../trace/types.js";
@@ -130,6 +130,14 @@ export interface TaskRecord {
130
130
  direct?: string[];
131
131
  /** [W6-A] 下一次续聊是人在视图里发的(子会话 user 消息记 `origin: "direct"`)。 */
132
132
  directNext?: boolean;
133
+ /**
134
+ * [W7-B1] 前台等待者:launch 时建,转后台时 resolve(工具调用先返回);结束时删掉。
135
+ * `detachParent` 解绑父 signal 与进度回调(父回合 Esc 不再连带停止它)。
136
+ */
137
+ foregroundWaiter?: {
138
+ resolve(result: SubagentResult): void;
139
+ detachParent(): void;
140
+ };
133
141
  }
134
142
  export declare function newRecord(info: TaskInfo, agent: AgentDefinition, parentToolCallId: string, cwd: string, isolation?: "none" | "worktree"): TaskRecord;
135
143
  export interface ProgressSink {
@@ -142,6 +150,10 @@ export interface ProgressSink {
142
150
  export declare function flushText(record: TaskRecord, sink: ProgressSink): void;
143
151
  /** runner 进度 → 记录与 `subagent_update`(文本 ≥ 250 ms 合并,轮次与结束时冲刷)。 */
144
152
  export declare function applyRunnerEvent(record: TaskRecord, event: SubagentEvent, sink: ProgressSink): void;
153
+ /** `subagent_start` 事件(新开或续聊都发)。 */
154
+ export declare function startEvent(record: TaskRecord, handle: TaskHandle): SubagentStartEvent;
155
+ /** `getStats().tasks`:没有任务时 undefined。 */
156
+ export declare function taskStats(records: Iterable<TaskRecord>): SessionTaskStats | undefined;
145
157
  /** 父会话 `custom{ama.task}` 的 data:TaskInfo + 父 toolCallId + 运行目录(带 `status`)。 */
146
158
  export declare function recordData(record: TaskRecord): Record<string, unknown>;
147
159
  /** 从父会话分支重建(同一 taskId 取最后一条;未结束的标 interrupted);返回最大序号。 */
@@ -209,6 +209,38 @@ function recordDisplay(record, event, now) {
209
209
  return;
210
210
  }
211
211
  }
212
+ /** `subagent_start` 事件(新开或续聊都发)。 */
213
+ export function startEvent(record, handle) {
214
+ const event = {
215
+ type: "subagent_start",
216
+ taskId: record.info.taskId,
217
+ parentToolCallId: record.parentToolCallId,
218
+ agent: record.agent.name,
219
+ runner: record.agent.runner,
220
+ description: record.info.description,
221
+ background: record.info.background,
222
+ cwd: record.cwd,
223
+ };
224
+ if (handle.model !== undefined)
225
+ event.model = handle.model;
226
+ if (handle.sessionFile !== undefined)
227
+ event.sessionFile = handle.sessionFile;
228
+ return event;
229
+ }
230
+ /** `getStats().tasks`:没有任务时 undefined。 */
231
+ export function taskStats(records) {
232
+ const byStatus = {};
233
+ let total = 0;
234
+ let running = 0;
235
+ for (const { info } of records) {
236
+ total++;
237
+ if (info.status === "running")
238
+ running++;
239
+ else
240
+ byStatus[info.status] = (byStatus[info.status] ?? 0) + 1;
241
+ }
242
+ return total === 0 ? undefined : { total, running, byStatus };
243
+ }
212
244
  /** 父会话 `custom{ama.task}` 的 data:TaskInfo + 父 toolCallId + 运行目录(带 `status`)。 */
213
245
  export function recordData(record) {
214
246
  return { ...record.info, parentToolCallId: record.parentToolCallId, cwd: record.cwd };
@@ -25,7 +25,11 @@ export interface AgentDefinition {
25
25
  /** 缺省 30。 */
26
26
  maxTurns: number;
27
27
  isolation: "none" | "worktree";
28
- background: boolean;
28
+ /**
29
+ * 显式覆盖缺省前台 / 后台;不设由 `subagents.background` 决定([W7-B1],优先级:调用参数 > 类型
30
+ * 定义 > 配置)。
31
+ */
32
+ background?: boolean;
29
33
  runner: AgentRunnerSpec;
30
34
  /** 正文:追加到子会话系统提示末尾的角色说明。 */
31
35
  prompt: string;
@@ -3,9 +3,13 @@
3
3
  *
4
4
  * 模型表为空、接受任意 slug 的供应商(`chatgpt` 的订阅后端)在选择器里原本一个模型都看不到。
5
5
  * `ama auth login chatgpt` 成功后与 `ama models discover chatgpt` 把账户可用的 slug 写进这里;组装注册表时
6
- * (`mergeDiscoveredModels`)并入**模型表仍为空**的供应商,元数据用 models.dev(快照 ⊕ 用户刷新)补全。
7
- * `ama auth logout chatgpt` 删掉它。只存 slug、显示名、后端给的元数据(codex:上下文窗口、输入模态、推理强度,
8
- * models.dev 补不到时用)、flavor 与时间戳——没有 token、账户 id。
6
+ * (`mergeDiscoveredModels`)并入**模型表仍为空**的供应商。`ama auth logout chatgpt` 删掉它。只存 slug、显示名、
7
+ * 后端给的元数据(上下文窗口、输入模态、推理强度)、flavor 与时间戳——没有 token、账户 id。
8
+ *
9
+ * 元数据优先级:后端明确给的 > models.dev(快照 ⊕ 用户刷新)> 自定义缺省。上下文窗口尤其如此:订阅后端的生效
10
+ * 窗口(如 codex 的 272k)可能远小于 models.dev 记的 API 版窗口(1.1M),按后者算压缩阈值会让请求超窗被拒。
11
+ * 旧版本写的缓存(siwc 不含上下文窗口)照旧用 models.dev;`chatgpt` 两边都没有时用保守缺省
12
+ * `SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW`,不让自动压缩关闭。
9
13
  *
10
14
  * ChatGPT 的缓存带登录 flavor;组装时与当前登录的 flavor 不符视为过期、不并入(选择器提示重新发现)。
11
15
  */
@@ -44,7 +48,9 @@ export declare function writeDiscoveredCache(dataDir: string, provider: string,
44
48
  export declare function clearDiscoveredCache(dataDir: string, provider: string): boolean;
45
49
  /** 后端给的推理强度 → 思考级别映射(`none` 对应 off;没列出的级别不支持)。 */
46
50
  export declare function thinkingMapOf(levels: readonly string[]): Partial<Record<ModelThinkingLevel, string | null>>;
47
- /** 缓存里的模型 → 注册表模型(models.dev 补元数据,补不到用后端给的,再补不到用自定义缺省)。 */
51
+ /** 订阅后端:models.dev 与后端都没给上下文窗口时的保守缺省。 */
52
+ export declare const SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW = 128000;
53
+ /** 缓存里的模型 → 注册表模型(后端给的元数据优先,其次 models.dev,再次自定义缺省)。 */
48
54
  export declare function discoveredModels(file: DiscoveredCacheFile, provider: ProviderData, modelsDev: () => ModelsDevIndex | undefined): Model[];
49
55
  /** 注册表里能追加模型的最小接口(`ProviderRegistry.addModels`)。 */
50
56
  export interface DiscoveredTarget {
@@ -3,9 +3,13 @@
3
3
  *
4
4
  * 模型表为空、接受任意 slug 的供应商(`chatgpt` 的订阅后端)在选择器里原本一个模型都看不到。
5
5
  * `ama auth login chatgpt` 成功后与 `ama models discover chatgpt` 把账户可用的 slug 写进这里;组装注册表时
6
- * (`mergeDiscoveredModels`)并入**模型表仍为空**的供应商,元数据用 models.dev(快照 ⊕ 用户刷新)补全。
7
- * `ama auth logout chatgpt` 删掉它。只存 slug、显示名、后端给的元数据(codex:上下文窗口、输入模态、推理强度,
8
- * models.dev 补不到时用)、flavor 与时间戳——没有 token、账户 id。
6
+ * (`mergeDiscoveredModels`)并入**模型表仍为空**的供应商。`ama auth logout chatgpt` 删掉它。只存 slug、显示名、
7
+ * 后端给的元数据(上下文窗口、输入模态、推理强度)、flavor 与时间戳——没有 token、账户 id。
8
+ *
9
+ * 元数据优先级:后端明确给的 > models.dev(快照 ⊕ 用户刷新)> 自定义缺省。上下文窗口尤其如此:订阅后端的生效
10
+ * 窗口(如 codex 的 272k)可能远小于 models.dev 记的 API 版窗口(1.1M),按后者算压缩阈值会让请求超窗被拒。
11
+ * 旧版本写的缓存(siwc 不含上下文窗口)照旧用 models.dev;`chatgpt` 两边都没有时用保守缺省
12
+ * `SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW`,不让自动压缩关闭。
9
13
  *
10
14
  * ChatGPT 的缓存带登录 flavor;组装时与当前登录的 flavor 不符视为过期、不并入(选择器提示重新发现)。
11
15
  */
@@ -93,26 +97,38 @@ export function thinkingMapOf(levels) {
93
97
  map[level] = levels.includes(level) ? level : null;
94
98
  return map;
95
99
  }
96
- /** models.dev 没补上的字段用后端给的元数据。 */
97
- function withBackendMetadata(model, cached, matched) {
98
- if (model.contextWindow === undefined && cached.contextWindow !== undefined)
100
+ /** 订阅后端:models.dev 与后端都没给上下文窗口时的保守缺省。 */
101
+ export const SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW = 128_000;
102
+ const SUBSCRIPTION_PROVIDERS = new Set(["chatgpt"]);
103
+ /** 后端明确给的元数据覆盖 models.dev 补的;没给的字段保留 models.dev 的(maxTokens 只来自 models.dev)。 */
104
+ function withBackendMetadata(model, cached) {
105
+ if (cached.contextWindow !== undefined) {
99
106
  model.contextWindow = cached.contextWindow;
100
- if (matched)
101
- return;
107
+ // models.dev 的输入上限是 API 版的,超过后端窗口就没有意义
108
+ if (model.inputLimit !== undefined && model.inputLimit >= cached.contextWindow)
109
+ delete model.inputLimit;
110
+ }
102
111
  if (cached.input !== undefined)
103
112
  model.input = [...cached.input];
104
- const efforts = (cached.reasoningLevels ?? []).filter((x) => x !== "none");
105
- if (efforts.length > 0) {
113
+ if (cached.reasoningLevels === undefined)
114
+ return;
115
+ if (cached.reasoningLevels.some((x) => x !== "none")) {
106
116
  model.reasoning = true;
107
- model.thinkingLevelMap = thinkingMapOf(cached.reasoningLevels ?? []);
117
+ model.thinkingLevelMap = thinkingMapOf(cached.reasoningLevels);
118
+ }
119
+ else {
120
+ model.reasoning = false;
121
+ delete model.thinkingLevelMap;
108
122
  }
109
123
  }
110
- /** 缓存里的模型 → 注册表模型(models.dev 补元数据,补不到用后端给的,再补不到用自定义缺省)。 */
124
+ /** 缓存里的模型 → 注册表模型(后端给的元数据优先,其次 models.dev,再次自定义缺省)。 */
111
125
  export function discoveredModels(file, provider, modelsDev) {
112
126
  return file.models.map((m) => {
113
- const { entry, metadata } = enrichEntry(m.name ? { id: m.id, name: m.name } : { id: m.id }, modelsDev);
127
+ const { entry } = enrichEntry(m.name ? { id: m.id, name: m.name } : { id: m.id }, modelsDev);
114
128
  const model = withCustomDefaults(entry, provider.id, provider.api);
115
- withBackendMetadata(model, m, metadata.match !== undefined);
129
+ withBackendMetadata(model, m);
130
+ if (model.contextWindow === undefined && SUBSCRIPTION_PROVIDERS.has(provider.id))
131
+ model.contextWindow = SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW;
116
132
  // 只挂发现时登录的 flavor 对应的渠道(订阅后端的另一条渠道用不了,不在说明与 `@` 行里出现);
117
133
  // 缓存没写 flavor 或渠道不存在时用供应商的缺省渠道
118
134
  const names = (provider.channels ?? []).map((c) => c.name);
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * ChatGPT 后端的只读查询([W6-O]):模型列表(`ama models discover chatgpt`)与 codex 的 `wham/usage`。
3
3
  *
4
- * - SIWC:`GET {base}/models`,只留 `visibility === "list"`(没有该字段的也留);
4
+ * - SIWC:`GET {base}/models`,只留 `visibility === "list"`(没有该字段的也留);条目若带下述元数据字段同样取;
5
5
  * - codex:`GET {base}/models?client_version=<Codex CLI 版本>`(`codexClientVersion()`;后端按每个模型的
6
6
  * `minimal_client_version` 过滤,发 ama 自己的版本号会一个都拿不到),`models[].slug`(同样按 visibility 筛),
7
- * 另取 `context_window`、`input_modalities`、`supported_reasoning_levels` 作元数据(models.dev 补不到时用);
8
- * `GET https://chatgpt.com/backend-api/wham/usage`(由 codex base 推出),私有格式,解析失败返回 undefined。
9
- * SIWC 只取 slug 与显示名。请求带 Bearer;codex 另带 `ChatGPT-Account-ID` 与 `originator`。
7
+ * 另取 `context_window`、`input_modalities`、`supported_reasoning_levels` 作元数据(后端给了就优先于 models.dev,
8
+ * 见 discovered-cache.ts;`max_context_window` 是可调上限而非生效值、`auto_compact_token_limit` 没有对应的模型字段,
9
+ * 都不取);`GET https://chatgpt.com/backend-api/wham/usage`(由 codex base 推出),私有格式,解析失败返回 undefined。
10
+ * 请求带 Bearer;codex 另带 `ChatGPT-Account-ID` 与 `originator`。
10
11
  */
11
12
  import { type QuotaSnapshot } from "../../ai/apis/chatgpt-rate-limits.js";
12
13
  import type { ChatGptFlavor } from "../../config/types-w6.js";
@@ -23,7 +24,7 @@ export interface BackendAuth {
23
24
  export interface DiscoveredModel {
24
25
  id: string;
25
26
  name?: string;
26
- /** 以下只有 codex 后端给。 */
27
+ /** 以下是后端给的元数据(codex 后端给;SIWC 条目带了也取)。 */
27
28
  contextWindow?: number;
28
29
  input?: ("text" | "image")[];
29
30
  /** 支持的推理强度(`none` / `minimal` / `low` / `medium` / `high` / `xhigh` …)。 */
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * ChatGPT 后端的只读查询([W6-O]):模型列表(`ama models discover chatgpt`)与 codex 的 `wham/usage`。
3
3
  *
4
- * - SIWC:`GET {base}/models`,只留 `visibility === "list"`(没有该字段的也留);
4
+ * - SIWC:`GET {base}/models`,只留 `visibility === "list"`(没有该字段的也留);条目若带下述元数据字段同样取;
5
5
  * - codex:`GET {base}/models?client_version=<Codex CLI 版本>`(`codexClientVersion()`;后端按每个模型的
6
6
  * `minimal_client_version` 过滤,发 ama 自己的版本号会一个都拿不到),`models[].slug`(同样按 visibility 筛),
7
- * 另取 `context_window`、`input_modalities`、`supported_reasoning_levels` 作元数据(models.dev 补不到时用);
8
- * `GET https://chatgpt.com/backend-api/wham/usage`(由 codex base 推出),私有格式,解析失败返回 undefined。
9
- * SIWC 只取 slug 与显示名。请求带 Bearer;codex 另带 `ChatGPT-Account-ID` 与 `originator`。
7
+ * 另取 `context_window`、`input_modalities`、`supported_reasoning_levels` 作元数据(后端给了就优先于 models.dev,
8
+ * 见 discovered-cache.ts;`max_context_window` 是可调上限而非生效值、`auto_compact_token_limit` 没有对应的模型字段,
9
+ * 都不取);`GET https://chatgpt.com/backend-api/wham/usage`(由 codex base 推出),私有格式,解析失败返回 undefined。
10
+ * 请求带 Bearer;codex 另带 `ChatGPT-Account-ID` 与 `originator`。
10
11
  */
11
12
  import { parseUsagePayload } from "../../ai/apis/chatgpt-rate-limits.js";
12
13
  import { DEFAULT_CODEX_CLIENT_VERSION, DEFAULT_ORIGINATOR } from "./presets.js";
@@ -27,8 +28,8 @@ function listed(item) {
27
28
  const visibility = item["visibility"];
28
29
  return visibility === undefined || visibility === "list";
29
30
  }
30
- /** codex 模型条目里的元数据;字段缺失或形状不对就不填。 */
31
- function codexMetadata(item) {
31
+ /** 模型条目里的元数据;字段缺失或形状不对就不填。 */
32
+ function backendMetadata(item) {
32
33
  const out = {};
33
34
  const window = item["context_window"];
34
35
  if (typeof window === "number" && Number.isInteger(window) && window > 0)
@@ -80,7 +81,7 @@ export async function listChatGptModels(fetchFn, baseUrl, auth) {
80
81
  out.push({
81
82
  id,
82
83
  ...(typeof name === "string" && name !== "" ? { name } : {}),
83
- ...(auth.flavor === "codex" ? codexMetadata(item) : {}),
84
+ ...backendMetadata(item),
84
85
  });
85
86
  }
86
87
  return out;