@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
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ // @wudao-org/providers — concrete ModelClient adapters. Depends on @wudao-org/core (the port).
2
+ export { createOpenAICompatibleProvider, serializePrompt, withRetry, withRetryStream, isRetryableProviderError, backoffDelayMs, defaultMaxRetries,
3
+ // M47 §7: overload classification + branch.
4
+ ProviderHttpError, OVERLOAD_BODY_SIGNATURES, sniffOverloadBody, isOverloadError, overloadBackoffDelayMs,
5
+ // M58 §A: context-overflow classification (413 reactive layer).
6
+ ContextLengthError, CONTEXT_LENGTH_BODY_SIGNATURES,
7
+ // 120-b B2: the GLM/vLLM strict spelling (classify + trusted-parse, N>M).
8
+ CONTEXT_LIMIT_INPUT_LENGTH_RE, matchContextLimitInputLength, sniffContextLengthBody, isContextLengthFailure,
9
+ // M58 §B / ω99-315: the dual-tier stream idle watchdog (soft warn / hard
10
+ // abort; the shared guard, budget resolver, and constants live in
11
+ // stream-idle.ts and are re-exported through openai-compatible.ts).
12
+ StreamIdleError, DEFAULT_STREAM_IDLE_TIMEOUT_MS, MAX_STREAM_IDLE_TIMEOUT_MS, defaultStreamIdleTimeoutMs, DEFAULT_STREAM_IDLE_SOFT_MS, DEFAULT_STREAM_IDLE_HARD_MS, resolveStreamIdleBudget,
13
+ // r1b: the SSE line-buffer overflow cap (same ladder family as StreamIdleError).
14
+ StreamOverflowError,
15
+ // ω99-44: the typed EOF-without-terminal abort (same ladder family — a clean
16
+ // EOF that never spoke finish/stop semantics is a cut, never a completion).
17
+ TruncatedStreamError,
18
+ // M70 §3: the foreground-only retry allowlist (background overload = bail).
19
+ FOREGROUND_QUERY_SOURCES, isOverloadClassFailure,
20
+ // 124 项 2: network-outage grace window classifier + default knobs.
21
+ isNetworkClassFailure, DEFAULT_NETWORK_GRACE_RETRY_MULTIPLIER, DEFAULT_NETWORK_GRACE_MAX_DELAY_MS, BackgroundOverloadError,
22
+ // M70 §4: max_tokens overflow self-repair (parseable 400 → retry once).
23
+ parseMaxTokensOverflow, selfRepairMaxTokensValue,
24
+ // ω-2b (I1-a): the trusted context-limit body parse (classified overflow
25
+ // bodies → the observed {promptTokens, windowTokens} pair).
26
+ parseContextLimitBody,
27
+ // M48 §2: terminal-shape repair (one frozen replay per degenerate finish).
28
+ terminalRepairKind,
29
+ // ω99-42: the typed mandatory-reasoning rejection (hermes f6bd1633f7 +
30
+ // 63279301bc) — matcher + signatures alongside the class.
31
+ ReasoningMandatoryError, isMandatoryReasoningRejection, MANDATORY_REASONING_BODY_SIGNATURES,
32
+ // M79 §2: the pre-I/O image capability/role gate + the tool-image note.
33
+ assertImagesSupported, TOOL_IMAGE_NOTE,
34
+ // 113-B3: the account-quota park domain — identification, the trusted
35
+ // reset parse, and the typed QuotaParkError (the CLI parks on it).
36
+ QUOTA_BODY_SIGNATURES, sniffQuotaBody, parseQuotaResetAt, isQuotaParkFailure, tryQuotaParkError, QuotaParkError, } from "./openai-compatible.js";
37
+ // Persona adapters (Task 4 / Task 9)
38
+ export { createAnthropicProvider } from "./anthropic.js";
39
+ export { createResponsesProvider } from "./responses.js";
40
+ // r1a Task 3 (M62 §7): the ordered model-fallback chain, extracted from the
41
+ // openai factory and shared by all three protocol factories.
42
+ export { withModelFallback } from "./model-fallback.js";
43
+ // wire-r2 follow-up ①: typed fail-closed conflict — the anthropic adapter
44
+ // throws it when an explicit maxTokens (input or config) sits at/below the
45
+ // thinking budget implied by reasoningEffort; the adapter's implicit default
46
+ // cap keeps the historic budget+1024 raise.
47
+ export { MaxTokensThinkingBudgetConflictError } from "./model-failure.js";
48
+ // r1a Task 4: 429 Retry-After parsing — the retryAfterMs source the shared
49
+ // ladder honors (deadline wait within the 60s fail cap).
50
+ export { parseRetryAfterMs, RETRY_AFTER_FAIL_CAP_MS } from "./retry-after.js";
51
+ // 波次 8 #42 (hermes #24): bounded error-body read — byte cap + wall clock +
52
+ // cancel-on-abandon, so a stalled or huge error body can neither hang the
53
+ // request nor buffer unbounded.
54
+ export { readErrorBody, ERROR_BODY_MAX_BYTES, ERROR_BODY_DEADLINE_MS } from "./error-body.js";
55
+ // Persona Phase B (design 93 §4.2-2): the single construction seam — dispatches
56
+ // on config.wire to the protocol adapter (absent = openai-compatible engine).
57
+ export { createProvider } from "./dispatcher.js";
58
+ // M79 §3 — per-model input-modality metadata (catalog + resolution).
59
+ export { modelInputModalities, resolveInputModalities, modelAcceptsImages, } from "./models.js";
60
+ export { PROVIDER_PROFILES, REASONING_EFFORTS, REASONING_FORMATS, REASONING_REPLAYS, NO_KEY_REASON, parseApiKeyPool, isProviderName,
61
+ // Provider management R2: the closed builtin table narrows separately from
62
+ // the widened ProviderName alias.
63
+ isBuiltinProviderName, parseReasoningEffort, parseReasoningFormat, parseReasoningReplay,
64
+ // ω99-39: the probe-verified replay cap — shared by the resolution seam
65
+ // and the CLI's provider-switch carry lane (one rule, both config faces).
66
+ capClientReasoningReplay, parseWire, reasoningFormatBody,
67
+ // ω99-42: the repair body a mandatory-reasoning rejection re-sends with
68
+ // (the matrix's explicit enable, map-stripped, medium default level).
69
+ mandatoryRepairReasoningBody, reasoningResponseFields, pickReasoning, resolveReasoningContext, supportsReasoningEffort, resolveProviderConfig, resolveProviderConfigResult,
70
+ // M48 §2: the reasoning-only repair gate (profile-resolved).
71
+ acceptsReasoningOnlyFinish,
72
+ // 夜移 022: the tool-turn reasoning-omission fact (profile-resolved).
73
+ omitsReasoningOnToolTurns,
74
+ // 取证弧 0921: the output-limit ladder's escalation tier (profile-resolved).
75
+ outputLimitEscalateFor,
76
+ // ω-2b (I1-c): the profile's window semantics (gates the admission clip).
77
+ resolveWindowMode, } from "./config.js";
78
+ // 夜移 011: the in-band <think> peel (vLLM chat-template lanes inline CoT
79
+ // into content; the splitter routes a turn-initial block into reasoning).
80
+ export { InbandThinkSplitter, resolveSplitInbandThinking } from "./split-inband-thinking.js";
81
+ // Provider management §3/§7 — the runtime provider registry.
82
+ export { defaultProviderRegistryPath, readProviderRegistrySync, writeProviderRegistrySync, effectiveProfiles, validateUserProviderEntry, resolveContextWindowTokens, } from "./registry.js";
83
+ // 借鉴弧 B1(SoL-Pi C1)— 价目面:目录 schema + 缓存经济学 ratio 的自动推导
84
+ //(catalog pin > WUDAO_PRICE env > 保守缺省,来源类型化;不暴露为用户配置)。
85
+ export { deriveCachePricing, isModelPrices, catalogPricesFor } from "./pricing.js";
86
+ // ω99-245: #6 — the reasoning capability catalog (data layer of the effort
87
+ // preflight): family/model → supported effort set + reasoning_content flag.
88
+ export { supportedEfforts, reasoningContentStreams, MODEL_EFFORT_PINS, } from "./effort-catalog.js";
89
+ // P0-4 (96 队列) — the wallet-balance face (DeepSeek GET /user/balance;
90
+ // unconfigured → undefined, never an error; amounts stay the API's strings).
91
+ export { fetchProviderBalance, BALANCE_TIMEOUT_MS } from "./balance.js";
92
+ // M56 §1: the multi-key pool (round-robin + per-key 429 cooldown).
93
+ // createLiveKeyPool (2026-08-31 minimax-401 弧): the config-deriving twin
94
+ // the session engine wires over its mutable config holder.
95
+ export { createKeyPool, createLiveKeyPool, keyPoolFromEnv, cooldownClassForStatus, DEFAULT_KEY_COOLDOWN_MS, DEFAULT_AUTH_COOLDOWN_MS, } from "./key-pool.js";
96
+ // 波次 7 #37 (CodeWhale C1):provider 级 429 暂停窗 —— 三协议工厂同享、可注入。
97
+ export { createRatePauseWindow, DEFAULT_RATE_PAUSE_MS, RATE_PAUSE_SLICE_MS } from "./rate-pause.js";
98
+ // Persona layer exports (Task 5 / Task 6 / Task 7 / Task 8 / Task 9 / Task 11)
99
+ export { validatePersonaDefinition, effectiveLayers, BODY_WHITELIST, PROTOCOL_IDS } from "./persona/schema.js";
100
+ export { loadPersonaRegistry, lookupPersona } from "./persona/registry.js";
101
+ // SDD 2026-08-23 persona Task 5 — the CRUD file helpers (spec §7: same
102
+ // package as loadPersonaRegistry). The cli persona ops call these against
103
+ // the global personas dir; savePersonaDefinition is the ONE write path.
104
+ export { savePersonaDefinition, deletePersonaDefinition, personaIdsInDir } from "./persona/files.js";
105
+ export { BUILTIN_PERSONAS } from "./persona/builtins.js";
106
+ export { createPersonaRuntime, resolvePersona, parsePersonaSpec } from "./persona/runtime.js";
107
+ export { applyWirePersona, mergeBodyOverlay, resolveTemplate, resolvePersonaHeaders } from "./persona/apply.js";
108
+ export { withPersona } from "./persona/decorator.js";
109
+ export const PACKAGE_NAME = "@wudao-org/providers";
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,OAAO,EACL,8BAA8B,EAC9B,eAAe,EACf,SAAS,EACT,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,iBAAiB;AACjB,4CAA4C;AAC5C,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,eAAe,EACf,sBAAsB;AACtB,gEAAgE;AAChE,kBAAkB,EAClB,8BAA8B;AAC9B,0EAA0E;AAC1E,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,EACtB,sBAAsB;AACtB,yEAAyE;AACzE,mEAAmE;AACnE,qEAAqE;AACrE,eAAe,EACf,8BAA8B,EAC9B,0BAA0B,EAC1B,0BAA0B,EAC1B,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB;AAIvB,iFAAiF;AACjF,mBAAmB;AACnB,6EAA6E;AAC7E,4EAA4E;AAC5E,oBAAoB;AACpB,4EAA4E;AAC5E,wBAAwB,EACxB,sBAAsB;AACtB,mEAAmE;AACnE,qBAAqB,EACrB,sCAAsC,EACtC,kCAAkC,EAClC,uBAAuB;AACvB,wEAAwE;AACxE,sBAAsB,EACtB,wBAAwB;AACxB,yEAAyE;AACzE,4DAA4D;AAC5D,qBAAqB;AACrB,2EAA2E;AAC3E,kBAAkB;AAClB,uEAAuE;AACvE,0DAA0D;AAC1D,uBAAuB,EACvB,6BAA6B,EAC7B,mCAAmC;AACnC,wEAAwE;AACxE,qBAAqB,EACrB,eAAe;AACf,sEAAsE;AACtE,mEAAmE;AACnE,qBAAqB,EACrB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,qCAAqC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,4EAA4E;AAC5E,6DAA6D;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,0EAA0E;AAC1E,2EAA2E;AAC3E,6EAA6E;AAC7E,4CAA4C;AAC5C,OAAO,EAAE,oCAAoC,EAAE,MAAM,oBAAoB,CAAC;AAC1E,2EAA2E;AAC3E,yDAAyD;AACzD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE9E,2EAA2E;AAC3E,0EAA0E;AAC1E,gCAAgC;AAChC,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAE9F,gFAAgF;AAChF,8EAA8E;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,qEAAqE;AACrE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,cAAc;AACd,2EAA2E;AAC3E,kCAAkC;AAClC,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB;AACpB,wEAAwE;AACxE,0EAA0E;AAC1E,wBAAwB,EACxB,SAAS,EACT,mBAAmB;AACnB,wEAAwE;AACxE,sEAAsE;AACtE,4BAA4B,EAC5B,uBAAuB,EACvB,aAAa,EACb,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,2BAA2B;AAC3B,6DAA6D;AAC7D,0BAA0B;AAC1B,oEAAoE;AACpE,yBAAyB;AACzB,0EAA0E;AAC1E,sBAAsB;AACtB,0EAA0E;AAC1E,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,wEAAwE;AACxE,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAiB7F,6DAA6D;AAC7D,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAQvB,uDAAuD;AACvD,wDAAwD;AACxD,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEnF,2EAA2E;AAC3E,4EAA4E;AAC5E,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,sEAAsE;AACtE,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAExE,mEAAmE;AACnE,yEAAyE;AACzE,2DAA2D;AAC3D,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAEvB,6DAA6D;AAC7D,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpG,+EAA+E;AAC/E,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3E,uEAAuE;AACvE,0EAA0E;AAC1E,wEAAwE;AACxE,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhH,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAYrD,MAAM,CAAC,MAAM,YAAY,GAAG,sBAAsB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /** 波次 7 #33 (CodeWhale parse_json_with_repair + truncation salvage): the
2
+ * deterministic repair ladder for streamed tool-call arguments. Torn SSE
3
+ * fragments, literal control chars from local backends, and the 2M-cap
4
+ * marker all poison the accumulated JSON — the old catch{} returned {}
5
+ * SILENTLY (the tool ran with no arguments, or died on a misleading
6
+ * "missing required field" validation error). Ladder, cheapest first:
7
+ * 1. strict parse (zero-change fast path)
8
+ * 2. ```json fence strip (CodeWhale: only when BOTH fence ends present)
9
+ * 3. first balanced object payload in prose (CodeWhale, ≤64 starts)
10
+ * 4. escape literal C0 controls inside strings (wudao extension — the
11
+ * local-backend trigger)
12
+ * 5. truncation salvage: close an open string + the bracket stack, then
13
+ * a second pass that drops the trailing partial member first
14
+ * Anything unsalvageable falls back to `_unparsed` — the SAME visible
15
+ * convention as anthropic.ts and responses.ts, never a silent {}. */
16
+ export declare function parseToolCallArguments(raw: string): Record<string, unknown>;
17
+ //# sourceMappingURL=json-arguments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-arguments.d.ts","sourceRoot":"","sources":["../src/json-arguments.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;sEAcsE;AACtE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsB3E"}
@@ -0,0 +1,227 @@
1
+ // 拆分运动 C:从 openai-compatible.ts 原样外提的流式 tool-call 参数 JSON 修复梯子
2
+ //(波次 7 #33,CodeWhale parse_json_with_repair)。纯函数模块,零依赖。
3
+ /** 波次 7 #33 (CodeWhale parse_json_with_repair + truncation salvage): the
4
+ * deterministic repair ladder for streamed tool-call arguments. Torn SSE
5
+ * fragments, literal control chars from local backends, and the 2M-cap
6
+ * marker all poison the accumulated JSON — the old catch{} returned {}
7
+ * SILENTLY (the tool ran with no arguments, or died on a misleading
8
+ * "missing required field" validation error). Ladder, cheapest first:
9
+ * 1. strict parse (zero-change fast path)
10
+ * 2. ```json fence strip (CodeWhale: only when BOTH fence ends present)
11
+ * 3. first balanced object payload in prose (CodeWhale, ≤64 starts)
12
+ * 4. escape literal C0 controls inside strings (wudao extension — the
13
+ * local-backend trigger)
14
+ * 5. truncation salvage: close an open string + the bracket stack, then
15
+ * a second pass that drops the trailing partial member first
16
+ * Anything unsalvageable falls back to `_unparsed` — the SAME visible
17
+ * convention as anthropic.ts and responses.ts, never a silent {}. */
18
+ export function parseToolCallArguments(raw) {
19
+ // Empty/whitespace args are a NORMAL stream edge (a no-arg tool, or the
20
+ // fragment stream not started) — not damage; keep the legacy {}.
21
+ if (raw.trim() === "")
22
+ return {};
23
+ const direct = tryParseObject(raw);
24
+ if (direct !== undefined)
25
+ return direct;
26
+ const unfenced = stripCodeFence(raw);
27
+ if (unfenced !== raw) {
28
+ const p = tryParseObject(unfenced);
29
+ if (p !== undefined)
30
+ return p;
31
+ }
32
+ const extracted = firstBalancedObjectPayload(unfenced);
33
+ if (extracted !== undefined) {
34
+ const p = tryParseObject(extracted);
35
+ if (p !== undefined)
36
+ return p;
37
+ }
38
+ const base = escapeControlChars(extracted ?? unfenced);
39
+ const closed = tryParseObject(closeTruncatedJson(base));
40
+ if (closed !== undefined)
41
+ return closed;
42
+ const trimmed = tryParseObject(closeTruncatedJson(trimPartialMember(base)));
43
+ if (trimmed !== undefined)
44
+ return trimmed;
45
+ return { _unparsed: raw };
46
+ }
47
+ /** Strict parse that accepts only object-shaped results (the legacy
48
+ * predicate — arrays pass through as the record cast, matching the old
49
+ * parseArguments behavior). */
50
+ function tryParseObject(text) {
51
+ try {
52
+ const parsed = JSON.parse(text);
53
+ return typeof parsed === "object" && parsed !== null ? parsed : undefined;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ /** CodeWhale repair_json_text_once: strip a ```json / ``` fence — only when
60
+ * BOTH the prefix and the closing fence are present; anything else is
61
+ * returned trimmed, untouched. */
62
+ function stripCodeFence(raw) {
63
+ const trimmed = raw.trim();
64
+ const afterPrefix = trimmed.startsWith("```json") ? trimmed.slice(6)
65
+ : trimmed.startsWith("```") ? trimmed.slice(3)
66
+ : null;
67
+ if (afterPrefix === null)
68
+ return trimmed;
69
+ if (!afterPrefix.endsWith("```"))
70
+ return trimmed;
71
+ return afterPrefix.slice(0, -3).trim();
72
+ }
73
+ // Repair scans untrusted model text once per possible container start; keep
74
+ // the fallback bounded when malformed output contains a delimiter flood.
75
+ const JSON_REPAIR_CANDIDATE_LIMIT = 64;
76
+ /** CodeWhale first_valid_json_payload: the first `{`/`[` start whose payload
77
+ * is balanced AND parses as an object. Skips balanced-but-poisoned
78
+ * candidates (their content may still be recovered by stage 4 via the
79
+ * unfenced fallback — that is exactly CodeWhale's unwrap_or shape). */
80
+ function firstBalancedObjectPayload(raw) {
81
+ let attempted = 0;
82
+ for (let start = 0; start < raw.length; start += 1) {
83
+ const open = raw[start];
84
+ if (open !== "{" && open !== "[")
85
+ continue;
86
+ attempted += 1;
87
+ if (attempted > JSON_REPAIR_CANDIDATE_LIMIT)
88
+ break;
89
+ const end = balancedJsonEnd(raw, start);
90
+ if (end === undefined)
91
+ continue;
92
+ const candidate = raw.slice(start, end);
93
+ if (tryParseObject(candidate) !== undefined)
94
+ return candidate;
95
+ }
96
+ return undefined;
97
+ }
98
+ /** String/escape-aware balanced scan: index AFTER the closer matching the
99
+ * opener at `start`, or undefined when the payload is torn or mismatched. */
100
+ function balancedJsonEnd(raw, start) {
101
+ const stack = [];
102
+ let inString = false;
103
+ let escaped = false;
104
+ for (let i = start; i < raw.length; i += 1) {
105
+ const ch = raw[i];
106
+ if (inString) {
107
+ if (escaped)
108
+ escaped = false;
109
+ else if (ch === "\\")
110
+ escaped = true;
111
+ else if (ch === '"')
112
+ inString = false;
113
+ continue;
114
+ }
115
+ if (ch === '"')
116
+ inString = true;
117
+ else if (ch === "{" || ch === "[")
118
+ stack.push(ch);
119
+ else if (ch === "}" || ch === "]") {
120
+ const opener = stack.pop();
121
+ if (opener === undefined)
122
+ return undefined;
123
+ if ((opener === "{") !== (ch === "}"))
124
+ return undefined;
125
+ if (stack.length === 0)
126
+ return i + 1;
127
+ }
128
+ }
129
+ return undefined;
130
+ }
131
+ /** String-aware C0 escape (wudao extension): raw control characters inside
132
+ * strings are invalid JSON that local backends emit literally. \n \r \t
133
+ * get named escapes, the rest \\u00XX. */
134
+ function escapeControlChars(text) {
135
+ let out = "";
136
+ let inString = false;
137
+ let escaped = false;
138
+ for (const ch of text) {
139
+ if (inString) {
140
+ if (escaped) {
141
+ out += ch;
142
+ escaped = false;
143
+ continue;
144
+ }
145
+ if (ch === "\\") {
146
+ out += ch;
147
+ escaped = true;
148
+ continue;
149
+ }
150
+ if (ch === '"') {
151
+ out += ch;
152
+ inString = false;
153
+ continue;
154
+ }
155
+ if (ch < " ") {
156
+ out += ch === "\n" ? "\\n" : ch === "\r" ? "\\r" : ch === "\t" ? "\\t" : `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`;
157
+ continue;
158
+ }
159
+ out += ch;
160
+ continue;
161
+ }
162
+ if (ch === '"') {
163
+ inString = true;
164
+ out += ch;
165
+ continue;
166
+ }
167
+ out += ch;
168
+ }
169
+ return out;
170
+ }
171
+ /** Truncation salvage pass 1: close an open string (dropping a dangling
172
+ * escape backslash so it cannot eat the closer) and append the bracket
173
+ * closers in reverse stack order. */
174
+ function closeTruncatedJson(text) {
175
+ const stack = [];
176
+ let inString = false;
177
+ let escaped = false;
178
+ for (const ch of text) {
179
+ if (inString) {
180
+ if (escaped)
181
+ escaped = false;
182
+ else if (ch === "\\")
183
+ escaped = true;
184
+ else if (ch === '"')
185
+ inString = false;
186
+ continue;
187
+ }
188
+ if (ch === '"')
189
+ inString = true;
190
+ else if (ch === "{" || ch === "[")
191
+ stack.push(ch);
192
+ else if (ch === "}" || ch === "]")
193
+ stack.pop();
194
+ }
195
+ let out = inString && escaped ? text.slice(0, -1) : text;
196
+ if (inString)
197
+ out += '"';
198
+ for (let i = stack.length - 1; i >= 0; i -= 1)
199
+ out += stack[i] === "{" ? "}" : "]";
200
+ return out;
201
+ }
202
+ /** Truncation salvage pass 2: cut back to the last member boundary outside
203
+ * strings (`,` `:` `{` `[`) so a dangling partial member does not poison
204
+ * the closure. */
205
+ function trimPartialMember(text) {
206
+ let lastBoundary = -1;
207
+ let inString = false;
208
+ let escaped = false;
209
+ for (let i = 0; i < text.length; i += 1) {
210
+ const ch = text[i];
211
+ if (inString) {
212
+ if (escaped)
213
+ escaped = false;
214
+ else if (ch === "\\")
215
+ escaped = true;
216
+ else if (ch === '"')
217
+ inString = false;
218
+ continue;
219
+ }
220
+ if (ch === '"')
221
+ inString = true;
222
+ else if (ch === "," || ch === ":" || ch === "{" || ch === "[")
223
+ lastBoundary = i;
224
+ }
225
+ return lastBoundary === -1 ? text : text.slice(0, lastBoundary);
226
+ }
227
+ //# sourceMappingURL=json-arguments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-arguments.js","sourceRoot":"","sources":["../src/json-arguments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /** The /status + statusline shape: `keys n/m`. */
2
+ export interface KeyPoolSnapshot {
3
+ readonly total: number;
4
+ readonly available: number;
5
+ readonly cooling: number;
6
+ }
7
+ /**
8
+ * 夜移 007 (hermes credential-pool port): cooldowns are keyed by HTTP status
9
+ * class, not "was it a 429". `auth` (401/403 — the credential is dead) cools
10
+ * LONG so a revoked key stops re-entering rotation; `rate` (429) keeps the M56
11
+ * 60s contract; provider-side failures cool NOTHING (penalizing keys over the
12
+ * provider's own outage starves the pool for no gain).
13
+ */
14
+ export type CooldownClass = "auth" | "rate";
15
+ /** 401/403 → auth; 429 → rate; everything else → null (never cool the key). */
16
+ export declare function cooldownClassForStatus(status: number): CooldownClass | null;
17
+ export interface KeyPool {
18
+ /** The key for the next request (round-robin, cooldown-aware). */
19
+ select(): string;
20
+ /** Put a key into cooldown (no-op for unknown keys). Without a class the
21
+ * M56 rate contract applies — existing call sites keep their behavior. */
22
+ markCooldown(key: string, cls?: CooldownClass): void;
23
+ /** Live counts for /status and the statusline. */
24
+ snapshot(): KeyPoolSnapshot;
25
+ readonly size: number;
26
+ }
27
+ export interface KeyPoolOptions {
28
+ /** Cooldown after a 429; default 60s (WUDAO key cooldown contract). */
29
+ readonly cooldownMs?: number;
30
+ /** Cooldown after a 401/403 (dead credential); default 15min. */
31
+ readonly authCooldownMs?: number;
32
+ /** Injectable clock (tests); default Date.now. */
33
+ readonly now?: () => number;
34
+ }
35
+ export declare const DEFAULT_KEY_COOLDOWN_MS = 60000;
36
+ export declare const DEFAULT_AUTH_COOLDOWN_MS = 900000;
37
+ /** Build a pool from raw keys (deduped, order-preserving). Throws on empty —
38
+ * a pool with no key is a configuration error, not a runtime state. */
39
+ export declare function createKeyPool(rawKeys: readonly string[], options?: KeyPoolOptions): KeyPool;
40
+ /** Convenience: the env's pool, as `createKeyPool` wants it (may be empty —
41
+ * resolution is the caller's failure to explain). */
42
+ export declare function keyPoolFromEnv(env?: NodeJS.ProcessEnv): string[];
43
+ /**
44
+ * The LIVE pool — keys read from `getKeys` on EVERY select/snapshot, not
45
+ * captured at construction (2026-08-31 minimax-401 弧).
46
+ *
47
+ * The session engine used to build ONE static `createKeyPool` at boot and
48
+ * hand that object to every client rebuilt at a `/provider` switch; the
49
+ * adapters treat an explicit pool as authoritative (`options.keyPool ??
50
+ * own-config pool`), so a switched provider authenticated with the BOOT
51
+ * provider's key — `401 login fail` on every retry. The bug's class: key
52
+ * state derived once from the boot config and surviving config changes —
53
+ * a SECOND key store that can silently disagree with the config that built
54
+ * the current client.
55
+ *
56
+ * The fix is structural, not disciplinary (no "remember to reseed" at every
57
+ * future build site): the live pool HAS no independent key list. Wired to
58
+ * the engine's ONE mutable config holder (the S2b doctrine — the switch
59
+ * seam syncs it in place before every rebuild), it structurally cannot
60
+ * serve a key the current config does not carry. Rotation/cooldown
61
+ * semantics are the static pool's; cooldowns stay keyed by key VALUE, so
62
+ * a key that survives a config change keeps its cooldown, and a key that
63
+ * leaves the config takes its cooldown with it.
64
+ */
65
+ export declare function createLiveKeyPool(getKeys: () => readonly string[], options?: KeyPoolOptions): KeyPool;
66
+ //# sourceMappingURL=key-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-pool.d.ts","sourceRoot":"","sources":["../src/key-pool.ts"],"names":[],"mappings":"AA6BA,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAI3E;AAED,MAAM,WAAW,OAAO;IACtB,kEAAkE;IAClE,MAAM,IAAI,MAAM,CAAC;IACjB;+EAC2E;IAC3E,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrD,kDAAkD;IAClD,QAAQ,IAAI,eAAe,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,kDAAkD;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAEhD;wEACwE;AACxE,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAwD/F;AAED;sDACsD;AACtD,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,EAAE,CAE7E;AAaD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,SAAS,MAAM,EAAE,EAChC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAiET"}
@@ -0,0 +1,207 @@
1
+ // M56 §1 (hive llm/key_pool.py) — the multi-key pool. Domestic providers
2
+ // rate-limit PER KEY (RPM quotas per credential), so a harness aimed at them
3
+ // pools N keys and rotates. Semantics (the hive contract, pinned by tests):
4
+ //
5
+ // round-robin selection advances a cursor over the deduped key list;
6
+ // cooldown a key that just took a KEY-SIDE failure is skipped — 夜移 007
7
+ // (hermes port) keys this by HTTP status class: 401/403 = the
8
+ // credential is dead → `authCooldownMs` (default 15min, it
9
+ // must stop re-entering rotation); 429 = rate/fleet limit →
10
+ // `cooldownMs` (default 60s, the M56 contract); provider-side
11
+ // statuses (5xx…) cool NOTHING — penalizing keys over the
12
+ // provider's own outage starves the pool for no gain;
13
+ // soonest-expiring fallback
14
+ // when EVERY key is cooling, selection returns the key whose
15
+ // cooldown expires soonest — "so the caller can proceed with
16
+ // minimal delay... no sleep required". The retry ladder's
17
+ // backoff absorbs the wait; a single-key pool therefore keeps
18
+ // the historical single-key behavior exactly (same key, same
19
+ // ladder). A dead key in a one-key pool thus fails every
20
+ // request — correct: the retry ladder deliberately does NOT
21
+ // retry 401/403 (a wrong key is a config problem, not a
22
+ // transient; fail fast, diverging from hermes's rotate-on-auth
23
+ // on purpose).
24
+ //
25
+ // Key selection happens per REQUEST (inside each HTTP attempt): the key rides
26
+ // only the Authorization header, never the body, so rotating mid-session or
27
+ // mid-retry cannot disturb the prompt prefix (M24 cache invariant).
28
+ import { parseApiKeyPool } from "./config.js";
29
+ /** 401/403 → auth; 429 → rate; everything else → null (never cool the key). */
30
+ export function cooldownClassForStatus(status) {
31
+ if (status === 401 || status === 403)
32
+ return "auth";
33
+ if (status === 429)
34
+ return "rate";
35
+ return null;
36
+ }
37
+ export const DEFAULT_KEY_COOLDOWN_MS = 60_000;
38
+ export const DEFAULT_AUTH_COOLDOWN_MS = 900_000;
39
+ /** Build a pool from raw keys (deduped, order-preserving). Throws on empty —
40
+ * a pool with no key is a configuration error, not a runtime state. */
41
+ export function createKeyPool(rawKeys, options = {}) {
42
+ const keys = canonicalKeys(rawKeys);
43
+ if (keys.length === 0)
44
+ throw new Error("key pool needs at least one API key");
45
+ const cooldownMs = options.cooldownMs ?? DEFAULT_KEY_COOLDOWN_MS;
46
+ const authCooldownMs = options.authCooldownMs ?? DEFAULT_AUTH_COOLDOWN_MS;
47
+ const now = options.now ?? Date.now;
48
+ /** key → epoch-ms when its cooldown lapses. */
49
+ const coolingUntil = new Map();
50
+ let cursor = 0;
51
+ const isCooling = (key, t) => (coolingUntil.get(key) ?? 0) > t;
52
+ return {
53
+ get size() {
54
+ return keys.length;
55
+ },
56
+ select() {
57
+ const t = now();
58
+ // First available key at/after the cursor (wrap-around included).
59
+ for (let i = 0; i < keys.length; i += 1) {
60
+ const idx = (cursor + i) % keys.length;
61
+ const key = keys[idx];
62
+ if (!isCooling(key, t)) {
63
+ cursor = (idx + 1) % keys.length;
64
+ return key;
65
+ }
66
+ }
67
+ // All cooling: the soonest-expiring key — minimal delay, no sleep.
68
+ let best = keys[0];
69
+ let bestUntil = coolingUntil.get(best) ?? 0;
70
+ for (const key of keys) {
71
+ const until = coolingUntil.get(key) ?? 0;
72
+ if (until < bestUntil) {
73
+ best = key;
74
+ bestUntil = until;
75
+ }
76
+ }
77
+ const idx = keys.indexOf(best);
78
+ cursor = (idx + 1) % keys.length;
79
+ return best;
80
+ },
81
+ markCooldown(key, cls = "rate") {
82
+ if (!keys.includes(key))
83
+ return;
84
+ const until = now() + (cls === "auth" ? authCooldownMs : cooldownMs);
85
+ // Never SHORTEN an existing cooldown (the max wins on repeat hits —
86
+ // including cross-class: a 429 on an auth-cooling key extends nothing).
87
+ if (until > (coolingUntil.get(key) ?? 0))
88
+ coolingUntil.set(key, until);
89
+ },
90
+ snapshot() {
91
+ const t = now();
92
+ let cooling = 0;
93
+ for (const key of keys)
94
+ if (isCooling(key, t))
95
+ cooling += 1;
96
+ return { total: keys.length, available: keys.length - cooling, cooling };
97
+ },
98
+ };
99
+ }
100
+ /** Convenience: the env's pool, as `createKeyPool` wants it (may be empty —
101
+ * resolution is the caller's failure to explain). */
102
+ export function keyPoolFromEnv(env = process.env) {
103
+ return parseApiKeyPool(env);
104
+ }
105
+ /** Trim + dedup (order-preserving) — the pool's canonicalization of a raw
106
+ * key list, shared by the static and live factories. */
107
+ function canonicalKeys(rawKeys) {
108
+ const keys = [];
109
+ for (const k of rawKeys) {
110
+ const trimmed = k.trim();
111
+ if (trimmed !== "" && !keys.includes(trimmed))
112
+ keys.push(trimmed);
113
+ }
114
+ return keys;
115
+ }
116
+ /**
117
+ * The LIVE pool — keys read from `getKeys` on EVERY select/snapshot, not
118
+ * captured at construction (2026-08-31 minimax-401 弧).
119
+ *
120
+ * The session engine used to build ONE static `createKeyPool` at boot and
121
+ * hand that object to every client rebuilt at a `/provider` switch; the
122
+ * adapters treat an explicit pool as authoritative (`options.keyPool ??
123
+ * own-config pool`), so a switched provider authenticated with the BOOT
124
+ * provider's key — `401 login fail` on every retry. The bug's class: key
125
+ * state derived once from the boot config and surviving config changes —
126
+ * a SECOND key store that can silently disagree with the config that built
127
+ * the current client.
128
+ *
129
+ * The fix is structural, not disciplinary (no "remember to reseed" at every
130
+ * future build site): the live pool HAS no independent key list. Wired to
131
+ * the engine's ONE mutable config holder (the S2b doctrine — the switch
132
+ * seam syncs it in place before every rebuild), it structurally cannot
133
+ * serve a key the current config does not carry. Rotation/cooldown
134
+ * semantics are the static pool's; cooldowns stay keyed by key VALUE, so
135
+ * a key that survives a config change keeps its cooldown, and a key that
136
+ * leaves the config takes its cooldown with it.
137
+ */
138
+ export function createLiveKeyPool(getKeys, options = {}) {
139
+ // Fail-fast at construction, exactly like the static pool: a getter that
140
+ // yields nothing is a configuration error, not a runtime state.
141
+ if (canonicalKeys(getKeys()).length === 0)
142
+ throw new Error("key pool needs at least one API key");
143
+ const cooldownMs = options.cooldownMs ?? DEFAULT_KEY_COOLDOWN_MS;
144
+ const authCooldownMs = options.authCooldownMs ?? DEFAULT_AUTH_COOLDOWN_MS;
145
+ const now = options.now ?? Date.now;
146
+ /** key → epoch-ms when its cooldown lapses (values only — config-proof). */
147
+ const coolingUntil = new Map();
148
+ let cursor = 0;
149
+ const isCooling = (key, t) => (coolingUntil.get(key) ?? 0) > t;
150
+ return {
151
+ get size() {
152
+ return canonicalKeys(getKeys()).length;
153
+ },
154
+ select() {
155
+ const keys = canonicalKeys(getKeys());
156
+ // The holder mutated to empty mid-session (nothing upstream validates
157
+ // it): fail LOUD here rather than silently serving a stale key — that
158
+ // silence is exactly the 401 this pool exists to make impossible.
159
+ if (keys.length === 0)
160
+ throw new Error("key pool needs at least one API key");
161
+ const t = now();
162
+ // First available key at/after the cursor (wrap-around included) —
163
+ // modulo the CURRENT length, so a list that shrank or grew never
164
+ // indexes out of range or pins a dead position.
165
+ for (let i = 0; i < keys.length; i += 1) {
166
+ const idx = (cursor + i) % keys.length;
167
+ const key = keys[idx];
168
+ if (!isCooling(key, t)) {
169
+ cursor = (idx + 1) % keys.length;
170
+ return key;
171
+ }
172
+ }
173
+ // All cooling: the soonest-expiring CURRENT key (same contract).
174
+ let best = keys[0];
175
+ let bestUntil = coolingUntil.get(best) ?? 0;
176
+ for (const key of keys) {
177
+ const until = coolingUntil.get(key) ?? 0;
178
+ if (until < bestUntil) {
179
+ best = key;
180
+ bestUntil = until;
181
+ }
182
+ }
183
+ const idx = keys.indexOf(best);
184
+ cursor = (idx + 1) % keys.length;
185
+ return best;
186
+ },
187
+ markCooldown(key, cls = "rate") {
188
+ if (!canonicalKeys(getKeys()).includes(key))
189
+ return;
190
+ const until = now() + (cls === "auth" ? authCooldownMs : cooldownMs);
191
+ // Never SHORTEN an existing cooldown (the max wins on repeat hits —
192
+ // including cross-class: a 429 on an auth-cooling key extends nothing).
193
+ if (until > (coolingUntil.get(key) ?? 0))
194
+ coolingUntil.set(key, until);
195
+ },
196
+ snapshot() {
197
+ const keys = canonicalKeys(getKeys());
198
+ const t = now();
199
+ let cooling = 0;
200
+ for (const key of keys)
201
+ if (isCooling(key, t))
202
+ cooling += 1;
203
+ return { total: keys.length, available: keys.length - cooling, cooling };
204
+ },
205
+ };
206
+ }
207
+ //# sourceMappingURL=key-pool.js.map