@armadra/agent 0.7.3 → 0.7.5

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 (179) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/CHANGELOG.zh-CN.md +48 -0
  3. package/dist/agent/retry.d.ts +14 -5
  4. package/dist/agent/retry.js +30 -8
  5. package/dist/agent/session-cache-key.d.ts +8 -0
  6. package/dist/agent/session-cache-key.js +46 -0
  7. package/dist/agent/session-cache.d.ts +2 -3
  8. package/dist/agent/session-cache.js +19 -37
  9. package/dist/agent/session-classifier.d.ts +3 -2
  10. package/dist/agent/session-classifier.js +36 -10
  11. package/dist/agent/session-compaction.d.ts +24 -5
  12. package/dist/agent/session-compaction.js +54 -20
  13. package/dist/agent/session-core.d.ts +2 -0
  14. package/dist/agent/session-run.d.ts +12 -0
  15. package/dist/agent/session-run.js +29 -10
  16. package/dist/agent/session-subagent.d.ts +8 -1
  17. package/dist/agent/session-subagent.js +37 -14
  18. package/dist/agent/session-sync.d.ts +8 -2
  19. package/dist/agent/session-sync.js +7 -10
  20. package/dist/agent/session-tools.js +3 -2
  21. package/dist/agent/subagent-fork.d.ts +67 -0
  22. package/dist/agent/subagent-fork.js +87 -0
  23. package/dist/agent/subagent-registry.d.ts +2 -2
  24. package/dist/agent/subagent-registry.js +2 -2
  25. package/dist/agent/tool-availability.d.ts +8 -0
  26. package/dist/agent/tool-availability.js +8 -0
  27. package/dist/agent/tool-runner.js +13 -4
  28. package/dist/agent/worktree.d.ts +8 -0
  29. package/dist/agent/worktree.js +51 -4
  30. package/dist/agents/parse.js +6 -1
  31. package/dist/agents/task-record.js +3 -0
  32. package/dist/agents/types.d.ts +2 -0
  33. package/dist/ai/apis/anthropic-messages.js +10 -5
  34. package/dist/ai/apis/anthropic-request.d.ts +2 -1
  35. package/dist/ai/apis/anthropic-request.js +15 -4
  36. package/dist/ai/apis/cache-params.js +2 -1
  37. package/dist/ai/apis/google-generative-ai.js +4 -2
  38. package/dist/ai/apis/google-request.js +2 -1
  39. package/dist/ai/apis/max-tokens.d.ts +42 -0
  40. package/dist/ai/apis/max-tokens.js +121 -0
  41. package/dist/ai/apis/openai-completions.js +10 -5
  42. package/dist/ai/apis/openai-request.js +4 -2
  43. package/dist/ai/apis/openai-responses-request.js +3 -2
  44. package/dist/ai/apis/openai-responses.js +9 -5
  45. package/dist/ai/apis/shared.d.ts +3 -1
  46. package/dist/ai/apis/shared.js +21 -3
  47. package/dist/ai/cache/fingerprint.d.ts +4 -1
  48. package/dist/ai/cache/fingerprint.js +18 -1
  49. package/dist/ai/cache/miss.d.ts +2 -1
  50. package/dist/ai/cache/miss.js +9 -3
  51. package/dist/ai/cache/reporting.js +5 -1
  52. package/dist/ai/cache/types.d.ts +10 -2
  53. package/dist/ai/cache/warmer.d.ts +6 -1
  54. package/dist/ai/cache/warmer.js +7 -0
  55. package/dist/ai/context.d.ts +12 -4
  56. package/dist/ai/context.js +43 -24
  57. package/dist/ai/fake/fake-provider.d.ts +8 -1
  58. package/dist/ai/fake/fake-provider.js +19 -7
  59. package/dist/ai/fake/fake-script.d.ts +2 -0
  60. package/dist/ai/http.d.ts +17 -9
  61. package/dist/ai/http.js +33 -13
  62. package/dist/ai/image-intern.d.ts +29 -0
  63. package/dist/ai/image-intern.js +58 -0
  64. package/dist/ai/json-body.d.ts +63 -0
  65. package/dist/ai/json-body.js +286 -0
  66. package/dist/ai/overflow.js +5 -0
  67. package/dist/ai/providers/catalog-data.js +13 -13
  68. package/dist/ai/providers/catalog.d.ts +33 -0
  69. package/dist/ai/providers/catalog.js +105 -6
  70. package/dist/ai/providers/enrich.d.ts +13 -3
  71. package/dist/ai/providers/enrich.js +87 -31
  72. package/dist/ai/sse.d.ts +3 -2
  73. package/dist/ai/sse.js +3 -2
  74. package/dist/ai/types.d.ts +9 -2
  75. package/dist/bundle/ama.cjs +3637 -1973
  76. package/dist/cli/bootstrap.js +13 -5
  77. package/dist/cli/compose-agents.js +3 -0
  78. package/dist/cli/compose-extensions.js +2 -0
  79. package/dist/cli/compose-request.d.ts +20 -0
  80. package/dist/cli/compose-request.js +37 -0
  81. package/dist/cli/compose-session.js +2 -2
  82. package/dist/cli/deps.d.ts +1 -1
  83. package/dist/cli/subcommands/model-meta.d.ts +2 -1
  84. package/dist/cli/subcommands/model-meta.js +3 -2
  85. package/dist/codemode/host-side.d.ts +11 -4
  86. package/dist/codemode/host-side.js +25 -6
  87. package/dist/codemode/tool.d.ts +2 -0
  88. package/dist/codemode/tool.js +4 -0
  89. package/dist/config/json-schema.js +9 -2
  90. package/dist/config/key-docs.js +13 -4
  91. package/dist/config/schema-w5.js +9 -1
  92. package/dist/config/schema.d.ts +1 -1
  93. package/dist/config/schema.js +19 -3
  94. package/dist/config/settings-registry.js +6 -0
  95. package/dist/config/types-w5.d.ts +2 -0
  96. package/dist/config/types.d.ts +19 -1
  97. package/dist/config/types.js +4 -0
  98. package/dist/i18n/catalog.d.ts +13 -2
  99. package/dist/i18n/messages/cli.d.ts +2 -0
  100. package/dist/i18n/messages/cli.js +2 -0
  101. package/dist/i18n/messages/config-keys.d.ts +10 -0
  102. package/dist/i18n/messages/config-keys.js +12 -2
  103. package/dist/i18n/messages/config.d.ts +14 -0
  104. package/dist/i18n/messages/config.js +4 -0
  105. package/dist/i18n/messages/errors.d.ts +3 -2
  106. package/dist/i18n/messages/errors.js +15 -2
  107. package/dist/i18n/messages/report.d.ts +2 -0
  108. package/dist/i18n/messages/report.js +2 -0
  109. package/dist/i18n/messages/settings.d.ts +4 -0
  110. package/dist/i18n/messages/settings.js +6 -2
  111. package/dist/i18n/messages/subcommands.d.ts +2 -2
  112. package/dist/i18n/messages/subcommands.js +4 -2
  113. package/dist/modes/acp/acp-server.js +30 -18
  114. package/dist/modes/acp/acp-sessions.d.ts +22 -0
  115. package/dist/modes/acp/acp-sessions.js +68 -0
  116. package/dist/modes/context-report.js +3 -2
  117. package/dist/modes/print/json-event.d.ts +12 -1
  118. package/dist/modes/print/json-event.js +48 -1
  119. package/dist/modes/rpc/rpc-mode.d.ts +1 -0
  120. package/dist/modes/rpc/rpc-mode.js +9 -2
  121. package/dist/modes/session-report.d.ts +1 -1
  122. package/dist/modes/session-report.js +12 -6
  123. package/dist/rpc.d.ts +5 -2
  124. package/dist/session/line-reader.d.ts +28 -0
  125. package/dist/session/line-reader.js +82 -0
  126. package/dist/session/list.d.ts +14 -0
  127. package/dist/session/list.js +150 -0
  128. package/dist/session/manager.d.ts +23 -3
  129. package/dist/session/manager.js +91 -62
  130. package/dist/session/migrate.d.ts +2 -1
  131. package/dist/session/migrate.js +2 -1
  132. package/dist/session/offload.d.ts +51 -0
  133. package/dist/session/offload.js +195 -0
  134. package/dist/session/projection.d.ts +16 -3
  135. package/dist/session/projection.js +96 -29
  136. package/dist/session/scan.d.ts +3 -3
  137. package/dist/session/scan.js +9 -18
  138. package/dist/session/store.d.ts +15 -4
  139. package/dist/session/store.js +54 -38
  140. package/dist/session/types.d.ts +7 -2
  141. package/dist/tools/bash.d.ts +1 -1
  142. package/dist/tools/bash.js +5 -4
  143. package/dist/tools/glob.d.ts +2 -2
  144. package/dist/tools/glob.js +2 -2
  145. package/dist/tools/grep.js +4 -6
  146. package/dist/tools/image-file.js +2 -1
  147. package/dist/tools/read-lines.d.ts +71 -0
  148. package/dist/tools/read-lines.js +224 -0
  149. package/dist/tools/read.d.ts +6 -2
  150. package/dist/tools/read.js +47 -27
  151. package/dist/tools/task.d.ts +9 -0
  152. package/dist/tools/task.js +21 -8
  153. package/dist/tools/truncate.d.ts +7 -0
  154. package/dist/tools/truncate.js +11 -0
  155. package/dist/tools/types.d.ts +13 -0
  156. package/dist/trace/build-index.js +13 -1
  157. package/dist/tui/ansi.d.ts +2 -0
  158. package/dist/tui/ansi.js +7 -3
  159. package/dist/tui/components/editor-buffer.js +2 -2
  160. package/dist/tui/components/editor.js +5 -6
  161. package/dist/tui/keys.js +2 -2
  162. package/dist/tui/stdin-buffer.js +3 -3
  163. package/docs/acp.md +8 -1
  164. package/docs/agents.md +42 -6
  165. package/docs/codemode.md +4 -2
  166. package/docs/en/acp.md +3 -2
  167. package/docs/en/host-api.md +1 -1
  168. package/docs/en/permissions.md +2 -2
  169. package/docs/en/providers.md +54 -20
  170. package/docs/en/rpc.md +23 -7
  171. package/docs/en/sessions.md +4 -2
  172. package/docs/en/tui.md +2 -2
  173. package/docs/permissions.md +4 -1
  174. package/docs/providers.md +85 -22
  175. package/docs/rpc.md +22 -6
  176. package/docs/session-format.md +13 -2
  177. package/docs/sessions.md +3 -1
  178. package/docs/tui.md +1 -1
  179. package/package.json +2 -2
