@wudao-org/providers 0.0.0-stage → 0.4.21

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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -2
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/anthropic.d.ts +19 -0
  5. package/dist/anthropic.d.ts.map +1 -0
  6. package/dist/anthropic.js +621 -0
  7. package/dist/anthropic.js.map +1 -0
  8. package/dist/balance.d.ts +28 -0
  9. package/dist/balance.d.ts.map +1 -0
  10. package/dist/balance.js +105 -0
  11. package/dist/balance.js.map +1 -0
  12. package/dist/config.d.ts +328 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +623 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/dispatcher.d.ts +5 -0
  17. package/dist/dispatcher.d.ts.map +1 -0
  18. package/dist/dispatcher.js +21 -0
  19. package/dist/dispatcher.js.map +1 -0
  20. package/dist/effort-catalog.d.ts +17 -0
  21. package/dist/effort-catalog.d.ts.map +1 -0
  22. package/dist/effort-catalog.js +73 -0
  23. package/dist/effort-catalog.js.map +1 -0
  24. package/dist/error-body.d.ts +32 -0
  25. package/dist/error-body.d.ts.map +1 -0
  26. package/dist/error-body.js +74 -0
  27. package/dist/error-body.js.map +1 -0
  28. package/dist/header-validation.d.ts +57 -0
  29. package/dist/header-validation.d.ts.map +1 -0
  30. package/dist/header-validation.js +130 -0
  31. package/dist/header-validation.js.map +1 -0
  32. package/dist/index.d.ts +42 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +110 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/json-arguments.d.ts +17 -0
  37. package/dist/json-arguments.d.ts.map +1 -0
  38. package/dist/json-arguments.js +227 -0
  39. package/dist/json-arguments.js.map +1 -0
  40. package/dist/key-pool.d.ts +66 -0
  41. package/dist/key-pool.d.ts.map +1 -0
  42. package/dist/key-pool.js +207 -0
  43. package/dist/key-pool.js.map +1 -0
  44. package/dist/missing-reasoning-circuit.d.ts +17 -0
  45. package/dist/missing-reasoning-circuit.d.ts.map +1 -0
  46. package/dist/missing-reasoning-circuit.js +38 -0
  47. package/dist/missing-reasoning-circuit.js.map +1 -0
  48. package/dist/model-failure.d.ts +156 -0
  49. package/dist/model-failure.d.ts.map +1 -0
  50. package/dist/model-failure.js +253 -0
  51. package/dist/model-failure.js.map +1 -0
  52. package/dist/model-fallback.d.ts +18 -0
  53. package/dist/model-fallback.d.ts.map +1 -0
  54. package/dist/model-fallback.js +87 -0
  55. package/dist/model-fallback.js.map +1 -0
  56. package/dist/models.d.ts +20 -0
  57. package/dist/models.d.ts.map +1 -0
  58. package/dist/models.js +55 -0
  59. package/dist/models.js.map +1 -0
  60. package/dist/openai-compatible.d.ts +87 -0
  61. package/dist/openai-compatible.d.ts.map +1 -0
  62. package/dist/openai-compatible.js +368 -0
  63. package/dist/openai-compatible.js.map +1 -0
  64. package/dist/openai-engine.d.ts +18 -0
  65. package/dist/openai-engine.d.ts.map +1 -0
  66. package/dist/openai-engine.js +515 -0
  67. package/dist/openai-engine.js.map +1 -0
  68. package/dist/openai-images.d.ts +38 -0
  69. package/dist/openai-images.d.ts.map +1 -0
  70. package/dist/openai-images.js +301 -0
  71. package/dist/openai-images.js.map +1 -0
  72. package/dist/openai-messages.d.ts +40 -0
  73. package/dist/openai-messages.d.ts.map +1 -0
  74. package/dist/openai-messages.js +135 -0
  75. package/dist/openai-messages.js.map +1 -0
  76. package/dist/openai-wire.d.ts +93 -0
  77. package/dist/openai-wire.d.ts.map +1 -0
  78. package/dist/openai-wire.js +151 -0
  79. package/dist/openai-wire.js.map +1 -0
  80. package/dist/persona/apply.d.ts +19 -0
  81. package/dist/persona/apply.d.ts.map +1 -0
  82. package/dist/persona/apply.js +98 -0
  83. package/dist/persona/apply.js.map +1 -0
  84. package/dist/persona/builtins.d.ts +3 -0
  85. package/dist/persona/builtins.d.ts.map +1 -0
  86. package/dist/persona/builtins.js +180 -0
  87. package/dist/persona/builtins.js.map +1 -0
  88. package/dist/persona/decorator.d.ts +4 -0
  89. package/dist/persona/decorator.d.ts.map +1 -0
  90. package/dist/persona/decorator.js +112 -0
  91. package/dist/persona/decorator.js.map +1 -0
  92. package/dist/persona/files.d.ts +20 -0
  93. package/dist/persona/files.d.ts.map +1 -0
  94. package/dist/persona/files.js +84 -0
  95. package/dist/persona/files.js.map +1 -0
  96. package/dist/persona/ids.d.ts +9 -0
  97. package/dist/persona/ids.d.ts.map +1 -0
  98. package/dist/persona/ids.js +11 -0
  99. package/dist/persona/ids.js.map +1 -0
  100. package/dist/persona/registry.d.ts +12 -0
  101. package/dist/persona/registry.d.ts.map +1 -0
  102. package/dist/persona/registry.js +54 -0
  103. package/dist/persona/registry.js.map +1 -0
  104. package/dist/persona/runtime.d.ts +23 -0
  105. package/dist/persona/runtime.d.ts.map +1 -0
  106. package/dist/persona/runtime.js +32 -0
  107. package/dist/persona/runtime.js.map +1 -0
  108. package/dist/persona/schema.d.ts +27 -0
  109. package/dist/persona/schema.d.ts.map +1 -0
  110. package/dist/persona/schema.js +165 -0
  111. package/dist/persona/schema.js.map +1 -0
  112. package/dist/persona/types.d.ts +38 -0
  113. package/dist/persona/types.d.ts.map +1 -0
  114. package/dist/persona/types.js +2 -0
  115. package/dist/persona/types.js.map +1 -0
  116. package/dist/pricing.d.ts +40 -0
  117. package/dist/pricing.d.ts.map +1 -0
  118. package/dist/pricing.js +70 -0
  119. package/dist/pricing.js.map +1 -0
  120. package/dist/quota-park.d.ts +53 -0
  121. package/dist/quota-park.d.ts.map +1 -0
  122. package/dist/quota-park.js +135 -0
  123. package/dist/quota-park.js.map +1 -0
  124. package/dist/rate-pause.d.ts +28 -0
  125. package/dist/rate-pause.d.ts.map +1 -0
  126. package/dist/rate-pause.js +72 -0
  127. package/dist/rate-pause.js.map +1 -0
  128. package/dist/registry.d.ts +142 -0
  129. package/dist/registry.d.ts.map +1 -0
  130. package/dist/registry.js +471 -0
  131. package/dist/registry.js.map +1 -0
  132. package/dist/responses.d.ts +5 -0
  133. package/dist/responses.d.ts.map +1 -0
  134. package/dist/responses.js +290 -0
  135. package/dist/responses.js.map +1 -0
  136. package/dist/retry-after.d.ts +13 -0
  137. package/dist/retry-after.d.ts.map +1 -0
  138. package/dist/retry-after.js +64 -0
  139. package/dist/retry-after.js.map +1 -0
  140. package/dist/retry-ladder.d.ts +176 -0
  141. package/dist/retry-ladder.d.ts.map +1 -0
  142. package/dist/retry-ladder.js +664 -0
  143. package/dist/retry-ladder.js.map +1 -0
  144. package/dist/split-inband-thinking.d.ts +35 -0
  145. package/dist/split-inband-thinking.d.ts.map +1 -0
  146. package/dist/split-inband-thinking.js +117 -0
  147. package/dist/split-inband-thinking.js.map +1 -0
  148. package/dist/stream-idle.d.ts +97 -0
  149. package/dist/stream-idle.d.ts.map +1 -0
  150. package/dist/stream-idle.js +127 -0
  151. package/dist/stream-idle.js.map +1 -0
  152. package/dist/tool-call-ids.d.ts +16 -0
  153. package/dist/tool-call-ids.d.ts.map +1 -0
  154. package/dist/tool-call-ids.js +36 -0
  155. package/dist/tool-call-ids.js.map +1 -0
  156. package/package.json +32 -3
