mixdog 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/scripts/provider-pingpong-live.mjs +509 -0
  6. package/src/agents/advisor/AGENT.md +1 -1
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +3 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +6 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/defaults/skills/setup/references/actions.md +44 -22
  13. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  14. package/src/headless-exec.mjs +15 -8
  15. package/src/lib/keychain-cjs.cjs +7 -4
  16. package/src/output-styles/common.md +11 -10
  17. package/src/repl/slash-commands.mjs +16 -1
  18. package/src/rules/agent/AGENT.md +5 -5
  19. package/src/rules/lead/LEAD.md +5 -5
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  22. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  23. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  33. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  36. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  37. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  38. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  39. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  40. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  41. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  44. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  45. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
  46. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  47. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  48. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
  49. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
  50. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  51. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
  58. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  63. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  65. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  71. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  72. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
  73. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
  74. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
  75. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  76. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
  77. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  79. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  80. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
  81. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  83. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
  84. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  85. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  86. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
  87. package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
  88. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  89. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  90. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  91. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  92. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  93. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
  94. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
  96. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
  97. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  99. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  100. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  101. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  102. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
  103. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
  105. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
  106. package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
  107. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  108. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  109. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
  110. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  113. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  114. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  115. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  116. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  117. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  118. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  120. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  121. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  122. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  123. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  125. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  126. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  127. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  128. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  129. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  138. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  140. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  141. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  142. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
  143. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
  144. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  145. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  147. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
  148. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  150. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
  151. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
  153. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
  154. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
  155. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
  156. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
  157. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  158. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  159. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  160. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
  161. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  163. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
  164. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  165. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
  166. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  167. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  168. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  169. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  170. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
  171. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
  172. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  173. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
  174. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
  175. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  176. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  177. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  178. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  179. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  180. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  181. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  182. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  183. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  184. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  185. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  186. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  187. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  188. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
  189. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  190. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  191. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  192. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
  193. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  195. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
  196. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  197. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  198. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  199. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  200. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  201. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  202. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  203. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  204. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  205. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  206. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  207. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  208. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  209. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  210. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  211. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  212. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  213. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  214. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  215. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  216. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  217. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  218. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  219. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  220. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  221. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  222. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
  223. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  224. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  225. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  226. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  227. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  228. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  229. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  230. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  231. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  232. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  233. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  234. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  235. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  236. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  237. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  238. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  239. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  240. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
  241. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  242. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  243. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  244. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  245. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  246. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  248. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  249. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  250. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  251. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
  252. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  253. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  254. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  255. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  256. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  257. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  258. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
  259. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  260. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  261. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  262. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  263. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  264. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  265. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  266. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  267. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
  268. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  269. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  270. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  271. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  272. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  273. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  274. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  275. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  276. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  277. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  278. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  279. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  280. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  281. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
  282. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
  283. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
  284. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  285. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  286. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  287. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  288. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  289. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  290. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  291. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  292. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  293. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  294. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  295. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  296. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  297. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  298. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  299. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  300. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  301. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  302. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  303. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  304. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  305. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  306. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  307. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  308. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  309. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  310. package/src/runtime/github/tool.mjs +92 -2
  311. package/src/runtime/memory/index.mjs +1 -0
  312. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  313. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  314. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  315. package/src/runtime/memory/lib/pg/process.mjs +65 -50
  316. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  317. package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
  318. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  319. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  320. package/src/runtime/office/README.md +2 -2
  321. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  322. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  323. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  324. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  325. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  326. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  327. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  328. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  329. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  330. package/src/runtime/office/core/office-sessions.mjs +5 -0
  331. package/src/runtime/office/core/office-transactions.mjs +3 -0
  332. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  333. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  334. package/src/runtime/office/index.mjs +30 -0
  335. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  336. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  337. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  338. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  339. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  340. package/src/runtime/office/shared/values.mjs +0 -5
  341. package/src/runtime/shared/background-tasks.mjs +92 -14
  342. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  343. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  344. package/src/runtime/shared/buffered-appender.mjs +18 -0
  345. package/src/runtime/shared/config.mjs +78 -18
  346. package/src/runtime/shared/err-text.mjs +31 -0
  347. package/src/runtime/shared/error-presentation.mjs +159 -19
  348. package/src/runtime/shared/file-permissions.d.mts +6 -1
  349. package/src/runtime/shared/file-permissions.mjs +54 -6
  350. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  351. package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
  352. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  353. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  354. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  355. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  356. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  357. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  358. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  359. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  360. package/src/runtime/shared/loopback-listener.mjs +9 -0
  361. package/src/runtime/shared/model-display.mjs +13 -0
  362. package/src/runtime/shared/provider-api-key.mjs +2 -0
  363. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  364. package/src/runtime/shared/review-diff.mjs +52 -0
  365. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  366. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  367. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  368. package/src/runtime/shared/slow-span.mjs +114 -0
  369. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  370. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  371. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  372. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  373. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  374. package/src/runtime/shared/tool-card-model.mjs +5 -2
  375. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  376. package/src/runtime/shared/tool-primitives.mjs +13 -0
  377. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  378. package/src/runtime/shared/tool-surface.mjs +18 -3
  379. package/src/runtime/shared/tool-work-units.mjs +40 -4
  380. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  381. package/src/runtime/shared/transcript-writer.mjs +10 -2
  382. package/src/runtime/shared/turn-changes.mjs +670 -274
  383. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  384. package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
  385. package/src/runtime/shared/turn-snapshot.mjs +986 -690
  386. package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
  387. package/src/runtime/shared/user-data-guard.mjs +262 -49
  388. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  389. package/src/runtime/shared/worktrees/contract.mjs +34 -24
  390. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  391. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  392. package/src/runtime/shared/worktrees/git.mjs +8 -3
  393. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  394. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  395. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  396. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  397. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  398. package/src/runtime/terminal-bridge/client.mjs +44 -11
  399. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  400. package/src/runtime/tidy/apply.mjs +47 -2
  401. package/src/runtime/tidy/run-engines.mjs +11 -5
  402. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  403. package/src/session-runtime/boot/apis.mjs +19 -5
  404. package/src/session-runtime/boot/facade.mjs +1 -0
  405. package/src/session-runtime/boot/infrastructure.mjs +12 -1
  406. package/src/session-runtime/boot/session.mjs +0 -1
  407. package/src/session-runtime/boot/tools.mjs +4 -1
  408. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  409. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  410. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  411. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  412. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  413. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  414. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  415. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  416. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
  417. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  418. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  419. package/src/session-runtime/lifecycle/teardown.mjs +73 -8
  420. package/src/session-runtime/lifecycle-api.mjs +1 -0
  421. package/src/session-runtime/mcp-glue/connect.mjs +46 -14
  422. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  423. package/src/session-runtime/mcp-glue.mjs +3 -0
  424. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  425. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  426. package/src/session-runtime/model-route/set-route.mjs +85 -17
  427. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  428. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  429. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  430. package/src/session-runtime/native-web-search.mjs +10 -1
  431. package/src/session-runtime/new-session-config.mjs +4 -2
  432. package/src/session-runtime/provider-auth/login.mjs +4 -0
  433. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  434. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  435. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  436. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  437. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  438. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  439. package/src/session-runtime/runtime-review-api.mjs +21 -2
  440. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  441. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  442. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  443. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  444. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  445. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  446. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  447. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  448. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  449. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  450. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  451. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  452. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  453. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  454. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  455. package/src/session-runtime/services/agent-tool.mjs +3 -0
  456. package/src/session-runtime/services/channel-admin.mjs +19 -3
  457. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  458. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  459. package/src/session-runtime/services/provider-admin.mjs +81 -15
  460. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  461. package/src/session-runtime/session-turn-api.mjs +14 -5
  462. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  463. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  464. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  465. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  466. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  467. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  468. package/src/session-runtime/skills-api.mjs +3 -5
  469. package/src/session-runtime/tool-surface.mjs +5 -1
  470. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  471. package/src/session-runtime/turn/session-ops.mjs +7 -5
  472. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  473. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  474. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  475. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  476. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  477. package/src/session-runtime/turn/turn-run.mjs +25 -9
  478. package/src/session-runtime/usage-stats-api.mjs +43 -11
  479. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  480. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  481. package/src/session-runtime/workspace/transitions.mjs +190 -15
  482. package/src/standalone/daemon-agent-control.mjs +9 -1
  483. package/src/standalone/daemon-drain-policy.mjs +8 -0
  484. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  485. package/src/standalone/daemon-owner.mjs +17 -4
  486. package/src/standalone/daemon-pg-stop.mjs +51 -0
  487. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  488. package/src/standalone/daemon-stop-budget.mjs +18 -0
  489. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  490. package/src/standalone/daemon-telemetry.mjs +74 -0
  491. package/src/standalone/daemon.mjs +64 -8
  492. package/src/standalone/desktop-service-registry.mjs +5 -0
  493. package/src/standalone/local-session-runtime.mjs +70 -19
  494. package/src/standalone/session-client.mjs +42 -0
  495. package/src/standalone/session-protocol.mjs +14 -0
  496. package/src/standalone/session-proxy/projection.mjs +41 -4
  497. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  498. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  499. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  500. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  501. package/src/standalone/session-service/agent-tree.mjs +12 -0
  502. package/src/standalone/session-service/quota-resume.mjs +8 -0
  503. package/src/standalone/session-service/retention/busy.mjs +15 -1
  504. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  505. package/src/standalone/session-service-api.mjs +11 -1
  506. package/src/standalone/session-service.mjs +1 -0
  507. package/src/standalone/session-state-patch.d.mts +2 -1
  508. package/src/standalone/session-state-patch.mjs +41 -1
  509. package/src/tui/App.jsx +25 -0
  510. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  511. package/src/tui/app/app-view.jsx +1 -0
  512. package/src/tui/app/btw-controller.mjs +118 -0
  513. package/src/tui/app/create-app-pickers.mjs +6 -0
  514. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  515. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  516. package/src/tui/app/extension-pickers.mjs +2 -1
  517. package/src/tui/app/model-options.mjs +11 -16
  518. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  519. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  520. package/src/tui/app/slash-commands.mjs +20 -5
  521. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  522. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  523. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  524. package/src/tui/app/use-global-key-input.mjs +5 -12
  525. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  526. package/src/tui/components/BtwPanel.jsx +53 -0
  527. package/src/tui/components/QuestionPanel.jsx +275 -0
  528. package/src/tui/components/StatusLine.jsx +2 -0
  529. package/src/tui/components/question-panel-model.mjs +156 -0
  530. package/src/tui/components/status-line/local-line.mjs +5 -2
  531. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  532. package/src/tui/dist/index.mjs +3221 -1500
  533. package/src/tui/session/context-state.mjs +4 -2
  534. package/src/tui/session/quota-resume.mjs +46 -14
  535. package/src/tui/session/session-api/commands.mjs +43 -24
  536. package/src/tui/session/session-api/controls.mjs +2 -2
  537. package/src/tui/session/session-api/extensions.mjs +22 -0
  538. package/src/tui/session/session-api/integrations.mjs +16 -2
  539. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  540. package/src/tui/session/session-api/lifecycle.mjs +7 -2
  541. package/src/tui/session/session-api/routes.mjs +71 -7
  542. package/src/tui/session/session-api/settings.mjs +11 -14
  543. package/src/tui/session/session-api.mjs +1 -0
  544. package/src/tui/session/session-flow/drain.mjs +7 -1
  545. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  546. package/src/tui/session/tool-approval.mjs +10 -7
  547. package/src/tui/session/transcript-spill.mjs +14 -7
  548. package/src/tui/session/turn-stream.mjs +174 -31
  549. package/src/tui/session/turn.mjs +46 -16
  550. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  551. package/src/ui/statusline-agents.mjs +15 -4
  552. package/src/ui/statusline.mjs +7 -3
  553. package/src/ui/streaming-markdown-heal.mjs +34 -3
  554. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  555. package/src/workflows/default/WORKFLOW.md +3 -1
  556. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  557. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -0,0 +1,229 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import keychain from '../../../../lib/keychain-cjs.cjs';