@@ -78,6 +78,10 @@ export function checkCatalogEntry(value, path) {
78
78
  problems.push(`${path}.modelsDev`);
79
79
  if (value["_reason"] !== undefined && typeof value["_reason"] !== "string")
80
80
  problems.push(`${path}._reason`);
81
+ const aliases = value["aliases"];
82
+ if (aliases !== undefined &&
83
+ (!Array.isArray(aliases) || !aliases.every((a) => typeof a === "string" && a.length > 0)))
84
+ problems.push(`${path}.aliases`);
81
85
  if (value["maxTokens"] !== undefined &&
82
86
  (!isNonNegative(value["maxTokens"]) || value["maxTokens"] === 0)) {
83
87
  problems.push(`${path}.maxTokens`);
@@ -160,7 +164,7 @@ export function inheritedFields(file, entry, index = builtinSnapshotIndex()) {
160
164
  }
161
165
  /** 合并:快照 ⊕ 目录条目(`cost` 按键合并);返回模型与从快照继承的字段名。 */
162
166
  export function resolveCatalogEntry(entry, inherited) {
163
- const { modelsDev: _modelsDev, _reason: _r, ...own } = structuredClone(entry);
167
+ const { modelsDev: _modelsDev, _reason: _r, aliases: _a, ...own } = structuredClone(entry);
164
168
  const model = { ...structuredClone(inherited), ...own };
165
169
  if (own.cost !== undefined && inherited.cost !== undefined)
166
170
  model.cost = { ...structuredClone(inherited.cost), ...own.cost };
@@ -207,6 +211,9 @@ function fileProblems(value) {
207
211
  const modelsDev = value["modelsDev"];
208
212
  if (modelsDev !== undefined && modelsDev !== false && typeof modelsDev !== "string")
209
213
  problems.push("$.modelsDev");
214
+ const small = value["small"];
215
+ if (small !== undefined && (typeof small !== "string" || small.length === 0))
216
+ problems.push("$.small");
210
217
  if (!Array.isArray(value["models"]))
211
218
  problems.push("$.models must be an array");
212
219
  else {
@@ -220,6 +227,8 @@ function fileProblems(value) {
220
227
  seen.add(id);
221
228
  }
222
229
  });
230
+ if (typeof small === "string" && small.length > 0 && !seen.has(small))
231
+ problems.push(`$.small not in models: ${small}`);
223
232
  }
224
233
  return problems;
225
234
  }
@@ -260,11 +269,7 @@ export function parseCatalogFile(value, source, index) {
260
269
  }
261
270
  const caches = new WeakMap();
262
271
  let lastInherited = new Map();
263
- /**
264
- * 内置目录:供应商 id → 模型条目(深拷贝,调用方可随意修改)。`index` 缺省用内置快照;传入
265
- * 「快照 ⊕ 用户刷新」的索引时目录也吃刷新后的数值(缺字段的条目退回内置快照)。
266
- */
267
- export function loadBuiltinCatalog(index) {
272
+ function resolvedCatalog(index) {
268
273
  const key = index ?? builtinSnapshotIndex();
269
274
  let cache = caches.get(key);
270
275
  if (cache === undefined) {
@@ -276,6 +281,14 @@ export function loadBuiltinCatalog(index) {
276
281
  }
277
282
  caches.set(key, cache);
278
283
  }
284
+ return cache;
285
+ }
286
+ /**
287
+ * 内置目录:供应商 id → 模型条目(深拷贝,调用方可随意修改)。`index` 缺省用内置快照;传入
288
+ * 「快照 ⊕ 用户刷新」的索引时目录也吃刷新后的数值(缺字段的条目退回内置快照)。
289
+ */
290
+ export function loadBuiltinCatalog(index) {
291
+ const cache = resolvedCatalog(index);
279
292
  const copy = new Map();
280
293
  lastInherited = new Map();
281
294
  for (const [provider, resolved] of cache) {
@@ -289,6 +302,90 @@ export function loadBuiltinCatalog(index) {
289
302
  export function catalogInherited(provider, modelId) {
290
303
  return lastInherited.get(`${provider}/${modelId}`) ?? [];
291
304
  }
305
+ const TIER_SUFFIX = /-(minimal|low|medium|high|xhigh|tiered)$/;
306
+ export function normalizeModelId(id) {
307
+ return id
308
+ .trim()
309
+ .toLowerCase()
310
+ .replace(/^[a-z0-9_.-]+\//, "")
311
+ .replace(/:latest$/, "");
312
+ }
313
+ let aliasIndex;
314
+ /** 别名索引(测试可传入自造的目录源)。 */
315
+ export function buildAliasIndex(sources = CATALOG_SOURCES) {
316
+ const index = new Map();
317
+ const add = (key, target) => {
318
+ const seen = index.get(key);
319
+ if (seen === undefined)
320
+ index.set(key, target);
321
+ else if (seen === "ambiguous" || seen.provider !== target.provider || seen.id !== target.id)
322
+ index.set(key, "ambiguous");
323
+ };
324
+ for (const raw of Object.values(sources)) {
325
+ const file = parseCatalogSource(JSON.parse(raw), "catalog");
326
+ for (const entry of file.models) {
327
+ const snapshotRef = snapshotRefOf(file, entry);
328
+ const target = {
329
+ provider: file.provider,
330
+ id: entry.id,
331
+ ...(snapshotRef ? { snapshotRef } : {}),
332
+ };
333
+ if (!entry.id.includes("/"))
334
+ add(normalizeModelId(entry.id), target);
335
+ for (const alias of entry.aliases ?? [])
336
+ add(normalizeModelId(alias), target);
337
+ }
338
+ }
339
+ return index;
340
+ }
341
+ function hitOf(target, tier) {
342
+ const model = resolvedCatalog()
343
+ .get(target.provider)
344
+ ?.file.models.find((m) => m.id === target.id);
345
+ if (model === undefined)
346
+ return undefined;
347
+ return {
348
+ ref: `${target.provider}/${target.id}`,
349
+ model: structuredClone(model),
350
+ ...(target.snapshotRef !== undefined ? { snapshotRef: target.snapshotRef } : {}),
351
+ ...(tier !== undefined ? { tier } : {}),
352
+ };
353
+ }
354
+ /** 按 id 唯一命中的目录条目(没有或 ambiguous 返回 undefined)。 */
355
+ export function catalogByAlias(id) {
356
+ aliasIndex ??= buildAliasIndex();
357
+ const key = normalizeModelId(id);
358
+ const exact = aliasIndex.get(key);
359
+ if (exact !== undefined)
360
+ return exact === "ambiguous" ? undefined : hitOf(exact);
361
+ const tier = TIER_SUFFIX.exec(key);
362
+ if (tier === null)
363
+ return undefined;
364
+ const base = aliasIndex.get(key.slice(0, tier.index));
365
+ return base === undefined || base === "ambiguous" ? undefined : hitOf(base, tier[1]);
366
+ }
367
+ /** 显式指定的目录条目 `provider/id`(`models[].catalog`)。 */
368
+ export function catalogByRef(ref) {
369
+ const slash = ref.indexOf("/");
370
+ if (slash <= 0)
371
+ return undefined;
372
+ const provider = ref.slice(0, slash);
373
+ const id = ref.slice(slash + 1);
374
+ const raw = CATALOG_SOURCES[provider];
375
+ if (raw === undefined)
376
+ return undefined;
377
+ const file = parseCatalogSource(JSON.parse(raw), "catalog");
378
+ const entry = file.models.find((m) => m.id === id);
379
+ if (entry === undefined)
380
+ return undefined;
381
+ const snapshotRef = snapshotRefOf(file, entry);
382
+ return hitOf({ provider, id, ...(snapshotRef ? { snapshotRef } : {}) });
383
+ }
384
+ /** [ME-D] 供应商目录里给 auto 模式分类用的小模型 id(D12)。 */
385
+ export function catalogSmall(provider) {
386
+ const raw = CATALOG_SOURCES[provider];
387
+ return raw === undefined ? undefined : parseCatalogSource(JSON.parse(raw), "catalog").small;
388
+ }
292
389
  /** 目录条目 → Model(补 provider / api 与缺省 input)。 */
293
390
  export function toModel(entry, provider, api) {
294
391
  return { ...structuredClone(entry), input: entry.input ?? ["text"], provider, api };
@@ -308,6 +405,8 @@ export function withCustomDefaults(entry, provider, api) {
308
405
  /** 只改元数据:浅合并,`compat` / `cost` / `thinkingLevelMap` / `headers` 再合并一层。 */
309
406
  export function applyModelOverride(model, override) {
310
407
  const { id: _id, ...rest } = override;
408
+ // 配置专用键([ME-D] `catalog`)不进 Model
409
+ delete rest.catalog;
311
410
  const next = { ...model, ...structuredClone(rest) };
312
411
  if (override.compat && model.compat)
313
412
  next.compat = { ...model.compat, ...override.compat };
@@ -4,11 +4,17 @@
4
4
  * 优先级:用户配置写了的字段 > models.dev > 自定义缺省(maxTokens 8192、input ["text"]、
5
5
  * reasoning false、不猜 contextWindow)。内置目录的模型不经过这里:目录在 catalog.ts 里已经
6
6
  * 「快照 ⊕ 覆盖」合并好([W5-M1]),这里只给它们标来源。只读传入的索引,不联网。
7
+ *
8
+ * [ME-D] 中转模型继承官方目录(D10):`catalog` 没关时按 id 别名(或显式 `provider/id`)唯一命中
9
+ * 内置目录条目,继承模型固有属性 `reasoning`、`input`、`thinkingLevelMap`、`promptCache.minTokens`、
10
+ * `compat.requiresReasoningContentOnAssistantMessages`(用户写了的不覆盖),并把 models.dev 显式
11
+ * 匹配改为该条目的快照引用;不继承价格、TTL 与 `thinkingFormat`。去掉思考档后缀才命中时(中转按
12
+ * id 定思考档)不继承思考设置。models.dev 仍补不出的窗口 / 输出上限取目录值。
7
13
  */
8
14
  import type { ModelConfig } from "../../config/types.js";
9
15
  import type { Model } from "../types.js";
10
16
  import { type EnrichableField, type ModelsDevIndex, type ModelsDevMatch } from "./models-dev.js";
11
- export type FieldSource = "config" | "catalog" | "models.dev" | "default";
17
+ export type FieldSource = "config" | "catalog" | "catalog-alias" | "models.dev" | "default";
12
18
  export interface ModelMetadata {
13
19
  sources: Record<EnrichableField, FieldSource>;
14
20
  /** models.dev 匹配结果;未查(没有缓存或 `modelsDev: false`)为 undefined。 */
@@ -17,16 +23,19 @@ export interface ModelMetadata {
17
23
  looked: boolean;
18
24
  /** models.dev 的 tool_call。 */
19
25
  toolCall?: boolean;
26
+ /** [ME-D] 按 id 继承的内置目录条目 `provider/id`。 */
27
+ catalog?: string;
20
28
  }
21
29
  export type ModelsDevSource = ModelsDevIndex | (() => ModelsDevIndex | undefined) | undefined;
22
30
  /** 惰性取索引:只有真的要补字段时才读缓存(启动时没有自定义模型就不读约 2 MB 的缓存)。 */
23
31
  export declare function lazyIndex(source: ModelsDevSource): () => ModelsDevIndex | undefined;
32
+ type EnrichedEntry = Omit<ModelConfig, "modelsDev" | "channels" | "catalog">;
24
33
  /**
25
34
  * 补全一个自定义模型条目(config 的 `models[]` 或合成的模型)。返回补过字段的条目(不含
26
- * `modelsDev` / `channels` 这些配置专用键)与每个字段的来源。
35
+ * `modelsDev` / `channels` / `catalog` 这些配置专用键)与每个字段的来源。
27
36
  */
28
37
  export declare function enrichEntry(entry: ModelConfig, index: () => ModelsDevIndex | undefined): {
29
- entry: Omit<ModelConfig, "modelsDev" | "channels">;
38
+ entry: EnrichedEntry;
30
39
  metadata: ModelMetadata;
31
40
  };
32
41
  /**
@@ -35,3 +44,4 @@ export declare function enrichEntry(entry: ModelConfig, index: () => ModelsDevIn
35
44
  */
36
45
  export declare function catalogMetadata(model: Model): ModelMetadata;
37
46
  export declare function sourceText(source: FieldSource): string;
47
+ export {};
@@ -4,9 +4,15 @@
4
4
  * 优先级:用户配置写了的字段 > models.dev > 自定义缺省(maxTokens 8192、input ["text"]、
5
5
  * reasoning false、不猜 contextWindow)。内置目录的模型不经过这里:目录在 catalog.ts 里已经
6
6
  * 「快照 ⊕ 覆盖」合并好([W5-M1]),这里只给它们标来源。只读传入的索引,不联网。
7
+ *
8
+ * [ME-D] 中转模型继承官方目录(D10):`catalog` 没关时按 id 别名(或显式 `provider/id`)唯一命中
9
+ * 内置目录条目,继承模型固有属性 `reasoning`、`input`、`thinkingLevelMap`、`promptCache.minTokens`、
10
+ * `compat.requiresReasoningContentOnAssistantMessages`(用户写了的不覆盖),并把 models.dev 显式
11
+ * 匹配改为该条目的快照引用;不继承价格、TTL 与 `thinkingFormat`。去掉思考档后缀才命中时(中转按
12
+ * id 定思考档)不继承思考设置。models.dev 仍补不出的窗口 / 输出上限取目录值。
7
13
  */
8
- import { catalogInherited } from "./catalog.js";
9
- import { ENRICHABLE_FIELDS, modelsDevFields, } from "./models-dev.js";
14
+ import { catalogByAlias, catalogByRef, catalogInherited } from "./catalog.js";
15
+ import { ENRICHABLE_FIELDS, MODELS_DEV_MAX_OUTPUT, modelsDevFields, } from "./models-dev.js";
10
16
  import { msg } from "../../i18n/index.js";
11
17
  /** 惰性取索引:只有真的要补字段时才读缓存(启动时没有自定义模型就不读约 2 MB 的缓存)。 */
12
18
  export function lazyIndex(source) {
@@ -32,47 +38,97 @@ const METADATA_FIELDS = ["family", "knowledge", "releaseDate", "inputLimit", "st
32
38
  function needsLookup(entry) {
33
39
  return ENRICHABLE_FIELDS.some((field) => entry[field] === undefined);
34
40
  }
41
+ function catalogHit(entry) {
42
+ if (entry.catalog === false)
43
+ return undefined;
44
+ return typeof entry.catalog === "string" ? catalogByRef(entry.catalog) : catalogByAlias(entry.id);
45
+ }
46
+ /** 目录固有属性(用户写了的不覆盖)。 */
47
+ function inheritIntrinsic(out, hit, sources) {
48
+ const model = hit.model;
49
+ if (out.input === undefined && model.input !== undefined) {
50
+ out.input = [...model.input];
51
+ sources.input = "catalog-alias";
52
+ }
53
+ if (hit.tier === undefined) {
54
+ if (out.reasoning === undefined) {
55
+ out.reasoning = model.reasoning;
56
+ sources.reasoning = "catalog-alias";
57
+ }
58
+ if (out.thinkingLevelMap === undefined && model.thinkingLevelMap !== undefined)
59
+ out.thinkingLevelMap = structuredClone(model.thinkingLevelMap);
60
+ }
61
+ const minTokens = model.promptCache?.minTokens;
62
+ if (minTokens !== undefined && out.promptCache?.minTokens === undefined)
63
+ out.promptCache = { ...out.promptCache, minTokens };
64
+ const requires = model.compat?.requiresReasoningContentOnAssistantMessages;
65
+ if (requires !== undefined &&
66
+ out.compat?.requiresReasoningContentOnAssistantMessages === undefined)
67
+ out.compat = { ...out.compat, requiresReasoningContentOnAssistantMessages: requires };
68
+ }
69
+ /** models.dev 之后仍缺的窗口与输出上限取目录值(输出按 models.dev 口径封顶)。 */
70
+ function fillLimits(out, hit, sources) {
71
+ if (out.contextWindow === undefined && hit.model.contextWindow !== undefined) {
72
+ out.contextWindow = hit.model.contextWindow;
73
+ sources.contextWindow = "catalog-alias";
74
+ }
75
+ if (out.maxTokens === undefined) {
76
+ out.maxTokens = Math.min(hit.model.maxTokens, MODELS_DEV_MAX_OUTPUT);
77
+ sources.maxTokens = "catalog-alias";
78
+ }
79
+ }
35
80
  /**
36
81
  * 补全一个自定义模型条目(config 的 `models[]` 或合成的模型)。返回补过字段的条目(不含
37
- * `modelsDev` / `channels` 这些配置专用键)与每个字段的来源。
82
+ * `modelsDev` / `channels` / `catalog` 这些配置专用键)与每个字段的来源。
38
83
  */
39
84
  export function enrichEntry(entry, index) {
40
- const { modelsDev, channels: _channels, ...rest } = entry;
85
+ const { modelsDev, channels: _channels, catalog: _catalog, ...rest } = entry;
41
86
  const sources = {};
42
87
  for (const field of ENRICHABLE_FIELDS) {
43
88
  sources[field] = rest[field] !== undefined ? "config" : "default";
44
89
  }
45
90
  const metadata = { sources, looked: false };
46
- if (modelsDev === false || (!needsLookup(rest) && modelsDev === undefined)) {
47
- return { entry: rest, metadata };
48
- }
49
- const idx = index();
50
- if (idx === undefined)
51
- return { entry: rest, metadata };
52
- metadata.looked = true;
53
- const match = idx.match(entry.id, typeof modelsDev === "string" ? modelsDev : undefined);
54
- metadata.match = match;
55
- if (match === undefined)
56
- return { entry: rest, metadata };
57
- const fields = modelsDevFields(match.model);
58
- if (fields.toolCall !== undefined)
59
- metadata.toolCall = fields.toolCall;
60
91
  const out = { ...rest };
61
- for (const field of ENRICHABLE_FIELDS) {
62
- if (out[field] !== undefined)
63
- continue;
64
- const value = fields[field];
65
- if (value === undefined)
66
- continue;
67
- out[field] = structuredClone(value);
68
- sources[field] = "models.dev";
92
+ const hit = catalogHit(entry);
93
+ if (hit !== undefined) {
94
+ metadata.catalog = hit.ref;
95
+ inheritIntrinsic(out, hit, sources);
69
96
  }
70
- if (out.name === undefined && fields.name !== undefined)
71
- out.name = fields.name;
72
- for (const field of METADATA_FIELDS) {
73
- if (out[field] === undefined && fields[field] !== undefined)
74
- out[field] = fields[field];
97
+ const explicit = typeof modelsDev === "string" ? modelsDev : hit?.snapshotRef;
98
+ const lookup = modelsDev !== false && (needsLookup(out) || modelsDev !== undefined);
99
+ const idx = lookup ? index() : undefined;
100
+ if (idx !== undefined) {
101
+ metadata.looked = true;
102
+ // 目录给的快照引用在用户刷新的数据里不存在时,回落按 id 匹配
103
+ const match = idx.match(entry.id, explicit) ??
104
+ (modelsDev === undefined && explicit !== undefined ? idx.match(entry.id) : undefined);
105
+ metadata.match = match;
106
+ if (match !== undefined) {
107
+ const fields = modelsDevFields(match.model);
108
+ if (fields.toolCall !== undefined)
109
+ metadata.toolCall = fields.toolCall;
110
+ for (const field of ENRICHABLE_FIELDS) {
111
+ if (out[field] !== undefined)
112
+ continue;
113
+ // 思考档写在 id 里的中转模型:不让 models.dev 打开思考参数
114
+ if (field === "reasoning" && hit?.tier !== undefined)
115
+ continue;
116
+ const value = fields[field];
117
+ if (value === undefined)
118
+ continue;
119
+ out[field] = structuredClone(value);
120
+ sources[field] = "models.dev";
121
+ }
122
+ if (out.name === undefined && fields.name !== undefined)
123
+ out.name = fields.name;
124
+ for (const field of METADATA_FIELDS) {
125
+ if (out[field] === undefined && fields[field] !== undefined)
126
+ out[field] = fields[field];
127
+ }
128
+ }
75
129
  }
130
+ if (hit !== undefined && modelsDev !== false)
131
+ fillLimits(out, hit, sources);
76
132
  return { entry: out, metadata };
77
133
  }
78
134
  /**
package/dist/ai/sse.d.ts CHANGED
@@ -30,7 +30,8 @@ export declare class SseParser {
30
30
  }
31
31
  /**
32
32
  * 从字节流读 SSE 事件;`signal` 中止时停止读取(调用方据此产出 aborted)。
33
- * `idleTimeoutMs`:等待下一块字节的上限,每收到一块即重新计时(消费者处理事件的时间不计);
34
- * 超时取消底层流并抛 `IdleTimeoutError`(phase `stream`)。
33
+ * `idleTimeoutMs`:等待下一块字节的上限(调用方传 `streamIdleTimeoutOf(options)`,即
34
+ * `request.streamIdleTimeoutMs`,与等响应头的上限分开),每收到一块即重新计时(消费者处理事件的
35
+ * 时间不计);超时取消底层流并抛 `IdleTimeoutError`(phase `stream`)。
35
36
  */
36
37
  export declare function readSseEvents(body: ReadableStream<Uint8Array>, signal?: AbortSignal, idleTimeoutMs?: number): AsyncGenerator<SseEvent>;
package/dist/ai/sse.js CHANGED
@@ -105,8 +105,9 @@ export class SseParser {
105
105
  }
106
106
  /**
107
107
  * 从字节流读 SSE 事件;`signal` 中止时停止读取(调用方据此产出 aborted)。
108
- * `idleTimeoutMs`:等待下一块字节的上限,每收到一块即重新计时(消费者处理事件的时间不计);
109
- * 超时取消底层流并抛 `IdleTimeoutError`(phase `stream`)。
108
+ * `idleTimeoutMs`:等待下一块字节的上限(调用方传 `streamIdleTimeoutOf(options)`,即
109
+ * `request.streamIdleTimeoutMs`,与等响应头的上限分开),每收到一块即重新计时(消费者处理事件的
110
+ * 时间不计);超时取消底层流并抛 `IdleTimeoutError`(phase `stream`)。
110
111
  */
111
112
  export async function* readSseEvents(body, signal, idleTimeoutMs) {
112
113
  const reader = body.getReader();
@@ -86,6 +86,8 @@ export interface ToolCallBlock {
86
86
  arguments: Record<string, unknown>;
87
87
  /** Google thoughtSignature 等。 */
88
88
  thoughtSignature?: string;
89
+ /** 模型输出的原始 arguments 字符串(严格 JSON 可解析时才有);同协议回放时原样发回。 */
90
+ rawArguments?: string;
89
91
  }
90
92
  /** 用户消息与工具结果可携带的块。 */
91
93
  export type ContentBlock = TextBlock | ImageBlock;
@@ -163,6 +165,8 @@ export interface AssistantMessage {
163
165
  providerThinkingLevel?: string;
164
166
  /** 供应商原始 finish / stop reason。 */
165
167
  rawStopReason?: string;
168
+ /** 失败响应的 Retry-After(毫秒);会话层退避取 max(退避, 它)。可选字段,落盘与 RPC 原样透传。 */
169
+ retryAfterMs?: number;
166
170
  timestamp: number;
167
171
  }
168
172
  export interface ToolResultMessage {
@@ -385,10 +389,13 @@ export interface StreamOptions {
385
389
  headers?: Record<string, string | null>;
386
390
  timeoutMs?: number;
387
391
  /**
388
- * 空闲超时:等响应头、以及流中两块数据之间的最长间隔(每收到字节即重新计时)。缺省 300 000,
389
- * 0 关闭。超时报 `idle timeout` 错误,会话层按可重试处理。
392
+ * 等响应头的空闲超时(毫秒)。缺省 300 000,0 关闭。超时报 `idle timeout` 错误,会话层按可重试处理。
390
393
  */
391
394
  idleTimeoutMs?: number;
395
+ /**
396
+ * [ME-C] 流开始后两块数据之间的最长间隔(毫秒,每收到字节即重新计时)。缺省 180 000,0 关闭。
397
+ */
398
+ streamIdleTimeoutMs?: number;
392
399
  maxTokens?: number;
393
400
  temperature?: number;
394
401
  thinkingLevel?: ModelThinkingLevel;