@@ -0,0 +1,70 @@
1
+ // 借鉴弧 B1(SoL-Pi C1)— providers 的价目面:模型价格目录 schema + 缓存
2
+ // 经济学 ratio 的自动推导。
3
+ //
4
+ // 立场(对 SoL-Pi 的 N7 负裁决的正面侧):cacheWriteReadRatio 不做用户配置
5
+ // 项、不手填常量(他们的 12.5 抄 GPT-5.6 定价)。推导链:
6
+ // catalog pin(registry 行的 models[].prices——用户 overlay,builtin 只有
7
+ // 文档核实的数字才钉)> WUDAO_PRICE_<MODEL> env(CLI /cost 的既有约定,
8
+ // "in,out,cacheRead,cacheWrite" per-M-USD)> 保守缺省(core 的
9
+ // DEFAULT_CACHE_WRITE_READ_RATIO,ratioSource:"default" 类型化标注)。
10
+ //
11
+ // ratio 语义:重读每 token 价格 ÷ 缓存命中每 token 价格。重读价取
12
+ // max(input, cacheWrite):OpenAI/Anthropic 语义下 cacheWrite ≥ input(压缩后
13
+ // 重建前缀按 write 计价);DeepSeek 语义下无独立 write 价、重建按 miss=input
14
+ // 计价(usageCostUsd 的 write=0 形)。cacheRead ≤ 0(免费缓存或坏行)推不出
15
+ // 比值——回落下一档,绝不除零。
16
+ import { DEFAULT_CACHE_WRITE_READ_RATIO } from "@wudao-org/core";
17
+ /** registry/目录行的 fail-closed 形状校验:必需面在场且 finite ≥ 0,可选面
18
+ * 在场时同样。坏行拒收(fail-visible),绝不猜成 rate。 */
19
+ export function isModelPrices(v) {
20
+ if (typeof v !== "object" || v === null || Array.isArray(v))
21
+ return false;
22
+ const r = v;
23
+ const nonNeg = (x) => typeof x === "number" && Number.isFinite(x) && x >= 0;
24
+ if (!nonNeg(r.inputPerM) || !nonNeg(r.cacheReadPerM))
25
+ return false;
26
+ if (r.outputPerM !== undefined && !nonNeg(r.outputPerM))
27
+ return false;
28
+ if (r.cacheWritePerM !== undefined && !nonNeg(r.cacheWritePerM))
29
+ return false;
30
+ return true;
31
+ }
32
+ /** 从一档价格推导 ratio。cacheRead > 0 时 = prefill/cacheRead;cacheRead = 0
33
+ * 且 prefill > 0(免费缓存——DeepSeek 2024 真实存在过)时幸存前缀每请求
34
+ * $0:归档节省为 0 而重建仍付费 → 永不经济(Infinity,窗口保护兜底)。
35
+ * 两档推不出(全 0 / NaN)→ undefined(回落下一档)。 */
36
+ function ratioFrom(source) {
37
+ if (source.cacheReadPerM > 0) {
38
+ const r = Math.max(source.inputPerM, source.cacheWritePerM ?? 0) / source.cacheReadPerM;
39
+ return Number.isFinite(r) ? r : undefined;
40
+ }
41
+ const prefill = Math.max(source.inputPerM, source.cacheWritePerM ?? 0);
42
+ return prefill > 0 ? Number.POSITIVE_INFINITY : undefined;
43
+ }
44
+ /** ratio 可用 = 有限非负,或显式 +Infinity(免费缓存的永不经济档)。 */
45
+ const usableRatio = (r) => r !== undefined && (Number.isFinite(r) || r === Number.POSITIVE_INFINITY) && r >= 0;
46
+ /**
47
+ * 推导压缩经济学的定价事实。优先级 catalog > env > 保守缺省;每一档的来源
48
+ * 都类型化在 ratioSource 上("default" = 价目未知,决策面板可见)。
49
+ * 纯函数。
50
+ */
51
+ export function deriveCachePricing(input) {
52
+ const fromCatalog = ratioFrom(input.catalog ?? { inputPerM: 0, cacheReadPerM: 0 });
53
+ if (usableRatio(fromCatalog)) {
54
+ return { cacheWriteReadRatio: fromCatalog, ratioSource: "catalog" };
55
+ }
56
+ const fromEnv = ratioFrom(input.env ?? { inputPerM: 0, cacheReadPerM: 0 });
57
+ if (usableRatio(fromEnv)) {
58
+ return { cacheWriteReadRatio: fromEnv, ratioSource: "env" };
59
+ }
60
+ return { cacheWriteReadRatio: DEFAULT_CACHE_WRITE_READ_RATIO, ratioSource: "default" };
61
+ }
62
+ /** 在一个 effective profile 的目录里按模型名找价格 pin(精确名;目录缺该
63
+ * 模型 = undefined)。纯。 */
64
+ export function catalogPricesFor(models, model) {
65
+ if (models === undefined)
66
+ return undefined;
67
+ const entry = models.find((m) => m.name === model);
68
+ return entry?.prices;
69
+ }
70
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /** The account/window-level usage-cap body vocabulary. Deliberately does NOT
2
+ * include bare "limit" — the context-length family ("maximum context
3
+ * length", "context_limit") shares the word, and those classify one branch
4
+ * earlier at the HTTP boundary (isContextLengthFailure) with disjoint
5
+ * statuses (413/400/500 vs this module's 429); pinning the vocabulary tight
6
+ * keeps the two families disjoint even if the ordering ever moves. */
7
+ export declare const QUOTA_BODY_SIGNATURES: readonly RegExp[];
8
+ /** True when a 429 body carries a known account-quota signature. */
9
+ export declare function sniffQuotaBody(body: string): boolean;
10
+ /** Parse the provider's reset moment out of a 429 body (and, as the last
11
+ * resort, the already-parsed Retry-After header wait). Sources, first match
12
+ * wins: the Chinese absolute wording, the English absolute wording, a body
13
+ * `reset-after` seconds figure, the Retry-After header. Fail-closed:
14
+ * unparseable/past instants and non-positive header waits yield undefined
15
+ * (a past instant is the server's "already reset" — not park material; the
16
+ * ordinary ladder path owns it). */
17
+ export declare function parseQuotaResetAt(body: string, retryAfterMs?: number, now?: number): Date | undefined;
18
+ /** True iff the (status, body, deadline) triple is an account-level quota
19
+ * window past the 60s fail cap — the park candidate. All four gates must
20
+ * hold: HTTP 429, the quota vocabulary, a parseable reset moment, and a wait
21
+ * strictly beyond RETRY_AFTER_FAIL_CAP_MS (exactly-at-cap stays with the
22
+ * ladder — the existing `>` comparison). Everything else keeps today's
23
+ * semantics byte-for-byte. */
24
+ export declare function isQuotaParkFailure(status: number, body: string, retryAfterMs?: number, now?: number): boolean;
25
+ /** The typed account-quota failure (113-B3). Carries the parsed reset moment
26
+ * and the failing leg's provider identity — the CLI driver parks the run
27
+ * until resetAt (+margin) and schedules a one-shot wake. Message keeps a
28
+ * NON-transport prefix (`provider quota reached:` — deliberately not
29
+ * `provider (request|stream) failed: <status>`, so no message-regex consumer
30
+ * can misread it as a retryable status), and the name check in
31
+ * isRetryableProviderError makes the ladder's skip explicit regardless. */
32
+ export declare class QuotaParkError extends Error {
33
+ readonly status: number;
34
+ readonly body: string;
35
+ /** The parsed reset instant (see parseQuotaResetAt). */
36
+ readonly resetAt: Date;
37
+ /** v7 γ4 family: the failing leg's provider identity (registry id, or the
38
+ * baseURL host when the config carries no registry identity). */
39
+ readonly provider: string;
40
+ /** The failing leg's model id (the fallback chain swaps it — identity names
41
+ * the LEG). Omitted when unknown (omit-never-null). */
42
+ readonly model?: string;
43
+ constructor(which: "request" | "stream", status: number, body: string, resetAt: Date, provider: string, model?: string);
44
+ }
45
+ /** Classify at the HTTP boundary: build the typed QuotaParkError when the
46
+ * (status, body, deadline) triple is a park candidate, undefined otherwise
47
+ * (the caller falls through to the generic ProviderHttpError — today's
48
+ * semantics). */
49
+ export declare function tryQuotaParkError(which: "request" | "stream", status: number, body: string, retryAfterMs: number | undefined, identity: {
50
+ readonly provider?: string;
51
+ readonly model?: string;
52
+ }, now?: number): QuotaParkError | undefined;
53
+ //# sourceMappingURL=quota-park.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-park.d.ts","sourceRoot":"","sources":["../src/quota-park.ts"],"names":[],"mappings":"AAqBA;;;;;uEAKuE;AACvE,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EASlD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAoBD;;;;;;qCAMqC;AACrC,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,GAAG,SAAS,CAkBjH;AAED;;;;;+BAK+B;AAC/B,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,EACrB,GAAG,GAAE,MAAmB,GACvB,OAAO,CAKT;AAED;;;;;;4EAM4E;AAC5E,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB;sEACkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;4DACwD;IACxD,SAAiB,KAAK,CAAC,EAAE,MAAM,CAAC;gBAG9B,KAAK,EAAE,SAAS,GAAG,QAAQ,EAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,IAAI,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM;CAajB;AAED;;;kBAGkB;AAClB,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,QAAQ,EAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,QAAQ,EAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EACjE,GAAG,GAAE,MAAmB,GACvB,cAAc,GAAG,SAAS,CAW5B"}
@@ -0,0 +1,135 @@
1
+ // 113-B3(prime-agent #2375 形 + 超越点)— 账户级配额 429 的识别与类型化
2
+ // 停摆错误。
3
+ //
4
+ // 马拉松三次实证的问题形态:glm-5.3 账户级配额 429,错误体自带重置时刻——
5
+ // 429 [1308][已达到 5 小时的使用上限。您的限额将在 2026-09-24 05:30:32 重置。]
6
+ // 在位者(r1a Task 4)的裁决:Retry-After/等待 >60s 帽=硬停直接抛错。小时级
7
+ // 配额窗下这个"硬停"就是"整 run 报废"——基准马拉松里烧穿了两批任务。本模块
8
+ // 只把这一类错从"匿名硬停"升级为带 resetAt 的类型化 QuotaParkError:识别在
9
+ // HTTP 边界(与 ContextLengthError/MandatoryReasoning 同位、在通用
10
+ // ProviderHttpError 之前),梯子不重试它(isRetryableProviderError 的 name
11
+ // 判)、fallback 链不换腿(账户级配额是 provider 域,换模型烧的还是同一账
12
+ // 户),上层的 CLI 驱动据 resetAt 停摆 run 并排一次性唤醒。
13
+ //
14
+ // 语义红线:不改 60s 帽语义。≤60s 的配额等待(Retry-After 短、或重置时刻
15
+ // 30s 后就到)不分类——仍走既有梯子路径(Retry-After 精确等待,429 可重
16
+ // 试);无重置信息的普通 429 不误判——仍是 ProviderHttpError。只有"429 +
17
+ // 配额词表 + 可解析的重置时刻 + 距今 >60s"四条全中才升级。纯函数模块(只
18
+ // 依赖 retry-after 的帽常量,无环),永不触碰请求序列化(前缀缓存字节稳定)。
19
+ import { RETRY_AFTER_FAIL_CAP_MS } from "./retry-after.js";
20
+ /** The account/window-level usage-cap body vocabulary. Deliberately does NOT
21
+ * include bare "limit" — the context-length family ("maximum context
22
+ * length", "context_limit") shares the word, and those classify one branch
23
+ * earlier at the HTTP boundary (isContextLengthFailure) with disjoint
24
+ * statuses (413/400/500 vs this module's 429); pinning the vocabulary tight
25
+ * keeps the two families disjoint even if the ordering ever moves. */
26
+ export const QUOTA_BODY_SIGNATURES = [
27
+ /使用上限/, // glm/bigmodel 1308:已达到 5 小时的使用上限
28
+ /用量上限/,
29
+ /已达到.{0,12}上限/,
30
+ /限额将在/, // …您的限额将在 <时刻> 重置
31
+ /配额/,
32
+ /\bquota\b/i,
33
+ /usage\s+limit/i,
34
+ /rate\s*limit/i,
35
+ ];
36
+ /** True when a 429 body carries a known account-quota signature. */
37
+ export function sniffQuotaBody(body) {
38
+ return QUOTA_BODY_SIGNATURES.some((re) => re.test(body));
39
+ }
40
+ /** The Chinese absolute reset wording: `将在 2026-09-24 05:30:32 重置`(本
41
+ * 地墙钟——服务中文错误体的网关按用户所在时区报时;`new Date("…T…")` 无
42
+ * 时区标记即按本地解析,正是这个语义)。秒可省。 */
43
+ const CN_RESET_RE = /(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}(?::\d{2})?)\s*重置/;
44
+ /** The English absolute wording: `resets at 2026-09-24T05:30:32Z` / `will
45
+ * reset on 2026-09-24 05:30:32`(带时区标记则尊重,缺省同中文款按本地)。 */
46
+ const EN_RESET_RE = /reset(?:s|ting)?\s+(?:at|on)\s+(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}(?::\d{2})?)\s*(Z|UTC|[+-]\d{2}:?\d{2})?/i;
47
+ /** Body-relative reset seconds: `Reset-After: 3600` / `"reset_after": 3600`
48
+ * (JSON 或散文形,纯秒数——分/时单位的散文不猜,trusted-parse-only)。 */
49
+ const RESET_AFTER_BODY_RE = /reset[-_ ]?after["']?\s*[:=]\s*"?(\d{1,8})"?/i;
50
+ function parseWallClock(date, time, zone) {
51
+ const at = Date.parse(zone !== undefined ? `${date}T${time}${zone === "UTC" ? "Z" : zone}` : `${date}T${time}`);
52
+ return Number.isFinite(at) ? new Date(at) : undefined;
53
+ }
54
+ /** Parse the provider's reset moment out of a 429 body (and, as the last
55
+ * resort, the already-parsed Retry-After header wait). Sources, first match
56
+ * wins: the Chinese absolute wording, the English absolute wording, a body
57
+ * `reset-after` seconds figure, the Retry-After header. Fail-closed:
58
+ * unparseable/past instants and non-positive header waits yield undefined
59
+ * (a past instant is the server's "already reset" — not park material; the
60
+ * ordinary ladder path owns it). */
61
+ export function parseQuotaResetAt(body, retryAfterMs, now = Date.now()) {
62
+ const cn = CN_RESET_RE.exec(body);
63
+ if (cn !== null) {
64
+ const at = parseWallClock(cn[1], cn[2], undefined);
65
+ if (at !== undefined && at.getTime() > now)
66
+ return at;
67
+ }
68
+ const en = EN_RESET_RE.exec(body);
69
+ if (en !== null) {
70
+ const at = parseWallClock(en[1], en[2], en[3]);
71
+ if (at !== undefined && at.getTime() > now)
72
+ return at;
73
+ }
74
+ const rel = RESET_AFTER_BODY_RE.exec(body);
75
+ if (rel !== null) {
76
+ const seconds = Number(rel[1]);
77
+ if (Number.isFinite(seconds) && seconds > 0)
78
+ return new Date(now + seconds * 1000);
79
+ }
80
+ if (retryAfterMs !== undefined && retryAfterMs > 0)
81
+ return new Date(now + retryAfterMs);
82
+ return undefined;
83
+ }
84
+ /** True iff the (status, body, deadline) triple is an account-level quota
85
+ * window past the 60s fail cap — the park candidate. All four gates must
86
+ * hold: HTTP 429, the quota vocabulary, a parseable reset moment, and a wait
87
+ * strictly beyond RETRY_AFTER_FAIL_CAP_MS (exactly-at-cap stays with the
88
+ * ladder — the existing `>` comparison). Everything else keeps today's
89
+ * semantics byte-for-byte. */
90
+ export function isQuotaParkFailure(status, body, retryAfterMs, now = Date.now()) {
91
+ if (status !== 429)
92
+ return false;
93
+ if (!sniffQuotaBody(body))
94
+ return false;
95
+ const resetAt = parseQuotaResetAt(body, retryAfterMs, now);
96
+ return resetAt !== undefined && resetAt.getTime() - now > RETRY_AFTER_FAIL_CAP_MS;
97
+ }
98
+ /** The typed account-quota failure (113-B3). Carries the parsed reset moment
99
+ * and the failing leg's provider identity — the CLI driver parks the run
100
+ * until resetAt (+margin) and schedules a one-shot wake. Message keeps a
101
+ * NON-transport prefix (`provider quota reached:` — deliberately not
102
+ * `provider (request|stream) failed: <status>`, so no message-regex consumer
103
+ * can misread it as a retryable status), and the name check in
104
+ * isRetryableProviderError makes the ladder's skip explicit regardless. */
105
+ export class QuotaParkError extends Error {
106
+ status;
107
+ body;
108
+ /** The parsed reset instant (see parseQuotaResetAt). */
109
+ resetAt;
110
+ /** v7 γ4 family: the failing leg's provider identity (registry id, or the
111
+ * baseURL host when the config carries no registry identity). */
112
+ provider;
113
+ constructor(which, status, body, resetAt, provider, model) {
114
+ const tail = model !== undefined && model !== "" ? `/${model}` : "";
115
+ super(`provider quota reached: ${status} ${body} — account usage cap, window resets at ${resetAt.toISOString()} (provider ${provider}${tail}); run parked until reset`);
116
+ this.name = "QuotaParkError";
117
+ this.status = status;
118
+ this.body = body;
119
+ this.resetAt = resetAt;
120
+ this.provider = provider;
121
+ if (model !== undefined && model !== "")
122
+ this.model = model;
123
+ }
124
+ }
125
+ /** Classify at the HTTP boundary: build the typed QuotaParkError when the
126
+ * (status, body, deadline) triple is a park candidate, undefined otherwise
127
+ * (the caller falls through to the generic ProviderHttpError — today's
128
+ * semantics). */
129
+ export function tryQuotaParkError(which, status, body, retryAfterMs, identity, now = Date.now()) {
130
+ if (!isQuotaParkFailure(status, body, retryAfterMs, now))
131
+ return undefined;
132
+ const resetAt = parseQuotaResetAt(body, retryAfterMs, now);
133
+ return new QuotaParkError(which, status, body, resetAt, identity.provider !== undefined && identity.provider !== "" ? identity.provider : "unknown", identity.model);
134
+ }
135
+ //# sourceMappingURL=quota-park.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-park.js","sourceRoot":"","sources":["../src/quota-park.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /** Slice ceiling for the sliced re-check loop (CodeWhale C1's 250ms). */
2
+ export declare const RATE_PAUSE_SLICE_MS = 250;
3
+ /** Default pause when a 429 carries no Retry-After — the key pool's rate
4
+ * class cooldown (60s), so the provider dimension and the key dimension
5
+ * agree on how long a rate signal lasts. */
6
+ export declare const DEFAULT_RATE_PAUSE_MS = 60000;
7
+ export interface RatePauseWindow {
8
+ /** Wait out any active pause in ≤250ms slices; an abort signal breaks the
9
+ * loop with AbortError (the ladder never retries aborts — the caller's
10
+ * cancel survives the pause). No-op when idle. */
11
+ wait(signal?: AbortSignal): Promise<void>;
12
+ /** Extend the pause deadline (never shorten; clamped to the trust cap). */
13
+ trip(pauseMs: number): void;
14
+ /** A successful response proves the pause stale — reset to idle. */
15
+ clear(): void;
16
+ /** Current deadline in epoch ms; 0 when idle (assertions/observability). */
17
+ until(): number;
18
+ }
19
+ export interface RatePauseOptions {
20
+ /** Injectable clock (tests); default Date.now. */
21
+ readonly now?: () => number;
22
+ /** Injectable sleep (tests); default setTimeout-based. */
23
+ readonly sleep?: (ms: number) => Promise<void>;
24
+ /** Slice ceiling for wait(); default RATE_PAUSE_SLICE_MS. */
25
+ readonly sliceMs?: number;
26
+ }
27
+ export declare function createRatePauseWindow(options?: RatePauseOptions): RatePauseWindow;
28
+ //# sourceMappingURL=rate-pause.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-pause.d.ts","sourceRoot":"","sources":["../src/rate-pause.ts"],"names":[],"mappings":"AAgCA,yEAAyE;AACzE,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;6CAE6C;AAC7C,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B;;uDAEmD;IACnD,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,2EAA2E;IAC3E,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oEAAoE;IACpE,KAAK,IAAI,IAAI,CAAC;IACd,4EAA4E;IAC5E,KAAK,IAAI,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,gBAAqB,GAAG,eAAe,CA+BrF"}
@@ -0,0 +1,72 @@
1
+ // 波次 7 #37 (CodeWhale C1): the provider-level 429 pause window.
2
+ //
3
+ // The M56 key pool already handles the KEY dimension of a 429 — the limited
4
+ // key cools for every request through the shared pool. What it cannot handle
5
+ // is the PROVIDER dimension under concurrency: with a single key (or every
6
+ // key cooling), each concurrent request still enters its OWN retry ladder and
7
+ // hammers a provider that just said "stop" — under subagent fanout, N
8
+ // requests burn N ladders against the same rate limit (the self-harm CodeWhale
9
+ //'s C1 exists for). The window closes that gap:
10
+ //
11
+ // trip() — called at the HTTP boundary the moment a 429 arrives while the
12
+ // key pool has no healthy spare to rotate to (single-key, or all
13
+ // cooling). Deadline = the response's Retry-After when present,
14
+ // else the 60s rate-class default; never above the ladder's
15
+ // Retry-After trust cap, and never SHORTER than an existing
16
+ // deadline.
17
+ // wait() — the leg entry gate, before every generate/stream call. While a
18
+ // pause is active the caller re-checks the clock in ≤250ms
19
+ // slices (CodeWhale's sliced re-check) and fires its first HTTP
20
+ // attempt only after the window lapses. The originator's own
21
+ // ladder retries are NOT gated (they sit INSIDE one leg call): a
22
+ // transient 429 still recovers on its normal backoff — today's
23
+ // behavior, byte-for-byte.
24
+ // clear() — any 2xx proves the pause stale (the blip recovered); the next
25
+ // concurrent request enters immediately.
26
+ //
27
+ // The window is created once per provider factory closure (keyPool topology)
28
+ // and injectable whole through ProviderOptions.ratePauseWindow — tests drive
29
+ // it with a fake clock/sleep pair, embedders can share one window across
30
+ // protocol factories behind a single endpoint.
31
+ import { RETRY_AFTER_FAIL_CAP_MS } from "./retry-after.js";
32
+ /** Slice ceiling for the sliced re-check loop (CodeWhale C1's 250ms). */
33
+ export const RATE_PAUSE_SLICE_MS = 250;
34
+ /** Default pause when a 429 carries no Retry-After — the key pool's rate
35
+ * class cooldown (60s), so the provider dimension and the key dimension
36
+ * agree on how long a rate signal lasts. */
37
+ export const DEFAULT_RATE_PAUSE_MS = 60_000;
38
+ function defaultSleep(ms) {
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
40
+ }
41
+ export function createRatePauseWindow(options = {}) {
42
+ const now = options.now ?? Date.now;
43
+ const sleep = options.sleep ?? defaultSleep;
44
+ const sliceMs = options.sliceMs ?? RATE_PAUSE_SLICE_MS;
45
+ let deadline = 0;
46
+ const aborted = (signal) => {
47
+ const err = new Error("aborted while waiting out the provider rate pause");
48
+ err.name = "AbortError";
49
+ return err;
50
+ };
51
+ return {
52
+ async wait(signal) {
53
+ while (deadline > now()) {
54
+ if (signal?.aborted === true)
55
+ throw aborted(signal);
56
+ const remaining = deadline - now();
57
+ await sleep(Math.min(sliceMs, remaining));
58
+ }
59
+ },
60
+ trip(pauseMs) {
61
+ const clamped = Math.max(0, Math.min(pauseMs, RETRY_AFTER_FAIL_CAP_MS));
62
+ deadline = Math.max(deadline, now() + clamped);
63
+ },
64
+ clear() {
65
+ deadline = 0;
66
+ },
67
+ until() {
68
+ return deadline;
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=rate-pause.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-pause.js","sourceRoot":"","sources":["../src/rate-pause.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,sEAAsE;AACtE,+EAA+E;AAC/E,gDAAgD;AAChD,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,yEAAyE;AACzE,yBAAyB;AACzB,8EAA8E;AAC9E,wEAAwE;AACxE,6EAA6E;AAC7E,0EAA0E;AAC1E,8EAA8E;AAC9E,4EAA4E;AAC5E,wCAAwC;AACxC,6EAA6E;AAC7E,sDAAsD;AACtD,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,yEAAyE;AACzE,+CAA+C;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,yEAAyE;AACzE,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEvC;;6CAE6C;AAC7C,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAwB5C,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAA4B,EAAE;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,mBAAmB,CAAC;IACvD,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,MAAM,OAAO,GAAG,CAAC,MAAmB,EAAS,EAAE;QAC7C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC3E,GAAG,CAAC,IAAI,GAAG,YAAY,CAAC;QACxB,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,MAAoB;YAC7B,OAAO,QAAQ,GAAG,GAAG,EAAE,EAAE,CAAC;gBACxB,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI;oBAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBACpD,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC;gBACnC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAe;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC;YACxE,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;QACjD,CAAC;QACD,KAAK;YACH,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC;QACD,KAAK;YACH,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,142 @@
1
+ import type { ProviderProfile, ReasoningEffort, ReasoningFormat, WindowMode } from "./config.js";
2
+ import type { ProtocolId } from "./persona/types.js";
3
+ import { type ModelPrices } from "./pricing.js";
4
+ /** One catalog entry: a model name plus its optional context window and
5
+ * (借鉴弧 B1) its optional doc-verified prices — the catalog face the cache
6
+ * economics ratio derives from. A pin exists ONLY where the number comes
7
+ * from the provider's own docs (same policy as contextLength). */
8
+ export interface ProviderModelEntry {
9
+ readonly name: string;
10
+ readonly contextLength?: number;
11
+ readonly prices?: ModelPrices;
12
+ }
13
+ /** The user-facing slice of a profile (registry file shape). Every field is
14
+ * optional; unknown fields are ignored for forward compatibility. */
15
+ export interface UserProviderConfig {
16
+ readonly label?: string;
17
+ readonly baseUrl?: string;
18
+ readonly defaultModel?: string;
19
+ readonly reasoningFormat?: ReasoningFormat;
20
+ readonly windowMode?: WindowMode;
21
+ readonly defaultContextLength?: number;
22
+ readonly models?: readonly ProviderModelEntry[];
23
+ readonly thinkingLevelMap?: ProviderProfile["thinkingLevelMap"];
24
+ readonly supportsReasoningEffort?: boolean;
25
+ readonly acceptsReasoningOnlyFinish?: boolean;
26
+ readonly requiresBaseUrl?: boolean;
27
+ /** SDD 2026-08-23 persona Task 4 — the per-provider persona binding, a
28
+ * THREE-STATE column (spec §2): key absent = follow the global default;
29
+ * a non-empty string = bound persona id; explicit null = forced none.
30
+ * Deliberately NOT part of EffectiveProfile — it is binding-chain data
31
+ * (read by the CLI's readProviderPersonaBinding), never a profile fact,
32
+ * so the effectiveProfiles merge never touches it. */
33
+ readonly persona?: string | null;
34
+ /** SDD 2026-08-24 reg-headers Task 1 (92 §3.4-1); ω99-64 on the wire —
35
+ * per-provider extra request headers (the enterprise-gateway baseline).
36
+ * CONTROLLER RULING (ω99-64): the old "deliberately never an
37
+ * EffectiveProfile fact" stance is REVERSED. Unlike `persona` (binding-
38
+ * chain data with its own lifecycle, still profile-external), headers are
39
+ * a TRANSPORT-LAYER fact of the endpoint — the same class as baseUrl /
40
+ * wire / balanceUrl (gateway-fronting facts). They fold through the
41
+ * effectiveProfiles overlay merge (overlay-beats-builtin for free) and
42
+ * ride to request construction as ProviderConfig.extraHeaders, spread
43
+ * behind adapter auth and ahead of persona wire.headers. FORBIDDEN keys
44
+ * (auth/transport-control, single source persona/schema) refuse the row
45
+ * at save AND load — a registry row can structurally never override the
46
+ * adapter's credentials or transport headers. */
47
+ readonly headers?: Readonly<Record<string, string>>;
48
+ /** 0.3.13 (#215, user trial feedback): the PROTOCOL this row speaks —
49
+ * "openai-chat" | "anthropic" | "responses". Absent = the dispatch
50
+ * default (openai-chat), byte-identical to the historical shape. Unlike
51
+ * persona/headers this IS a profile fact: the effectiveProfiles merge
52
+ * folds it into EffectiveProfile.wire, where resolveProviderConfigResult
53
+ * already consults it (selection.wire > env > profile.wire) — so a
54
+ * custom Anthropic-protocol provider needs NO WUDAO_WIRE once saved. */
55
+ readonly wire?: ProtocolId;
56
+ /** P0-4 (96 队列): the row's wallet-balance endpoint. A profile fact like
57
+ * wire: an explicit overlay value beats the builtin's compiled-in pin,
58
+ * and a custom's own value rides through — the /cost all wallet face
59
+ * resolves it from the effective profile view. */
60
+ readonly balanceUrl?: string;
61
+ /** 2026-08-28 推理档三级配置 S1 — the per-provider reasoning-effort
62
+ * binding, a THREE-STATE column mirroring `persona`: key absent = follow
63
+ * the global default (model.defaultEffort); a string = bound level (a
64
+ * REASONING_EFFORTS member); explicit null = don't send the param at all
65
+ * (distinct from the literal "none" level, which IS a wire value). Like
66
+ * persona, deliberately never an EffectiveProfile fact — the CLI's
67
+ * effort resolution chain reads it (readProviderEffortBinding). */
68
+ readonly effort?: string | null;
69
+ /** ω99-245: #5 — the capability-catalog OVERRIDE column: what THIS
70
+ * endpoint accepts, wholesale-replacing the compiled-in family set (and
71
+ * its model pins) in supportedEfforts. The user's escape hatch for a new
72
+ * model or an outdated catalog pin — fail-closed but never bricked. A
73
+ * non-empty array of REASONING_EFFORTS members (validated fail-visibly:
74
+ * junk must never silently widen the gate). Unlike `effort` above this IS
75
+ * an EffectiveProfile fact (like headers/wire), read through the
76
+ * effective view. */
77
+ readonly efforts?: readonly ReasoningEffort[];
78
+ }
79
+ /** The registry file: version-gated from v1, malformed files load as empty. */
80
+ export interface ProviderRegistryFile {
81
+ readonly version: 1;
82
+ readonly providers: Record<string, UserProviderConfig>;
83
+ }
84
+ /** A load outcome: the entries that validated, plus one row per entry that
85
+ * did not (fail-visible — the UI lists them; boot never blocks on them). */
86
+ export interface RegistryLoadResult {
87
+ readonly entries: ReadonlyMap<string, UserProviderConfig>;
88
+ readonly invalid: readonly {
89
+ id: string;
90
+ reason: string;
91
+ }[];
92
+ }
93
+ /** A ProviderProfile extended with the registry-only facts: the per-model
94
+ * catalog (empty for builtins unless an overlay supplies one — the builtin
95
+ * defaultModel never joins it), the provider-level context-window default,
96
+ * whether the id is user-registered (custom) or compiled in, and (ω99-245)
97
+ * the optional capability-catalog override the effort preflight reads. */
98
+ export type EffectiveProfile = ProviderProfile & {
99
+ readonly models: readonly ProviderModelEntry[];
100
+ readonly defaultContextLength?: number;
101
+ readonly custom: boolean;
102
+ /** The supported-efforts override (see UserProviderConfig.efforts).
103
+ * Absent on every builtin; present exactly when a registry row declares it. */
104
+ readonly efforts?: readonly ReasoningEffort[];
105
+ };
106
+ /** Where the registry lives. Mirrors the CLI's global-home resolution so
107
+ * tests/CI isolate the whole ~/.wudao tier with one env var. */
108
+ export declare function defaultProviderRegistryPath(env?: NodeJS.ProcessEnv): string;
109
+ /** Validate one registry entry. Returns the SANITIZED config (unknown fields
110
+ * dropped) or the reason it was refused. A builtin id may overlay without a
111
+ * baseUrl; a custom id must carry one — it IS the provider. */
112
+ export declare function validateUserProviderEntry(id: string, cfg: unknown): {
113
+ ok: true;
114
+ value: UserProviderConfig;
115
+ } | {
116
+ ok: false;
117
+ reason: string;
118
+ };
119
+ /** Read + validate the registry. Fault-tolerant by contract: a missing file
120
+ * is an empty registry; a malformed file (bad JSON, wrong version, wrong
121
+ * shape) demotes to empty entries with ONE file-level invalid row; a bad
122
+ * ENTRY costs only that entry. Never throws. */
123
+ export declare function readProviderRegistrySync(path?: string): RegistryLoadResult;
124
+ /** Atomically write the registry (tmp + rename, 0600 — same shape as the
125
+ * global settings writer). Creates the parent directory when missing. */
126
+ export declare function writeProviderRegistrySync(path: string, file: ProviderRegistryFile): void;
127
+ /** Merge validated user entries onto the compiled-in profiles:
128
+ * - builtin ids always present; an entry for one overlays field-by-field
129
+ * (`overlay ?? base`, the FULL profile surface — reasoning fields included);
130
+ * - other ids become customs (requiresBaseUrl pinned true; the entry's
131
+ * baseUrl becomes defaultBaseUrl; reasoningFormat defaults to "none").
132
+ * `models` on an overlay REPLACES the builtin catalog wholesale (the
133
+ * builtins' own doc-verified context pins included); the builtin
134
+ * defaultModel never joins it. A custom entry without a baseUrl
135
+ * is defensively skipped — read-time validation is what reports it. */
136
+ export declare function effectiveProfiles(userEntries: ReadonlyMap<string, UserProviderConfig>): ReadonlyMap<string, EffectiveProfile>;
137
+ /** The context-window resolution chain (§7): the model's registered
138
+ * contextLength, else the provider's default, else the caller-supplied env
139
+ * value (already resolved: WUDAO_CONTEXT_WINDOW ?? 200_000). Pure — no fs, no
140
+ * env reads of its own. */
141
+ export declare function resolveContextWindowTokens(profiles: ReadonlyMap<string, EffectiveProfile>, providerId: string, model: string, envValue: number): number;
142
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}