4
+ import { openInBrowser } from '../../../shared/open-url.mjs';
5
+ import { normalizeMcpTransportUrl } from './client-config.mjs';
6
+ import {
7
+ MCP_OAUTH_CALLBACK_MAX_BYTES,
8
+ MCP_OAUTH_CALLBACK_NOT_PENDING,
9
+ parsePastedMcpOAuthCallback,
10
+ } from './oauth-callback.mjs';
11
+ import {
12
+ TOKENS_FLAG,
13
+ createCredentialCache,
14
+ credentialFile,
15
+ readStoredCredentials,
16
+ withCredentialLock,
17
+ writeRevisionMarker,
18
+ } from './oauth-credentials.mjs';
19
+ import { createLoginFlow } from './oauth-login.mjs';
20
+ import { createOAuthProvider } from './oauth-provider.mjs';
21
+ import { createAuthorizedFetch } from './oauth-request.mjs';
22
+ import { REAUTHENTICATION_REQUIRED_MESSAGE, anySignal } from './oauth-shared.mjs';
23
+
24
+ export { MCP_OAUTH_CALLBACK_MAX_BYTES, MCP_OAUTH_CALLBACK_NOT_PENDING, parsePastedMcpOAuthCallback };
25
+
26
+ export const MCP_OAUTH_TIMEOUT_MS = 5 * 60_000;
27
+
28
+ const scopes = new WeakMap();
29
+
30
+ /** Whether a connect failed only because the server needs an (explicit) OAuth
31
+ * sign-in. Transports may wrap the error (SSE re-raises it as an event
32
+ * message), so the message and cause chain count too. */
33
+ export function isMcpOAuthReauthenticationRequired(error) {
34
+ for (let current = error, depth = 0; current && depth < 5; current = current.cause, depth++) {
35
+ if (current.code === 'reauthenticationRequired') return true;
36
+ if (String(current.message ?? current).includes(REAUTHENTICATION_REQUIRED_MESSAGE)) return true;
37
+ }
38
+ return false;
39
+ }
40
+
41
+ // One keychain entry per exact server URL. Windows stores each account as a
42
+ // file name, so the account is a hash of the URL and nothing else.
43
+ export function mcpOAuthAccount(url) {
44
+ return `mcp-oauth.${createHash('sha256').update(url).digest('hex')}`;
45
+ }
46
+
47
+ /** Whether OAuth tokens are stored for one server URL, read from the
48
+ * non-secret revision marker without touching the keychain. */
49
+ export function hasStoredMcpOAuthTokens(url) {
50
+ try {
51
+ const marker = readFileSync(credentialFile(mcpOAuthAccount(normalizeMcpTransportUrl(url)), 'rev'), 'utf8');
52
+ return marker.endsWith(TOKENS_FLAG);
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /** Log out of one MCP server URL: delete its stored tokens and client
59
+ * registration and drop them from live connections, which then need an
60
+ * explicit reconnect to log in again. */
61
+ export async function clearMcpOAuth({ url, credentialStore = keychain }) {
62
+ url = normalizeMcpTransportUrl(url);
63
+ const account = mcpOAuthAccount(url);
64
+ await withCredentialLock(account, async () => {
65
+ await writeRevisionMarker(account, null);
66
+ if ((await readStoredCredentials(credentialStore, account)) != null)
67
+ await credentialStore.deleteSecretAsync(account);
68
+ });
69
+ scopes.get(credentialStore)?.get(url)?.oauth.clear();
70
+ }
71
+
72
+ // The keychain is the only durable store. Exact URLs, including query strings,
73
+ // partition clients and tokens even when servers share an authorization issuer.
74
+ export function createMcpOAuth({
75
+ url,
76
+ credentialStore = keychain,
77
+ interactive = false,
78
+ isTransportEndpoint = () => false,
79
+ serverName,
80
+ ...options
81
+ }) {
82
+ url = normalizeMcpTransportUrl(url);
83
+ let entries = scopes.get(credentialStore);
84
+ if (!entries) {
85
+ entries = new Map();
86
+ scopes.set(credentialStore, entries);
87
+ }
88
+ let entry = entries.get(url);
89
+ if (!entry) {
90
+ entry = { oauth: createSharedOAuth({ url, credentialStore, ...options }), users: 0 };
91
+ entries.set(url, entry);
92
+ }
93
+ entry.users++;
94
+ const lifetime = new AbortController();
95
+ let onStart = () => {};
96
+ let loginAllowed = interactive === true;
97
+ let loginStarted = false;
98
+ let loginCompleted = false;
99
+ return {
100
+ provider: entry.oauth.provider,
101
+ signal: lifetime.signal,
102
+ set onStart(value) {
103
+ onStart = value;
104
+ },
105
+ /** Whether this connection was opened by an explicit sign-in. */
106
+ get interactive() {
107
+ return interactive === true;
108
+ },
109
+ /** Whether this connection started an interactive (browser) login. */
110
+ get loginStarted() {
111
+ return loginStarted;
112
+ },
113
+ /** Whether that login obtained tokens (the sign-in itself completed). */
114
+ get loginCompleted() {
115
+ return loginCompleted;
116
+ },
117
+ finishConnect() {
118
+ loginAllowed = false;
119
+ },
120
+ /** Every OAuth token this connection held, for scrubbing errors and results. */
121
+ secrets: () => entry.oauth.secrets(),
122
+ /** Answer the browser login this connection started with the redirect URL
123
+ * pasted from the browser, exactly as the loopback callback would. */
124
+ completeCallback(pasted) {
125
+ if (!loginStarted || loginCompleted || lifetime.signal.aborted) throw new Error(MCP_OAUTH_CALLBACK_NOT_PENDING);
126
+ entry.oauth.completeCallback(pasted);
127
+ },
128
+ async fetch(input, init = {}) {
129
+ const signal = anySignal(lifetime.signal, init.signal);
130
+ let startedHere = false;
131
+ return entry.oauth.fetch(
132
+ input,
133
+ { ...init, signal },
134
+ () => {
135
+ if (!loginAllowed) return false;
136
+ loginAllowed = false;
137
+ loginStarted = startedHere = true;
138
+ onStart();
139
+ return true;
140
+ },
141
+ options.openBrowser,
142
+ isTransportEndpoint,
143
+ serverName,
144
+ // The login this request started stored tokens (a cancel, denial,
145
+ // timeout or failed exchange never gets here); the retry after it may
146
+ // still fail on its own.
147
+ () => {
148
+ if (startedHere) loginCompleted = true;
149
+ }
150
+ );
151
+ },
152
+ close() {
153
+ if (lifetime.signal.aborted) return;
154
+ lifetime.abort();
155
+ if (--entry.users === 0) {
156
+ entry.oauth.close();
157
+ entries.delete(url);
158
+ }
159
+ },
160
+ };
161
+ }
162
+
163
+ function createSharedOAuth({
164
+ url,
165
+ credentialStore,
166
+ openBrowser = openInBrowser,
167
+ fetchImpl = fetch,
168
+ timeoutMs = MCP_OAUTH_TIMEOUT_MS,
169
+ }) {
170
+ const lifetime = new AbortController();
171
+ const cache = createCredentialCache({ url, credentialStore, account: mcpOAuthAccount(url) });
172
+ const { creds } = cache;
173
+ const flow = {
174
+ listener: undefined,
175
+ state: undefined,
176
+ verifier: undefined,
177
+ allowBrowser: false,
178
+ browserOpened: false,
179
+ launchBrowser: openBrowser,
180
+ finishCallback: undefined,
181
+ };
182
+ const provider = createOAuthProvider({ cache, flow, lifetimeSignal: lifetime.signal });
183
+ const { login, stopListening, closeListener, answerCallback } = createLoginFlow({
184
+ url,
185
+ cache,
186
+ flow,
187
+ provider,
188
+ lifetimeSignal: lifetime.signal,
189
+ openBrowser,
190
+ fetchImpl,
191
+ timeoutMs,
192
+ });
193
+ const requests = createAuthorizedFetch({
194
+ url,
195
+ cache,
196
+ login,
197
+ fetchImpl,
198
+ lifetimeSignal: lifetime.signal,
199
+ });
200
+ return {
201
+ provider,
202
+ signal: lifetime.signal,
203
+ fetch: requests.fetch,
204
+ secrets: () => [...cache.held],
205
+ /** Answer the pending browser login with a pasted redirect URL: the same
206
+ * state check, PKCE exchange and persistence as the loopback callback,
207
+ * whose listener closes. An invalid paste leaves the login pending. */
208
+ completeCallback(pasted) {
209
+ if (!flow.finishCallback || !flow.browserOpened || !flow.state || !creds.redirectUrl) {
210
+ throw new Error(MCP_OAUTH_CALLBACK_NOT_PENDING);
211
+ }
212
+ const { code } = parsePastedMcpOAuthCallback(pasted, { redirectUrl: creds.redirectUrl, state: flow.state });
213
+ stopListening();
214
+ // A denial (no code) fails the login without echoing its description.
215
+ answerCallback(code);
216
+ },
217
+ /** Forget in-memory credentials (after a log-out) and cancel a login in
218
+ * progress; the next request re-reads the keychain. */
219
+ clear() {
220
+ requests.cancelLogin();
221
+ cache.clear();
222
+ },
223
+ close() {
224
+ lifetime.abort();
225
+ flow.finishCallback?.();
226
+ closeListener();
227
+ },
228
+ };
229
+ }
@@ -11,17 +11,60 @@ import {
11
11
  providerAccountsResetAt,
12
12
  } from '../../../shared/provider-accounts.mjs';
13
13
  import { withProviderAccount, hasExplicitProviderAuthBinding } from '../../../shared/provider-auth-binding.mjs';
14
- import { retryAfterMsFromError, isExplicitUserAbortError, typedErrorCode } from './retry-classifier.mjs';
14
+ import { isExplicitUserAbortError, markProviderRecoveryExhausted } from './retry-classifier.mjs';
15
+ import { sleepWithAbort } from './retry-backoff.mjs';
16
+ import { QUOTA_KINDS, providerErrorKind } from './provider-error-kind.mjs';
15
17
  import { fetchOAuthUsageSnapshot, readCachedOAuthUsageSnapshot } from './oauth-usage.mjs';
16
18
 
17
- // Only an explicit quota refusal advances the roster; a generic 429, auth
18
- // refusal, policy denial or transport failure retains its existing recovery.
19
+ // Only an explicit quota / plan / billing refusal advances the roster as
20
+ // quota; a generic 429, overload, auth refusal, policy denial or transport
21
+ // failure retains its existing recovery.
19
22
  export function isAccountQuotaError(error) {
20
- const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
21
- if (status === 401 || status === 403 || isExplicitUserAbortError(error)) return false;
22
- return ['insufficient_quota', 'quota_exceeded', 'usage_limit_reached', 'usage_not_included'].includes(
23
- String(typedErrorCode(error) || error?.error?.type || '').toLowerCase()
24
- );
23
+ if (isExplicitUserAbortError(error)) return false;
24
+ const { kind, status } = providerErrorKind(error);
25
+ if (status === 401 || status === 403) return false;
26
+ return QUOTA_KINDS.has(kind);
27
+ }
28
+
29
+ // Kinds that say nothing about the account: the server (or the network) is
30
+ // temporarily unable to serve (or has no capacity in the requested service
31
+ // tier). They never block or rotate an account.
32
+ const SERVER_SIDE_KINDS = new Set(['overloaded', 'transient', 'service_tier_unavailable']);
33
+
34
+ // Provider-supplied roster cooldowns (`accountCooldownPolicy` on the provider
35
+ // object), used when the server named no delay:
36
+ // { floorMs, rateLimitedMs, quotaStepsMs, unknownMs, rotateOnRateLimit,
37
+ // waitForCooldownMs, blockedMs }
38
+ // blockedMs: cooldown for an `accountBlocked` refusal, which then rotates.
39
+ // quotaStepsMs escalates per consecutive quota refusal of one account (reset
40
+ // by a served answer). Absent → the inferred five minutes.
41
+ // rotateOnRateLimit rotates on a plain 429 rate limit; its advice, when
42
+ // present, becomes that account's cooldown.
43
+ // waitForCooldownMs: when every eligible account is only cooling down (no
44
+ // quota evidence), the send waits — cancellable, reported as reconnect
45
+ // progress — for the earliest cooldown while the send's total wait stays
46
+ // within this bound; beyond it the cooldown error surfaces. Absent → no wait.
47
+ const INFERRED_COOLDOWN_MS = 5 * 60_000;
48
+ const quotaStrikes = new Map();
49
+
50
+ function cooldownPolicyOf(instance) {
51
+ const policy = instance?.accountCooldownPolicy;
52
+ return policy && typeof policy === 'object' ? policy : null;
53
+ }
54
+
55
+ function finiteMs(value, fallback) {
56
+ const n = Number(value);
57
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
58
+ }
59
+
60
+ function policyCooldownMs(policy, strikeKey, reason) {
61
+ if (reason === 'rate_limited') return finiteMs(policy.rateLimitedMs, INFERRED_COOLDOWN_MS);
62
+ if (reason === 'quota' && Array.isArray(policy.quotaStepsMs) && policy.quotaStepsMs.length) {
63
+ const strikes = quotaStrikes.get(strikeKey) || 0;
64
+ quotaStrikes.set(strikeKey, strikes + 1);
65
+ return finiteMs(policy.quotaStepsMs[Math.min(strikes, policy.quotaStepsMs.length - 1)], INFERRED_COOLDOWN_MS);
66
+ }
67
+ return finiteMs(policy.unknownMs, INFERRED_COOLDOWN_MS);
25
68
  }
26
69
 
27
70
  // Burst throttles reopen within seconds. A 429 whose Retry-After points
@@ -77,6 +120,23 @@ function accountsExhaustedError(useCredits) {
77
120
  return error;
78
121
  }
79
122
 
123
+ // Every eligible account is only cooling down after a rate limit (no quota
124
+ // evidence): a rate limit, advising the earliest cooldown end as the wait.
125
+ // The pool already spent its cooldown wait, so this is a terminal verdict for
126
+ // the loop, not an unclassified failure.
127
+ function earliestCooldownMs(rows, now) {
128
+ const ends = rows.map((row) => Number(row.blockedUntil)).filter((until) => Number.isFinite(until) && until > now);
129
+ return ends.length ? Math.min(...ends) - now : null;
130
+ }
131
+
132
+ function accountsCoolingDownError(rows, now) {
133
+ const error = new Error('Every connected account is cooling down after a rate limit. Try again shortly.');
134
+ error.code = 'rate_limited';
135
+ const wait = earliestCooldownMs(rows, now);
136
+ if (wait != null) error.retryAfterMs = wait;
137
+ return markProviderRecoveryExhausted(error, { owner: 'account-pool', terminal: true });
138
+ }
139
+
80
140
  // Structured proof that the send ended because no account can serve `model`
81
141
  // (not merely because a generic 429 or a stopped loop surfaced an error).
82
142
  function quotaExhausted(error, providerName, model, rows, options = {}) {
@@ -200,15 +260,53 @@ function recordServingAccount(providerName, result, id, bindingId) {
200
260
  // retry delay. Anthropic and other subscriptions may use the same 429 code
201
261
  // for burst throttling and exhausted subscription windows, so the native
202
262
  // usage endpoint decides rather than a human-readable message.
263
+ // `until` is when the block lifts: the real quota reset when the provider
264
+ // named one, else the advised delay, else an inferred five minutes. A long
265
+ // Retry-After without quota evidence is a rate-limit cooldown: it rotates
266
+ // this send, blocks no longer than advised and is never quota evidence.
203
267
  async function accountQuotaExhausted(providerName, row, model, error, account) {
204
- const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
205
- const delay = retryAfterMsFromError(error);
268
+ const { kind, status, retryAfterMs: delay, resetAt } = providerErrorKind(error);
269
+ const now = Date.now();
270
+ const policy = cooldownPolicyOf(account(row.id));
271
+ const floorMs = policy ? finiteMs(policy.floorMs, 0) : 0;
272
+ const blockUntil = (withReset, reason = 'unknown') => {
273
+ if (withReset && resetAt > now) return { until: resetAt, source: 'retry-after' };
274
+ if (delay > 0) return { until: now + Math.max(delay, floorMs), source: 'retry-after' };
275
+ if (policy) {
276
+ const ms = policyCooldownMs(policy, `${providerName}:${row.id}`, reason);
277
+ return { until: now + Math.max(ms, floorMs), source: 'inferred' };
278
+ }
279
+ return { until: now + INFERRED_COOLDOWN_MS, source: 'inferred' };
280
+ };
281
+ if (SERVER_SIDE_KINDS.has(kind) || isExplicitUserAbortError(error)) {
282
+ return { exhausted: false, delay, evidence: false, ...blockUntil(false) };
283
+ }
284
+ // An account-level block (`accountBlocked`, e.g. the account needs
285
+ // verification): with a policy `blockedMs` that account cools for it and the
286
+ // roster rotates; never quota evidence. Without one it surfaces as before.
287
+ const blockedMs = policy ? finiteMs(policy.blockedMs, 0) : 0;
288
+ if (error?.accountBlocked === true && blockedMs > 0) {
289
+ return {
290
+ exhausted: true,
291
+ accountBlocked: true,
292
+ delay,
293
+ evidence: false,
294
+ until: now + Math.max(blockedMs, floorMs),
295
+ source: 'inferred',
296
+ };
297
+ }
206
298
  const typed = isAccountQuotaError(error);
207
- let exhausted = typed || (status === 429 && delay > QUOTA_WINDOW_RETRY_AFTER_MS);
299
+ if (typed) return { exhausted: true, delay, evidence: true, ...blockUntil(true, 'quota') };
300
+ // A policy with rotateOnRateLimit rotates on a plain rate limit too (the
301
+ // advice as its cooldown, else rateLimitedMs); never quota evidence.
302
+ if (policy?.rotateOnRateLimit === true && kind === 'rate_limited' && status === 429) {
303
+ return { exhausted: true, delay, evidence: false, ...blockUntil(false, 'rate_limited') };
304
+ }
305
+ let exhausted = status === 429 && delay > QUOTA_WINDOW_RETRY_AFTER_MS;
208
306
  // Proof of a spent quota: a typed quota refusal or an exhausted usage window
209
307
  // (a long Retry-After alone only rotates the roster, it proves nothing).
210
- let evidence = typed;
211
- if (!typed && status === 429) {
308
+ let evidence = false;
309
+ if (status === 429) {
212
310
  await fetchOAuthUsageSnapshot({ provider: providerName, accountId: row.id }, account(row.id), () => {}, {
213
311
  force: true,
214
312
  });
@@ -218,7 +316,7 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
218
316
  evidence = providerAccountExhausted({ ...entry, blockedUntil: 0 }, Date.now(), model, options);
219
317
  exhausted = exhausted || evidence || providerAccountExhausted(entry, Date.now(), model, options);
220
318
  }
221
- return { exhausted, delay, evidence };
319
+ return { exhausted, delay, evidence, ...(exhausted ? blockUntil(evidence, evidence ? 'quota' : 'unknown') : {}) };
222
320
  }
223
321
 
224
322
  // The pool's send: walks the eligible accounts until one answers, blocking
@@ -237,7 +335,9 @@ function poolSend(providerName, account) {
237
335
  let refusalScoped = false;
238
336
  // Mark `error` only when every account in `ids` (default: the roster) is
239
337
  // exhausted with quota evidence: observed in this send, or a full window.
240
- const markExhausted = (error, ids) => {
338
+ // Without an error of its own, the send reports quota exhaustion only on
339
+ // that evidence, and a rate-limit cooldown otherwise.
340
+ const exhaustion = (ids) => {
241
341
  const pool = readProviderAccountPool(providerName);
242
342
  // Manual policy: the eligible set is the selected account alone.
243
343
  const scope = ids || (providerAccountPolicy(pool) === 'manual' ? [pool.selectedId] : null);
@@ -254,9 +354,47 @@ function poolSend(providerName, account) {
254
354
  opts
255
355
  )
256
356
  );
257
- return all ? quotaExhausted(error, providerName, model, rows, opts) : error;
357
+ return { rows, opts, now, all };
358
+ };
359
+ const markExhausted = (error, ids) => {
360
+ const { rows, opts, now, all } = exhaustion(ids);
361
+ if (all) {
362
+ return quotaExhausted(error || accountsExhaustedError(opts.useCredits), providerName, model, rows, opts);
363
+ }
364
+ return error || accountsCoolingDownError(rows, now);
365
+ };
366
+ // Accounts this send blocked on a rate-limit cooldown, the time it spent
367
+ // waiting for cooldowns, and the provider's wait bound.
368
+ const cooled = new Set();
369
+ let waitedMs = 0;
370
+ let sends = 0;
371
+ const waitBoundMs = (ids) => {
372
+ const id = ids?.[0] || readProviderAccountPool(providerName).selectedId || 'default';
373
+ return finiteMs(cooldownPolicyOf(account(id))?.waitForCooldownMs, 0);
374
+ };
375
+ // A cooldown-only eligible set whose earliest cooldown ends inside the
376
+ // remaining wait budget is waited out; otherwise the send fails as before.
377
+ const waitOrThrow = async (error, ids) => {
378
+ const { rows, now, all } = exhaustion(ids);
379
+ const wait = all ? null : earliestCooldownMs(rows, now);
380
+ if (wait == null || waitedMs + wait > waitBoundMs(ids)) throw markExhausted(error, ids);
381
+ options.onStageChange?.('reconnecting', {
382
+ message: `Rate limited — every account is cooling down; retrying in ${Math.ceil(wait / 1000)}s`,
383
+ reason: 'rate_limited',
384
+ waitMs: wait,
385
+ retryAt: now + wait,
386
+ });
387
+ await sleepWithAbort(wait, options.signal);
388
+ waitedMs += wait;
389
+ const lifted = Date.now();
390
+ for (const row of readProviderAccountPool(providerName).accounts) {
391
+ if (cooled.has(row.id) && !(Number(row.blockedUntil) > lifted)) {
392
+ cooled.delete(row.id);
393
+ attempted.delete(row.id);
394
+ }
395
+ }
258
396
  };
259
- while (attempted.size < 20) {
397
+ while (attempted.size < 20 && sends < 20) {
260
398
  options.signal?.throwIfAborted();
261
399
  const pool = readProviderAccountPool(providerName);
262
400
  if (!pool.accounts.length) {
@@ -281,12 +419,15 @@ function poolSend(providerName, account) {
281
419
  probed = true;
282
420
  if (await probeRefusedAccounts(providerName, pool, model, account)) continue;
283
421
  }
284
- throw markExhausted(lastError || accountsExhaustedError(pool.useCredits === true));
422
+ await waitOrThrow(lastError);
423
+ continue;
285
424
  }
286
425
  attempted.add(row.id);
426
+ sends += 1;
287
427
  const bound = boundAccountSend(providerName, row, messages, options);
288
428
  try {
289
429
  const result = await account(row.id).send(bound.history, model, tools, bound.opts);
430
+ quotaStrikes.delete(`${providerName}:${row.id}`);
290
431
  // Headroom policy: a session's first pick, or its move off its account
291
432
  // on a limit, becomes the selection; continuing on its account does
292
433
  // not, so concurrent sessions never flip the selection back and forth.
@@ -330,24 +471,42 @@ function poolSend(providerName, account) {
330
471
  options.onStageChange?.('reconnecting', { message: 'Sign-in expired — switching to the next account' });
331
472
  continue;
332
473
  }
333
- const { exhausted, delay, evidence } = await accountQuotaExhausted(providerName, row, model, error, account);
334
- if (options.signal?.aborted || !exhausted) throw error;
335
- if (evidence) evidenced.add(row.id);
336
- await blockProviderAccount(
474
+ const { exhausted, evidence, until, source, accountBlocked } = await accountQuotaExhausted(
337
475
  providerName,
338
- row.id,
339
- Date.now() + (delay > 0 ? delay : 5 * 60_000),
476
+ row,
340
477
  model,
341
- delay > 0 ? 'retry-after' : 'inferred',
342
- { quota: evidence === true }
478
+ error,
479
+ account
343
480
  );
344
- if (bound.emitted() || error.unsafeToRetry || error.liveTextEmitted || error.emittedToolCall) throw error;
481
+ if (options.signal?.aborted || !exhausted) throw error;
482
+ if (evidence) evidenced.add(row.id);
483
+ else cooled.add(row.id);
484
+ await blockProviderAccount(providerName, row.id, until, model, source, { quota: evidence === true });
485
+ // An account block is unsafe to re-issue on THAT account only; with no
486
+ // output emitted, the next account may serve the same request.
487
+ if (
488
+ bound.emitted() ||
489
+ (error.unsafeToRetry && accountBlocked !== true) ||
490
+ error.liveTextEmitted ||
491
+ error.emittedToolCall
492
+ )
493
+ throw error;
345
494
  // Manual policy: the eligible set is the selected account alone.
346
- if (policy === 'manual') throw markExhausted(error, [row.id]);
495
+ if (policy === 'manual') {
496
+ await waitOrThrow(error, [row.id]);
497
+ lastError = error;
498
+ continue;
499
+ }
347
500
  lastError = error;
348
501
  switched = true;
349
502
  refusalScoped = accountExhaustedForModelOnly(providerName, row.id, model);
350
- options.onStageChange?.('reconnecting', { message: 'Quota exhausted — switching to the next account' });
503
+ options.onStageChange?.('reconnecting', {
504
+ message: evidence
505
+ ? 'Quota exhausted — switching to the next account'
506
+ : accountBlocked
507
+ ? 'Account blocked — switching to the next account'
508
+ : 'Rate limited (cooldown) — switching to the next account',
509
+ });
351
510
  }
352
511
  }
353
512
  throw markExhausted(lastError || new Error('Provider account attempts exhausted.'));
@@ -9,6 +9,7 @@
9
9
  import { PROVIDER_NONSTREAM_TOTAL_TIMEOUT_MS, createTimeoutSignal } from '../stall-policy.mjs';
10
10
  import { AnthropicFallbackTriggeredError, markProviderRecoveryExhausted } from './retry-classifier.mjs';
11
11
  import { normalizeAnthropicNonStreamingResponse } from './lib/anthropic-request-utils.mjs';
12
+ import { capForNonStreaming } from './anthropic-max-tokens-recovery.mjs';
12
13
  import { createAnthropicRecoveryGuards } from './anthropic-recovery-guards.mjs';
13
14
 
14
15
  /**
@@ -44,7 +45,7 @@ export function createAnthropicApiRecovery({
44
45
  try {
45
46
  onStageChange?.('requesting', { transport: 'non-streaming-fallback' });
46
47
  } catch {}
47
- const nonStreamingParams = { ...params, stream: false };
48
+ const nonStreamingParams = capForNonStreaming({ ...params, stream: false });
48
49
  const timeoutMs =
49
50
  Number(opts._nonStreamingTimeoutMs) > 0
50
51
  ? Number(opts._nonStreamingTimeoutMs)
@@ -16,10 +16,12 @@ import { fastModeAvailable } from './anthropic-fast-mode.mjs';
16
16
  import { applyAnthropicEffortToBody, shouldIncludeEffortBeta } from './anthropic-effort.mjs';
17
17
  import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
18
18
  import {
19
+ anthropicThinkingActive,
19
20
  applyAnthropicCacheMarkers,
20
21
  clampAnthropicThinkingBudget as clampThinkingBudgetTokens,
21
22
  resolveAnthropicCacheTtls as resolveCacheTtls,
22
23
  resolveAnthropicMessageCacheSlots,
24
+ sharedCachePrefixBoundary,
23
25
  toAnthropicToolChoice,
24
26
  } from './lib/anthropic-request-utils.mjs';
25
27
  import {
@@ -54,17 +56,21 @@ export async function buildAnthropicApiRequest({
54
56
  opts,
55
57
  fastModeLatched,
56
58
  modelInfo = null,
59
+ // The Claude id a gateway slug stands for (OpenRouter `anthropic/claude-opus-4.8`
60
+ // → `claude-opus-4.8`): the output cap and the thinking/effort shape are read
61
+ // from it, while the wire still names `useModel`.
62
+ capabilityModel = useModel,
63
+ // A gateway-supplied output limit for the model; wins over the Anthropic catalog.
64
+ modelMaxOutputTokens = opts?.modelMaxOutputTokens ?? null,
57
65
  }) {
58
66
  // A user-defined provider's reasoning comes from its own model row only:
59
67
  // the global by-model-id capability index and the Claude-id ladders are
60
68
  // first-party knowledge and must not leak onto (or from) a custom endpoint.
61
69
  const isCustom = isCustomProviderName(name);
62
- const maxTokens = resolveMaxTokens(useModel);
63
- const effortProjection = projectEffortConfiguration(messages, name, useModel, {
64
- ...config,
65
- ...opts,
66
- disableBetaHeaders: config?.disableBetaHeaders,
67
- });
70
+ const maxTokens = resolveMaxTokens(capabilityModel, { modelMaxOutputTokens });
71
+ const effortOptions = { ...config, ...opts, disableBetaHeaders: config?.disableBetaHeaders };
72
+ const gatewayEffort = typeof config?.messagesEffort === 'function' ? config.messagesEffort(opts) : null;
73
+ const effortProjection = projectEffortConfiguration(messages, name, useModel, effortOptions);
68
74
  const ttls = resolveCacheTtls(opts);
69
75
 
70
76
  const systemMsgs = messages.filter((m) => m.role === 'system');
@@ -73,20 +79,29 @@ export async function buildAnthropicApiRequest({
73
79
  // (cacheTier:'tier3') at ttls.tier3 — each its own system content block.
74
80
  const systemBlocks = buildSystemBlocks(systemMsgs, ttls.system, ttls.tier3);
75
81
 
76
- // Message-tail cache budget (4-BP layout + ANTHROPIC_MSG_SLOTS) is
77
- // shared with anthropic-oauth — see resolveAnthropicMessageCacheSlots.
78
- const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls);
79
82
  // Tools are resolved BEFORE the messages: the lowering needs the final
80
83
  // tool list to drop tool_reference blocks whose tool no longer ships
81
84
  // in this request (otherwise the API rejects the whole turn).
82
- const requestTools = requestAnthropicTools(tools, chatMsgs, opts);
85
+ const requestTools = requestAnthropicTools(tools, chatMsgs, opts, name);
86
+ // Message-tail cache budget (4-BP layout + ANTHROPIC_MSG_SLOTS) is
87
+ // shared with anthropic-oauth — see resolveAnthropicMessageCacheSlots.
88
+ const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls, { tools: requestTools });
83
89
  // Build → sanitize (once, inside toAnthropicMessages) → mark. Markers
84
90
  // are applied to the FINAL sanitized array by invariant, so block
85
91
  // drops / inserts / reorders performed by the sanitizer can never move
86
92
  // or delete a marked block. NEVER sanitize again after this.
93
+ const lowerHistory = (history, projection) =>
94
+ lowerAnthropicEffortHistory(
95
+ history.filter((m) => m.role !== 'system'),
96
+ (segment) => toAnthropicMessages(segment, requestTools, name),
97
+ projection
98
+ );
87
99
  const anthropicMessages = applyAnthropicCacheMarkers(
88
- lowerAnthropicEffortHistory(chatMsgs, (segment) => toAnthropicMessages(segment, requestTools), effortProjection),
89
- messageCacheSlots
100
+ lowerHistory(chatMsgs, effortProjection),
101
+ messageCacheSlots,
102
+ sharedCachePrefixBoundary(messages, opts, (prefix) =>
103
+ lowerHistory(prefix, projectEffortConfiguration(prefix, name, useModel, effortOptions))
104
+ )
90
105
  );
91
106
 
92
107
  const params = {
@@ -108,10 +123,9 @@ export async function buildAnthropicApiRequest({
108
123
  // tool_choice only when tools are actually present (Anthropic rejects
109
124
  // tool_choice without tools). 'none' rides the hard-cap final turn to
110
125
  // forbid tool USE while keeping the tools prefix stable for cache reuse.
111
- if (params.tools) {
112
- const toolChoice = toAnthropicToolChoice(opts.toolChoice);
113
- if (toolChoice) params.tool_choice = toolChoice;
114
- }
126
+ // The key is reserved here (wire key order) and resolved once thinking is
127
+ // known: forced any/tool is invalid while thinking is enabled.
128
+ if (params.tools) params.tool_choice = undefined;
115
129
  const hasDeferredTools = Array.isArray(params.tools) && params.tools.some((tool) => tool?.defer_loading === true);
116
130
  // Known tool names for the shared parseSSEStream leaked-tool-call guard
117
131
  // (same guard fixes both Anthropic providers). Recovered leaked calls
@@ -129,16 +143,30 @@ export async function buildAnthropicApiRequest({
129
143
  });
130
144
  if (fields.thinking) params.thinking = fields.thinking;
131
145
  if (fields.output_config) params.output_config = { ...(params.output_config || {}), ...fields.output_config };
132
- } else {
146
+ } else if (gatewayEffort !== 'none') {
147
+ // A gateway's own reasoning control (`messagesEffort`) replaces the turn
148
+ // effort; 'none' sends no thinking at all.
149
+ const turnOpts = effortProjection ? { ...opts, effort: effortProjection.initialEffort } : opts;
133
150
  applyAnthropicEffortToBody(params, {
134
- model: useModel,
135
- opts: effortProjection ? { ...opts, effort: effortProjection.initialEffort } : opts,
151
+ model: capabilityModel,
152
+ opts: gatewayEffort ? { ...turnOpts, effort: gatewayEffort } : turnOpts,
136
153
  maxTokens,
137
154
  clampThinkingBudgetTokens,
138
155
  logTag: name,
139
156
  progressUpdates: config?.disableBetaHeaders !== true,
140
157
  });
141
158
  }
159
+ if (params.tools) {
160
+ const toolChoice = toAnthropicToolChoice(opts.toolChoice, {
161
+ thinking: anthropicThinkingActive(params),
162
+ disableParallelToolUse: opts.disableParallelToolUse === true,
163
+ // A gateway that cannot honour disable_parallel_tool_use declares so.
164
+ supportsDisableParallelToolUse: config?.supportsDisableParallelToolUse !== false,
165
+ onThinkingConflict: opts.toolChoiceThinkingConflict,
166
+ });
167
+ if (toolChoice) params.tool_choice = toolChoice;
168
+ else delete params.tool_choice;
169
+ }
142
170
  // Fast mode → speed: "fast" on models Anthropic marks as speed-capable.
143
171
  // Suppressed while the fast capacity pool is cooling down.
144
172
  let fastLatched = fastModeLatched;
@@ -169,7 +197,7 @@ export async function buildAnthropicApiRequest({
169
197
  fastMode: fastLatched,
170
198
  toolSearch: hasDeferredTools,
171
199
  // Custom: the beta follows the resolver's output, not the model id.
172
- effort: isCustom ? Boolean(params.output_config?.effort) : shouldIncludeEffortBeta(useModel, opts),
200
+ effort: isCustom ? Boolean(params.output_config?.effort) : shouldIncludeEffortBeta(capabilityModel, opts),
173
201
  serverFallback: params.fallbacks === 'default',
174
202
  thinkingDisplayUpdates: params.thinking?.display === 'updates',
175
203
  }),