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
@@ -7,17 +7,16 @@ import { noteErrorUsage } from './lib/note-error-usage.mjs';
7
7
  import { retryReasonText, retryStatusLine } from '../../../shared/err-text.mjs';
8
8
  import {
9
9
  TERMINAL_EDGE_STATUSES,
10
- headerValue,
11
10
  boundedCauseChain,
12
11
  classifyError,
13
- isPermanentQuotaError,
14
12
  isStaleKeepAliveError,
15
13
  isToolInputCut,
16
14
  retryAfterMsFromError,
17
15
  } from './retry-classification.mjs';
16
+ import { DETERMINISTIC_KINDS, QUOTA_KINDS, errorHeaderValue, providerErrorKind } from './provider-error-kind.mjs';
17
+ import { adviceGatedRetry } from './retry-midstream.mjs';
18
18
  import {
19
- ANTHROPIC_MAX_CONSECUTIVE_529,
20
- AnthropicFallbackTriggeredError,
19
+ ModelFallbackTriggeredError,
21
20
  DEFAULT_BACKOFF_MS,
22
21
  DEFAULT_MAX_ATTEMPTS,
23
22
  jitterDelayMs,
@@ -64,11 +63,16 @@ export function createStreamSafetyStamps() {
64
63
 
65
64
  const providerRecoveryExhaustedErrors = new WeakSet();
66
65
 
67
- export function markProviderRecoveryExhausted(error, { owner = 'provider', attempts = null } = {}) {
66
+ // `terminal: true` records a provider VERDICT that recovery is over (a policy
67
+ // veto such as an unadvised advice-gated overload, or the repeated-overload
68
+ // stop), not merely a spent ladder: outer layers end recovery on it instead
69
+ // of opening another budget.
70
+ export function markProviderRecoveryExhausted(error, { owner = 'provider', attempts = null, terminal = false } = {}) {
68
71
  if (!error || (typeof error !== 'object' && typeof error !== 'function')) return error;
69
72
  providerRecoveryExhaustedErrors.add(error);
70
73
  try {
71
74
  error.providerRecoveryExhausted = true;
75
+ if (terminal) error.providerRecoveryTerminal = true;
72
76
  if (owner) error.providerRecoveryOwner = String(owner);
73
77
  const count = Number(attempts);
74
78
  if (Number.isFinite(count) && count > 0) error.providerRecoveryAttempts = Math.floor(count);
@@ -85,18 +89,25 @@ export function isProviderRecoveryExhausted(error) {
85
89
  );
86
90
  }
87
91
 
92
+ /** True when a provider layer ruled that no further recovery may run (see markProviderRecoveryExhausted). */
93
+ export function isProviderRecoveryTerminal(error) {
94
+ if (!error || (typeof error !== 'object' && typeof error !== 'function')) return false;
95
+ return boundedCauseChain(error).some((candidate) => candidate?.providerRecoveryTerminal === true);
96
+ }
97
+
88
98
  function throwIfRetryAborted(signal) {
89
99
  if (!signal?.aborted) return;
90
100
  const reason = signal.reason;
91
101
  throw reason instanceof Error ? reason : new Error('withRetry: aborted');
92
102
  }
93
103
 
94
- // A stale keep-alive socket, or (Grok) the first 5xx, rebuilds the HTTP
95
- // client to escape a poisoned HTTP/2 keep-alive pool (CF 522/523/524
96
- // included). 525/526 stay fatal.
97
- function recycleDispatcherAfter(caught, status) {
104
+ // Before a retry: a stale keep-alive socket rebuilds the HTTP client to escape
105
+ // a poisoned keep-alive pool. A caller whose upstream poisons its HTTP/2 pool
106
+ // on server faults (`recycleDispatcherOn5xx`) also rebuilds it on a retried
107
+ // 5xx (edge 522/523/524 included). Never runs for a failure that is not retried.
108
+ function recycleDispatcherAfter(caught, status, opts) {
98
109
  const poisoned =
99
- isStaleKeepAliveError(caught) || (status >= 500 && status < 600 && !TERMINAL_EDGE_STATUSES.has(status));
110
+ isStaleKeepAliveError(caught) || (opts.recycleDispatcherOn5xx === true && status >= 500 && status < 600);
100
111
  if (!poisoned) return;
101
112
  try {
102
113
  recycleLlmDispatcher();
@@ -105,23 +116,71 @@ function recycleDispatcherAfter(caught, status) {
105
116
  }
106
117
  }
107
118
 
119
+ // Opt-in: without a fallback model, the request loop stops after
120
+ // `overloadFallbackAfter` overloads instead of spending the whole
121
+ // attempt budget. A provider verdict: no outer layer re-opens recovery.
122
+ function repeated529Error(caught, opts, maxAttempts) {
123
+ const error = new Error('Repeated 529 Overloaded errors', { cause: caught });
124
+ error.name = 'Repeated529Error';
125
+ error.repeated529 = true;
126
+ error.status = Number(caught?.status || caught?.httpStatus || 529) || 529;
127
+ error.httpStatus = error.status;
128
+ error.code = 'overloaded_error';
129
+ if (caught?.headers) error.headers = caught.headers;
130
+ return markProviderRecoveryExhausted(error, {
131
+ owner: opts.recoveryOwner || 'withRetry',
132
+ attempts: maxAttempts,
133
+ terminal: true,
134
+ });
135
+ }
136
+
137
+ // Deterministic refusals of this request (auth, permission, policy, invalid
138
+ // request, context/size) end the loop before any status, header or overload
139
+ // rule may re-issue it. Quota kinds are judged separately below.
140
+ function deterministicRefusal(errorKind, kind, status, opts) {
141
+ if (!DETERMINISTIC_KINDS.has(errorKind) || QUOTA_KINDS.has(errorKind)) return false;
142
+ // Opt-in: a status-only 499 is retried (classifyError's retry499 verdict;
143
+ // a typed code on the 499 keeps it final).
144
+ return !(status === 499 && opts.retry499 === true && kind === 'transient');
145
+ }
146
+
147
+ // The overload counter (`overloadFallbackAfter`) counts every overload of the
148
+ // send; intervening non-overload failures do not reset it.
149
+ function isOverloadFailure(status, errorKind) {
150
+ return status === 529 || errorKind === 'overloaded';
151
+ }
152
+
108
153
  // Decides whether `caught` may be re-issued: returns the error to throw (the
109
154
  // caught one, the exhausted-marked one on the last attempt, or the model
110
- // fallback trigger) or null when the next attempt may run.
111
- function retryVeto(caught, { opts, attempt, maxAttempts, state }) {
155
+ // fallback trigger) or null when the next attempt may run — and only then
156
+ // rebuilds a poisoned HTTP pool for it.
157
+ function retryVeto(caught, context) {
158
+ const verdict = retryVerdict(caught, context);
159
+ if (verdict === null) {
160
+ const status = Number(caught?.httpStatus || caught?.status || caught?.response?.status || 0);
161
+ recycleDispatcherAfter(caught, status, context.opts);
162
+ }
163
+ return verdict;
164
+ }
165
+
166
+ function retryVerdict(caught, { opts, attempt, maxAttempts, state }) {
112
167
  const lastAttempt = attempt === maxAttempts - 1;
168
+ // `finalOnExhaust`: the caller's ladder is the whole recovery, so a spent
169
+ // ladder is a provider-terminal verdict no outer layer re-opens.
113
170
  const exhaustedError = () =>
114
- maxAttempts > 1
171
+ maxAttempts > 1 || opts.finalOnExhaust === true
115
172
  ? markProviderRecoveryExhausted(caught, {
116
173
  owner: opts.recoveryOwner || 'withRetry',
117
174
  attempts: maxAttempts,
175
+ terminal: opts.finalOnExhaust === true,
118
176
  })
119
177
  : caught;
120
178
  // A nested provider layer already spent its complete recovery budget.
121
179
  // Never multiply that budget by replaying it from an outer withRetry.
122
180
  if (isProviderRecoveryExhausted(caught)) return caught;
123
181
  const status = Number(caught?.httpStatus || caught?.status || caught?.response?.status || 0);
124
- const kind = classifyError(caught);
182
+ const kind = classifyError(caught, { retry499: opts.retry499 === true });
183
+ const errorKind = providerErrorKind(caught).kind;
125
184
  // Hard replay boundary: a retry RE-ISSUES the request, so it is denied
126
185
  // once visible output was relayed or a tool call was dispatched. Whether
127
186
  // an eligible failure is actually retried remains the typed question
@@ -131,40 +190,67 @@ function retryVeto(caught, { opts, attempt, maxAttempts, state }) {
131
190
  // split-call notice; re-issuing the identical request here would tend to be
132
191
  // cut at the same place again.
133
192
  if (isToolInputCut(caught)) return caught;
134
- recycleDispatcherAfter(caught, status);
193
+ const overload = isOverloadFailure(status, errorKind);
194
+ // Deterministic refusals first: no status, header (x-should-retry:true
195
+ // included) or overload rule below may re-issue them.
196
+ if (deterministicRefusal(errorKind, kind, status, opts)) return caught;
197
+ // An origin-TLS edge failure (525/526) is final whatever header, advice or
198
+ // overload code accompanies it.
199
+ if (TERMINAL_EDGE_STATUSES.has(status)) return caught;
200
+ // An explicit quota / plan / billing refusal cannot recover by replaying
201
+ // the same request, whatever Retry-After the server attached. A 429 whose
202
+ // only quota evidence is header-derived (providerQuota / quotaExceeded) is
203
+ // still retried for non-subscriber callers (Enterprise/PAYG
204
+ // 429s); a typed quota/billing code never is.
205
+ if (QUOTA_KINDS.has(errorKind)) {
206
+ const typedQuota = QUOTA_KINDS.has(providerErrorKind(caught, { quotaFlags: false }).kind);
207
+ if (typedQuota || opts.retry429 === false || status !== 429) return caught;
208
+ }
135
209
  // Background calls (titles and the like) never retry an overload: during a
136
210
  // capacity cascade every retry multiplies gateway load, and nobody is
137
211
  // waiting on the result.
138
212
  if (status === 529 && opts.retry529 === false) return caught;
139
- // x-should-retry:false is an explicit server veto on retrying and is
140
- // honored as-is. Keep this ahead of status defaults, including the
141
- // request-local 429 path.
142
- const shouldRetryHeader = String(
143
- headerValue(caught?.headers || caught?.response?.headers || caught?.data?.responseHeaders, 'x-should-retry') || ''
144
- ).toLowerCase();
145
- if (shouldRetryHeader === 'false') return caught;
146
- // Anthropic's non-standard positive override outranks ordinary status
147
- // classification. Keep subscription OAuth 429 fail-fast ownership:
148
- // retry429:false is the Max/Pro gate and must not wait for that window.
149
- if (opts.provider === 'anthropic' && shouldRetryHeader === 'true' && !(status === 429 && opts.retry429 === false)) {
150
- return lastAttempt ? exhaustedError() : null;
213
+ // A caller may opt into advice-gated overload / generic-429 retries. The
214
+ // denial is the provider's verdict: outer recovery layers honor it.
215
+ if (adviceGatedRetry(caught, opts) === false) {
216
+ return markProviderRecoveryExhausted(caught, {
217
+ owner: opts.recoveryOwner || 'withRetry',
218
+ attempts: attempt + 1,
219
+ terminal: true,
220
+ });
151
221
  }
152
- // The optional model fallback fires on the third 529. This remains opt-in:
153
- // providers pass fallbackModel only when the caller set one. The hard
154
- // progress veto above must run first so fallback can never replay partial
155
- // thinking/tool output.
156
- if (status === 529 && opts.fallbackModel && opts.fallbackModel !== opts.model) {
222
+ // Overload first: counted and retried ahead of the x-should-retry veto.
223
+ // The optional model fallback fires after `overloadFallbackAfter`
224
+ // overloads in the send (opt-in: providers pass fallbackModel only when
225
+ // the caller set one); without one, `stopAfterRepeated529` ends the loop
226
+ // there instead. The hard progress veto above runs first so neither can
227
+ // replay partial thinking/tool output. `overloadState` shares the count
228
+ // across a send.
229
+ if (overload) {
230
+ // Subscriber 429 fail-fast ownership also covers an overload-coded 429.
231
+ if (status === 429 && opts.retry429 === false) return caught;
157
232
  state.consecutive529Errors += 1;
158
- if (state.consecutive529Errors >= ANTHROPIC_MAX_CONSECUTIVE_529) {
159
- return new AnthropicFallbackTriggeredError(opts.model, opts.fallbackModel, caught);
233
+ const limit = Number(opts.overloadFallbackAfter);
234
+ if (Number.isFinite(limit) && limit > 0 && state.consecutive529Errors >= limit) {
235
+ if (opts.fallbackModel && opts.fallbackModel !== opts.model) {
236
+ return new ModelFallbackTriggeredError(opts.model, opts.fallbackModel, caught);
237
+ }
238
+ if (opts.stopAfterRepeated529 === true) return repeated529Error(caught, opts, maxAttempts);
160
239
  }
240
+ return lastAttempt ? exhaustedError() : null;
241
+ }
242
+ // x-should-retry:false is an explicit server veto on retrying and is
243
+ // honored as-is, ahead of status defaults including the 429 path.
244
+ const shouldRetryHeader = String(errorHeaderValue(caught, 'x-should-retry') || '').toLowerCase();
245
+ if (shouldRetryHeader === 'false') return caught;
246
+ // A transport that opts in (honorShouldRetryTrue) lets the non-standard
247
+ // positive override outrank ordinary status classification — except for
248
+ // subscribers (retry429:false), whose fail-fast ownership holds for every status.
249
+ if (opts.honorShouldRetryTrue === true && shouldRetryHeader === 'true' && opts.retry429 !== false) {
250
+ return lastAttempt ? exhaustedError() : null;
161
251
  }
162
252
  if (status === 429) {
163
253
  if (opts.retry429 === false) return caught;
164
- // A deterministic quota refusal cannot recover by replaying the same
165
- // request. An explicit server retry window outranks message-text quota
166
- // heuristics: RESOURCE_EXHAUSTED + Retry-After/RetryInfo is transient.
167
- if (retryAfterMsFromError(caught) == null && isPermanentQuotaError(caught)) return caught;
168
254
  // Retry only this request. Admission concurrency is fixed and is never
169
255
  // reduced by rate limits. Output/tool stamps above remain a hard replay
170
256
  // boundary.
@@ -184,8 +270,12 @@ function retryOptions(opts) {
184
270
  perAttemptTimeoutMs: Number(opts.perAttemptTimeoutMs || 0),
185
271
  perAttemptLabel: opts.perAttemptLabel || 'provider request',
186
272
  retryJitterRatio: Number(opts.retryJitterRatio ?? PROVIDER_RETRY_JITTER_RATIO),
187
- retryJitterMode: opts.retryJitterMode === 'positive' ? 'positive' : 'symmetric',
273
+ retryJitterMode: ['positive', 'negative'].includes(opts.retryJitterMode) ? opts.retryJitterMode : 'symmetric',
188
274
  sleepFn: typeof opts.sleepFn === 'function' ? opts.sleepFn : undefined,
275
+ // Opt-in: positive jitter on server advice, and advice as a
276
+ // floor under — never a replacement for — the current backoff step.
277
+ retryAfterJitterRatio: Math.max(0, Number(opts.retryAfterJitterRatio) || 0),
278
+ adviceAtLeastBackoff: opts.adviceAtLeastBackoff === true,
189
279
  };
190
280
  }
191
281
 
@@ -193,11 +283,17 @@ function retryOptions(opts) {
193
283
  // the jittered backoff step. Retry-After is a server-mandated minimum. Do
194
284
  // not cap, shorten, or jitter it; cancellation remains active throughout
195
285
  // the full wait.
196
- function retryWaitMs(attempt, { nextDelayMs, nextDelayReason }, { backoffMs, retryJitterRatio, retryJitterMode }) {
197
- const rawWait = nextDelayMs ?? backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? 0;
198
- return nextDelayReason === 'retry-after'
199
- ? Math.max(0, rawWait)
200
- : jitterDelayMs(rawWait, retryJitterRatio, retryJitterMode);
286
+ function retryWaitMs(
287
+ attempt,
288
+ { nextDelayMs, nextDelayReason },
289
+ { backoffMs, retryJitterRatio, retryJitterMode, retryAfterJitterRatio = 0, adviceAtLeastBackoff = false }
290
+ ) {
291
+ const step = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? 0;
292
+ if (nextDelayReason !== 'retry-after') return jitterDelayMs(nextDelayMs ?? step, retryJitterRatio, retryJitterMode);
293
+ let wait = Math.max(0, nextDelayMs ?? 0);
294
+ if (adviceAtLeastBackoff) wait = Math.max(wait, jitterDelayMs(step, retryJitterRatio, retryJitterMode));
295
+ // Positive-only jitter never shortens the advice.
296
+ return retryAfterJitterRatio > 0 ? jitterDelayMs(wait, retryAfterJitterRatio, 'positive') : wait;
201
297
  }
202
298
 
203
299
  /**
@@ -260,7 +356,14 @@ export async function withRetry(fn, opts = {}) {
260
356
  let lastErr = null;
261
357
  let nextDelayMs = null;
262
358
  let nextDelayReason = null;
263
- const state = { consecutive529Errors: Math.max(0, Number(opts.initialConsecutive529Errors) || 0) };
359
+ // `opts.overloadState` ({ consecutive529Errors }) lets one logical send share
360
+ // its overload count across every withRetry it runs (streaming, recovery
361
+ // cycles, non-streaming fallback), so the configured fallback still fires.
362
+ const shared = opts.overloadState && typeof opts.overloadState === 'object' ? opts.overloadState : null;
363
+ const state = shared || {};
364
+ if (!Number.isFinite(state.consecutive529Errors)) {
365
+ state.consecutive529Errors = Math.max(0, Number(opts.initialConsecutive529Errors) || 0);
366
+ }
264
367
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
265
368
  throwIfRetryAborted(signal);
266
369
  if (attempt > 0) {
@@ -11,6 +11,46 @@ import {
11
11
  isTransientErrorCode,
12
12
  typedStatusFrom,
13
13
  } from './retry-classification.mjs';
14
+ import { DETERMINISTIC_KINDS, providerErrorKind } from './provider-error-kind.mjs';
15
+
16
+ // A 429 that is an overload or an ordinary rate limit is request-local: it is
17
+ // re-issued under the bounded mid-stream budget (the server's advice, when it
18
+ // sent one, sets the wait). Quota / plan / billing refusals never are.
19
+ // The advice-honoring mid-stream wait lives with the backoff schedule.
20
+ export { midstreamRetryDelayMs } from './retry-backoff.mjs';
21
+
22
+ // Per-provider parity, as policy options (default false = retry under the
23
+ // bounded budget with ordinary backoff):
24
+ // overloadRetryRequiresAdvice — an overload (server_is_overloaded,
25
+ // overloaded_error, 529, statusless overload event) retries ONLY when the
26
+ // server gave advice (Retry-After / retryAfterMs / RetryInfo).
27
+ // generic429RetryRequiresAdvice — a rate limit without a typed
28
+ // rate_limit_exceeded / slow_down code retries ONLY on advice.
29
+ // A typed rate_limit_exceeded / slow_down always retries (advice, else the
30
+ // delay its message names, else local backoff). Returns false when the
31
+ // policy denies the retry, null when it has no say.
32
+ const TYPED_RATE_LIMIT_CODES = new Set(['rate_limit_exceeded', 'slow_down']);
33
+ export function adviceGatedRetry(err, policy = {}) {
34
+ const { kind, code, retryAfterMs } = providerErrorKind(err);
35
+ const advised = retryAfterMs != null;
36
+ if (kind === 'overloaded' && policy.overloadRetryRequiresAdvice === true && !advised) return false;
37
+ if (
38
+ kind === 'rate_limited' &&
39
+ !TYPED_RATE_LIMIT_CODES.has(code) &&
40
+ policy.generic429RetryRequiresAdvice === true &&
41
+ !advised
42
+ ) {
43
+ return false;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function retryable429(err, policy) {
49
+ if (policy.retry429 === false) return false;
50
+ if (adviceGatedRetry(err, policy) === false) return false;
51
+ const { kind } = providerErrorKind(err);
52
+ return kind === 'overloaded' || kind === 'rate_limited';
53
+ }
14
54
 
15
55
  // F) Retry-budget profiles as DATA. The numbers live ONLY here now.
16
56
  // ws.*Retries (5) — one Responses stream retry budget.
@@ -88,19 +128,24 @@ function isWebSocketConnectionLimit(err) {
88
128
  // bucket. Buckets need typed codes; the DEFAULT for an unrecognized (or
89
129
  // absent) code is the bounded retry, with fatal refusal codes staying
90
130
  // terminal.
91
- function classifyResponseFailed(failed, attemptIndex, policy) {
131
+ function classifyResponseFailed(failed, attemptIndex, policy, err = null) {
92
132
  const detail = failed?.response?.error || failed?.error || failed;
93
133
  const failedStatus = typedStatusFrom(detail, failed?.response, failed);
134
+ // Deterministic codes outrank a numeric 5xx on the same payload.
135
+ const keys = [detail?.code, detail?.type]
136
+ .map((field) => (typeof field === 'string' ? field.trim().toLowerCase() : ''))
137
+ .filter(Boolean);
138
+ const codeClassifier = keys.map((key) => RESPONSE_FAILED_CODE_CLASSIFIERS.get(key)).find(Boolean) || null;
139
+ if (!codeClassifier && keys.some((key) => WIRE_ERROR_FATAL_CODES.has(key))) return null;
140
+ const subject = { responseFailed: failed, headers: err?.headers, retryAfterMs: err?.retryAfterMs };
141
+ if (DETERMINISTIC_KINDS.has(providerErrorKind(subject).kind)) return null;
142
+ // Statusless overload / generic rate-limit events follow the advice gate.
143
+ if (adviceGatedRetry(subject, policy) === false) return null;
94
144
  if (failedStatus >= 500 && failedStatus < 600) {
95
145
  if (TERMINAL_EDGE_STATUSES.has(failedStatus)) return null;
96
146
  return _allowMidstream(`http_${failedStatus}`, attemptIndex, policy);
97
147
  }
98
- for (const field of [detail?.code, detail?.type]) {
99
- const key = typeof field === 'string' ? field.trim().toLowerCase() : '';
100
- const classifier = RESPONSE_FAILED_CODE_CLASSIFIERS.get(key);
101
- if (classifier) return _allowMidstream(classifier, attemptIndex, policy);
102
- if (key && WIRE_ERROR_FATAL_CODES.has(key)) return null;
103
- }
148
+ if (codeClassifier) return _allowMidstream(codeClassifier, attemptIndex, policy);
104
149
  // A typed non-transient 4xx on the failure payload is a deterministic
105
150
  // refusal even without a recognized code string.
106
151
  if (failedStatus >= 400 && failedStatus < 500 && !TRANSIENT_STATUSES.has(failedStatus)) return null;
@@ -146,12 +191,19 @@ function _classifyMidstreamWs(err, state, attemptIndex, policy) {
146
191
  // NOMINAL closes count too: a pooled socket the server retires between
147
192
  // turns closes with 1000/1001/1005 and must reconnect fresh — treating
148
193
  // that as terminal killed the turn instead of reissuing it.
149
- if (!state.sawResponseCreated && !NON_TERMINAL_STREAM_CLOSE_CODES.has(closeCode)) return null;
194
+ // An EXPLICIT wire failure (response.failed / error event) before
195
+ // response.created exposed nothing either: it gets the same bounded WS
196
+ // retry budget, and its typed code decides below.
197
+ const failed = err?.responseFailed || state.responseFailedPayload;
198
+ if (!state.sawResponseCreated && !NON_TERMINAL_STREAM_CLOSE_CODES.has(closeCode) && !failed) return null;
150
199
  if (state.userAbort) return null;
151
200
 
152
201
  if (!err) return null;
153
202
  const status = Number(err?.httpStatus || 0);
154
- if (status === 401 || status === 403 || status === 429) return null;
203
+ if (status === 401 || status === 403) return null;
204
+ if (DETERMINISTIC_KINDS.has(providerErrorKind(err).kind)) return null;
205
+ if (status === 429) return retryable429(err, policy) ? _allowMidstream('http_429', attemptIndex, policy) : null;
206
+ if (status && adviceGatedRetry(err, policy) === false) return null;
155
207
  if (status >= 500 && status < 600) {
156
208
  if (TERMINAL_EDGE_STATUSES.has(status)) return null;
157
209
  return _allowMidstream(`http_${status}`, attemptIndex, policy);
@@ -167,8 +219,7 @@ function _classifyMidstreamWs(err, state, attemptIndex, policy) {
167
219
  return _allowMidstream(`ws_${closeCode}`, attemptIndex, policy);
168
220
  }
169
221
 
170
- const failed = err?.responseFailed || state.responseFailedPayload;
171
- if (failed) return classifyResponseFailed(failed, attemptIndex, policy);
222
+ if (failed) return classifyResponseFailed(failed, attemptIndex, policy, err);
172
223
  return null;
173
224
  }
174
225
 
@@ -181,8 +232,8 @@ const RESPONSE_FAILED_CODE_CLASSIFIERS = new Map([
181
232
  ['auth_expired', 'response_failed_auth_expired'],
182
233
  ['previous_response_not_found', 'previous_response_not_found'],
183
234
  ['websocket_connection_limit_reached', 'websocket_connection_limit'],
184
- // A rate limit is retried after the server-advised wait (the reference
185
- // client's RateLimitExceeded); a usage limit stays a fatal code.
235
+ // A rate limit is retried after the server-advised wait; a usage limit
236
+ // stays a fatal code.
186
237
  ['rate_limit_exceeded', 'response_failed_rate_limited'],
187
238
  ]);
188
239
 
@@ -197,7 +248,10 @@ function _classifyMidstreamSse(err, state, attemptIndex, policy) {
197
248
  if (!err) return null;
198
249
  const status = Number(err?.httpStatus || err?.status || err?.response?.status || 0);
199
250
  if (status === 401 || status === 403) return null;
200
- if (status === 429) return 'http_429';
251
+ if (DETERMINISTIC_KINDS.has(providerErrorKind(err).kind)) return null;
252
+ if (status === 429) return retryable429(err, policy) ? 'http_429' : null;
253
+ // Opt-in: overloads retry only on server advice.
254
+ if (adviceGatedRetry(err, policy) === false) return null;
201
255
  if (status >= 500 && status < 600) {
202
256
  if (TERMINAL_EDGE_STATUSES.has(status)) return null;
203
257
  return `http_${status}`;
@@ -322,13 +376,24 @@ export function shouldFallbackTransport(err, { signal, enabled = true } = {}) {
322
376
  // returns null. HTTP 401 is reserved for auth recovery and 426 for immediate
323
377
  // HTTPS fallback. The OpenAI OAuth caller opts out of 429 retries
324
378
  // (retry429:false); all other callers retain the historical retryable
325
- // UnexpectedStatus policy.
326
- export function classifyHandshakeError(err, { retry429 = true } = {}) {
379
+ // UnexpectedStatus policy. overloadRetryRequiresAdvice /
380
+ // generic429RetryRequiresAdvice (default false) opt a caller into
381
+ // advice-gated overload / generic-429 retries.
382
+ export function classifyHandshakeError(
383
+ err,
384
+ { retry429 = true, overloadRetryRequiresAdvice = false, generic429RetryRequiresAdvice = false } = {}
385
+ ) {
327
386
  if (!err) return null;
328
387
  const code = err.code || '';
329
388
  const status = Number(err.httpStatus || 0);
330
389
 
331
390
  if (status === 401 || status === 426 || (status === 429 && !retry429)) return null;
391
+ // Every deterministic refusal (quota, billing, auth, permission, policy,
392
+ // invalid request, context/size) is final, whatever 5xx or advice it carries.
393
+ if (DETERMINISTIC_KINDS.has(providerErrorKind(err).kind)) return null;
394
+ if (status && adviceGatedRetry(err, { overloadRetryRequiresAdvice, generic429RetryRequiresAdvice }) === false) {
395
+ return null;
396
+ }
332
397
  if (status > 0) {
333
398
  // Typed transient handshake statuses only. A 403/404/4xx upgrade refusal
334
399
  // is a deterministic decision: spending the retry budget on it cannot
@@ -0,0 +1,6 @@
1
+ export const TRANSPORT_SCOPE_DISPOSED: 'transport_scope_disposed';
2
+ export function forkTransportScope(sessionId: string, label?: string): string;
3
+ export function disposeTransportScope(
4
+ scope: string,
5
+ provider?: { releaseTransportScope?(scope: string): void } | null
6
+ ): void;
@@ -0,0 +1,28 @@
1
+ // transport-scope.mjs — a send's transport state scope, apart from its
2
+ // session identity. Providers key their session-scoped transport and
3
+ // continuation state (pooled sockets and their previous_response_id anchor,
4
+ // Codex turn-state routing tokens, sticky HTTP fallback, Cursor runs and
5
+ // conversations) by `opts.transportScope || opts.sessionId`, while prompt
6
+ // cache routing (prompt_cache_key, handshake session id) stays on
7
+ // `opts.sessionId`. A request forked from a session (/btw) runs in a scope of
8
+ // its own: it shares the session's prompt cache but reads and writes none of
9
+ // its transport state, and its scope is disposed once it is answered.
10
+ import { randomUUID } from 'node:crypto';
11
+
12
+ /** The reason a scope's provider connections are closed when it is disposed. */
13
+ export const TRANSPORT_SCOPE_DISPOSED = 'transport_scope_disposed';
14
+
15
+ /** A fresh transport scope for one request forked from `sessionId`. */
16
+ export function forkTransportScope(sessionId, label = 'fork') {
17
+ return `${sessionId}#${label}:${randomUUID()}`;
18
+ }
19
+
20
+ /** Close every connection and drop every transport record of `scope`. */
21
+ export function disposeTransportScope(scope, provider) {
22
+ if (!scope) return;
23
+ try {
24
+ globalThis.__mixdogCloseProviderConnectionsForSession?.(scope, TRANSPORT_SCOPE_DISPOSED);
25
+ } finally {
26
+ provider?.releaseTransportScope?.(scope);
27
+ }
28
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * wire-gateway.mjs — an API-key gateway that serves one account on several
3
+ * wires (OpenRouter, DeepSeek). Chat Completions and Responses go through the
4
+ * OpenAI-compatible engine, Anthropic Messages through an Anthropic engine
5
+ * bound to the gateway; wire-route.mjs picks the wire per model.
6
+ */
7
+ import { AnthropicProvider } from './anthropic.mjs';
8
+ import { OpenAICompatProvider, OPENAI_COMPAT_PRESETS } from './openai-compat.mjs';
9
+ import { originBoundFetch } from './lib/origin-bound-fetch.mjs';
10
+ import { withInclusiveUsage } from './lib/inclusive-usage.mjs';
11
+ import { getModelMetadataSync } from './model-catalog.mjs';
12
+ import { resolveWireRoute, sendOnRoute } from './wire-route.mjs';
13
+
14
+ // A Files API file-ID image anywhere in the history (tool results nest content).
15
+ function hasFileIdImage(value, depth = 0) {
16
+ if (!value || typeof value !== 'object' || depth > 6) return false;
17
+ if (Array.isArray(value)) return value.some((entry) => hasFileIdImage(entry, depth + 1));
18
+ const fileId = value.type === 'input_image' ? value.file_id : value.type === 'image' && value.source?.type === 'file' ? value.source.file_id : null;
19
+ if (typeof fileId === 'string' && fileId) return true;
20
+ return hasFileIdImage(value.content, depth + 1);
21
+ }
22
+
23
+ /**
24
+ * The per-request capability options a gateway's Messages engine hands the
25
+ * shared Anthropic request builder (anthropic-api-request.mjs):
26
+ * - `modelMaxOutputTokens`: the gateway's own output ceiling for the model
27
+ * (`config.modelMaxOutputTokens(model)`),
28
+ * - `messagesEffortProjection`: the gateway's own effort projection
29
+ * (`config.messagesEffortProjection`), replacing the Claude rules,
30
+ * - `anthropic-beta` (`config.fileIdImageBeta`) only on a request that
31
+ * carries a file-ID image; every other beta stays suppressed.
32
+ */
33
+ export function gatewayMessagesOpts(config, model, messages, opts) {
34
+ const out = { ...(opts || {}) };
35
+ const ceiling = Math.floor(Number(typeof config?.modelMaxOutputTokens === 'function' ? config.modelMaxOutputTokens(model) : 0));
36
+ if (ceiling > 0 && out.modelMaxOutputTokens === undefined) out.modelMaxOutputTokens = ceiling;
37
+ if (typeof config?.messagesEffortProjection === 'function') out.messagesEffortProjection = config.messagesEffortProjection;
38
+ if (config?.fileIdImageBeta && hasFileIdImage(messages)) {
39
+ out.requestHeaders = { 'anthropic-beta': config.fileIdImageBeta, ...(out.requestHeaders || {}) };
40
+ }
41
+ return out;
42
+ }
43
+
44
+ /**
45
+ * Anthropic engine bound to a gateway: only the gateway key is sent — never
46
+ * an ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from the environment — and only
47
+ * to the gateway origin. `config.authScheme: 'bearer'` sends the key as
48
+ * `Authorization: Bearer` (OpenRouter); the default is `x-api-key`. The client
49
+ * is rebuilt through here on every key change or reload.
50
+ * `config.capabilityModel(model)` maps the routing slug to the Claude id its
51
+ * capabilities are read from.
52
+ */
53
+ export class GatewayMessagesEngine extends AnthropicProvider {
54
+ // Document capability of this gateway's Messages route, declared by the
55
+ // gateway config. `nativePdf`: PDFs ride as document blocks (default: lowered
56
+ // to text). `nativeDocuments`: any document block at all; unset leaves
57
+ // non-PDF documents as they are, an explicit false (DeepSeek) means every
58
+ // document variant is lowered to text or rejected.
59
+ nativePdf = this.config.nativePdf === true;
60
+ nativeDocuments = this.config.nativeDocuments;
61
+ _sendOnce(messages, model, tools, opts) {
62
+ return super._sendOnce(messages, model, tools, gatewayMessagesOpts(this.config, model, messages, opts));
63
+ }
64
+ _createClient(apiKey) {
65
+ const client = super._createClient(apiKey);
66
+ if (this.config.authScheme === 'bearer') {
67
+ client.apiKey = null;
68
+ client.authToken = apiKey || null;
69
+ } else {
70
+ client.authToken = null;
71
+ }
72
+ client.fetch = originBoundFetch(new URL(this.config.baseURL).origin, this.config.fetchImpl, this.name);
73
+ return client;
74
+ }
75
+ _capabilityModel(model) {
76
+ return (typeof this.config.capabilityModel === 'function' && this.config.capabilityModel(model)) || model;
77
+ }
78
+ }
79
+
80
+ export class WireGatewayProvider {
81
+ // No document block on any gateway wire: a PDF is lowered to text.
82
+ nativePdf = false;
83
+ // Every wire reports inclusive input (Messages usage is converted).
84
+ static inputExcludesCache = false;
85
+ name;
86
+ config;
87
+ openai;
88
+ anthropic;
89
+
90
+ /**
91
+ * @param {string} name preset name (openai-compat-presets.mjs)
92
+ * @param {object} config provider config
93
+ * @param {object} messages the Messages engine: `{ baseURL, authScheme?, capabilityModel? }`
94
+ * @param {object} [compatConfig] extra OpenAI-compatible engine config
95
+ */
96
+ constructor(name, config = {}, messages, compatConfig = {}) {
97
+ this.name = name;
98
+ this.config = config;
99
+ this.openai = new OpenAICompatProvider(name, { ...config, ...compatConfig });
100
+ this.anthropic = new GatewayMessagesEngine({
101
+ ...config,
102
+ ...messages,
103
+ name,
104
+ disableBetaHeaders: true,
105
+ modelMaxOutputTokens: messages.modelMaxOutputTokens ?? ((model) => this._modelMaxOutputTokens(model)),
106
+ extraHeaders: { ...(OPENAI_COMPAT_PRESETS[name]?.extraHeaders || {}), ...(config.extraHeaders || {}) },
107
+ });
108
+ // The Chat engine's retry profile stays the account's loop policy.
109
+ if (this.openai.retryPolicy) this.retryPolicy = this.openai.retryPolicy;
110
+ }
111
+
112
+ resolveWireRoute(model, ctx = {}) {
113
+ return resolveWireRoute(this.name, model, { ...ctx, override: this.config?.modelWires?.[model] ?? null });
114
+ }
115
+
116
+ // The model's output ceiling: the gateway roster, else the shared catalog.
117
+ _modelMaxOutputTokens(model) {
118
+ const declared = Number(this.getCachedModelInfo(model)?.outputTokens ?? getModelMetadataSync(model, this.name)?.outputTokens);
119
+ return Number.isFinite(declared) && declared > 0 ? declared : null;
120
+ }
121
+
122
+ send(messages, model, tools, sendOpts) {
123
+ const useModel = model || this.openai.defaultModel;
124
+ // The resolved route is the only wire authority: a caller's own wire
125
+ // switches never reach an engine, and one clear "unsupported" refusal
126
+ // moves the send to the next wire once (sendOnRoute).
127
+ return sendOnRoute(this, useModel, tools, sendOpts, messages, (wire, history, opts) => {
128
+ if (wire === 'messages') return withInclusiveUsage(() => this.anthropic.send(history, useModel, tools, opts));
129
+ if (wire === 'responses') return this.openai.send(history, model, tools, { ...opts, compatWireApi: 'responses' });
130
+ return this.openai.send(history, model, tools, opts);
131
+ });
132
+ }
133
+
134
+ reloadApiKey() {
135
+ this.openai.reloadApiKey();
136
+ this.anthropic.reloadApiKey();
137
+ }
138
+
139
+ async listModels() {
140
+ return this.openai.listModels();
141
+ }
142
+
143
+ async isAvailable() {
144
+ return this.openai.isAvailable();
145
+ }
146
+
147
+ getCachedModelInfo(model) {
148
+ return this.openai.getCachedModelInfo(model);
149
+ }
150
+ }
151
+
152
+ /** The Anthropic SDK base for a gateway's OpenAI base: the SDK appends `/v1/messages`. */
153
+ export function messagesBaseFromOpenAIBase(baseURL) {
154
+ return String(baseURL || '')
155
+ .replace(/\/+$/, '')
156
+ .replace(/\/v1$/i, '');
157
+ }