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
@@ -4,6 +4,7 @@ import crypto from 'node:crypto';
4
4
  import http2 from 'node:http2';
5
5
  import { MAX_CONNECT_FRAME_BYTES, createCursorByteQueue } from './cursor-wire-guards.mjs';
6
6
  import { cursorClientVersion, warmCursorClientVersion } from './cursor-client-version.mjs';
7
+ import { retryAfterMsFromError } from './retry-classifier.mjs';
7
8
 
8
9
  export { warmCursorClientVersion };
9
10
 
@@ -40,7 +41,66 @@ function rpcHeaders(accessToken, path, unary) {
40
41
  };
41
42
  }
42
43
 
43
- export function cursorError(message, { status = 0, code = '', retryAfter = null } = {}) {
44
+ // HTTP-equivalent status for every Connect error code.
45
+ const CONNECT_CODE_STATUS = Object.freeze({
46
+ canceled: 499,
47
+ unknown: 500,
48
+ invalid_argument: 400,
49
+ deadline_exceeded: 504,
50
+ not_found: 404,
51
+ already_exists: 409,
52
+ permission_denied: 403,
53
+ resource_exhausted: 429,
54
+ failed_precondition: 400,
55
+ aborted: 409,
56
+ out_of_range: 400,
57
+ unimplemented: 501,
58
+ internal: 500,
59
+ unavailable: 503,
60
+ data_loss: 500,
61
+ unauthenticated: 401,
62
+ });
63
+
64
+ // Connect code for a plain HTTP failure that carried no Connect error body.
65
+ const HTTP_STATUS_CODE = Object.freeze({
66
+ 401: 'unauthenticated',
67
+ 403: 'permission_denied',
68
+ 404: 'not_found',
69
+ 429: 'resource_exhausted',
70
+ 500: 'internal',
71
+ 502: 'unavailable',
72
+ 503: 'unavailable',
73
+ 504: 'deadline_exceeded',
74
+ });
75
+
76
+ // Only explicit wording about the tool catalog or request payload being too
77
+ // large counts; resource_exhausted otherwise stays a throttle.
78
+ const REQUEST_TOO_LARGE_RE =
79
+ /\b(?:tool\s+(?:catalog|definitions?|schemas?)|tools|payload|request(?:\s+body)?)\b[^.\n]{0,80}?\b(?:too\s+(?:large|big)|exceed(?:s|ed)?\b[^.\n]{0,30}?\b(?:size|limit|maximum))/i;
80
+
81
+ // Rate/quota wording wins over the size match: "request exceeded the rate
82
+ // limit" is a throttle, not an oversized request.
83
+ const THROTTLE_CUE_RE = /too\s+many\s+requests|quota|rate[\s_-]*limit|throttl/i;
84
+
85
+ function firstValue(value) {
86
+ return Array.isArray(value) ? value[0] : value;
87
+ }
88
+
89
+ // Lowercased string-valued header map without HTTP/2 pseudo-headers.
90
+ function plainHeaders(source) {
91
+ const headers = {};
92
+ for (const [key, value] of Object.entries(source || {})) {
93
+ const first = firstValue(value);
94
+ if (key.startsWith(':') || first == null || typeof first === 'object') continue;
95
+ headers[key.toLowerCase()] = String(first);
96
+ }
97
+ return headers;
98
+ }
99
+
100
+ export function cursorError(
101
+ message,
102
+ { status = 0, code = '', cursorCode = code, headers = null, retryAfter = null, reauthRequired = false, detail } = {}
103
+ ) {
44
104
  const error = new Error(message);
45
105
  if (status) {
46
106
  error.status = status;
@@ -48,17 +108,32 @@ export function cursorError(message, { status = 0, code = '', retryAfter = null
48
108
  }
49
109
  if (code) {
50
110
  error.code = code;
51
- error.cursorCode = code;
111
+ error.cursorCode = cursorCode;
52
112
  }
113
+ if (detail != null) error.cursorDetail = detail;
114
+ if (reauthRequired) error.reauthRequired = true;
115
+ const errorHeaders = plainHeaders(headers);
53
116
  if (retryAfter != null && retryAfter !== '') {
54
- const headers = { 'retry-after': String(retryAfter) };
55
117
  error.retryAfter = retryAfter;
56
- error.headers = headers;
57
- error.response = { status, headers };
118
+ errorHeaders['retry-after'] = String(retryAfter);
119
+ }
120
+ if (Object.keys(errorHeaders).length) {
121
+ error.headers = errorHeaders;
122
+ error.response = { status, headers: errorHeaders };
123
+ const retryAfterMs = retryAfterMsFromError(error);
124
+ if (Number.isFinite(retryAfterMs)) error.retryAfterMs = retryAfterMs;
58
125
  }
59
126
  return error;
60
127
  }
61
128
 
129
+ function httpStatusError(status, headers) {
130
+ return cursorError(`Cursor request failed (${status})`, {
131
+ status,
132
+ code: HTTP_STATUS_CODE[status] || '',
133
+ headers,
134
+ });
135
+ }
136
+
62
137
  export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }) {
63
138
  const session = http2.connect(url);
64
139
  const request = session.request(rpcHeaders(accessToken, path, false));
@@ -66,7 +141,7 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
66
141
  let closeHandler = null;
67
142
  let closed = false;
68
143
  let status = 0;
69
- let retryAfter = null;
144
+ let responseHeaders = null;
70
145
  let closeError = null;
71
146
  let timeout = setTimeout(
72
147
  () => close(cursorError('Cursor connection timed out', { code: 'connection_timeout' })),
@@ -97,8 +172,7 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
97
172
  closed = true;
98
173
  clearTimeout(timeout);
99
174
  clearInterval(ping);
100
- closeError =
101
- error || (status >= 400 ? cursorError(`Cursor request failed (${status})`, { status, retryAfter }) : null);
175
+ closeError = error || (status >= 400 ? httpStatusError(status, responseHeaders) : null);
102
176
  try {
103
177
  request.close();
104
178
  } catch {}
@@ -120,7 +194,7 @@ export function openCursorStream({ accessToken, path = RUN_PATH, url = API_URL }
120
194
 
121
195
  request.on('response', (headers) => {
122
196
  status = Number(headers[':status'] || 0);
123
- retryAfter = headers['retry-after'] ?? null;
197
+ responseHeaders = headers;
124
198
  resetTimeout();
125
199
  });
126
200
  request.on('data', (chunk) => {
@@ -167,7 +241,7 @@ export async function callCursorUnary({ accessToken, path, body, url = API_URL,
167
241
  const request = session.request(rpcHeaders(accessToken, path, true));
168
242
  const chunks = [];
169
243
  let status = 0;
170
- let retryAfter = null;
244
+ let responseHeaders = null;
171
245
  let settled = false;
172
246
  const timeout = setTimeout(() => finish(new Error('Cursor request timed out')), timeoutMs);
173
247
  const finish = (error = null) => {
@@ -181,12 +255,12 @@ export async function callCursorUnary({ accessToken, path, body, url = API_URL,
181
255
  session.close();
182
256
  } catch {}
183
257
  if (error) reject(error);
184
- else if (status >= 400) reject(cursorError(`Cursor request failed (${status})`, { status, retryAfter }));
258
+ else if (status >= 400) reject(httpStatusError(status, responseHeaders));
185
259
  else resolve(Buffer.concat(chunks));
186
260
  };
187
261
  request.on('response', (headers) => {
188
262
  status = Number(headers[':status'] || 0);
189
- retryAfter = headers['retry-after'] ?? null;
263
+ responseHeaders = headers;
190
264
  });
191
265
  request.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
192
266
  request.on('end', () => finish());
@@ -235,7 +309,7 @@ export function createFrameParser(onMessage, onEnd) {
235
309
  };
236
310
  parse.finish = () => {
237
311
  if (pending.byteLength) {
238
- throw cursorError('Cursor stream ended with a truncated frame', { code: 'protocol_error' });
312
+ throw cursorError('Cursor stream ended with a truncated frame', { code: 'incomplete_stream' });
239
313
  }
240
314
  };
241
315
  parse.bufferedBytes = () => pending.byteLength;
@@ -246,26 +320,22 @@ export function parseEndStream(bytes) {
246
320
  try {
247
321
  const payload = JSON.parse(textDecoder.decode(bytes));
248
322
  if (!payload?.error) return null;
249
- const code = String(payload.error.code || 'error');
250
- const status =
251
- {
252
- unauthenticated: 401,
253
- permission_denied: 403,
254
- not_found: 404,
255
- resource_exhausted: 429,
256
- invalid_argument: 400,
257
- internal: 500,
258
- unavailable: 503,
259
- }[code] || 0;
260
- return cursorError(`Cursor ${code}: ${payload.error.message || 'request failed'}`, {
261
- status,
262
- code,
263
- retryAfter:
264
- payload.error.retryAfter ??
265
- payload.error.retry_after ??
266
- payload.metadata?.['retry-after'] ??
267
- payload.metadata?.retryAfter ??
268
- null,
323
+ const connectCode = String(payload.error.code || 'error').toLowerCase();
324
+ const detail = String(payload.error.message || 'request failed');
325
+ const tooLarge = connectCode === 'resource_exhausted' && !THROTTLE_CUE_RE.test(detail) && REQUEST_TOO_LARGE_RE.test(detail);
326
+ const retryAfter =
327
+ payload.error.retryAfter ??
328
+ payload.error.retry_after ??
329
+ payload.metadata?.['retry-after'] ??
330
+ payload.metadata?.retryAfter ??
331
+ null;
332
+ return cursorError(`Cursor ${connectCode}: ${detail}`, {
333
+ status: tooLarge ? 413 : CONNECT_CODE_STATUS[connectCode] || 0,
334
+ code: tooLarge ? 'request_too_large' : connectCode,
335
+ cursorCode: connectCode,
336
+ headers: payload.metadata,
337
+ retryAfter: firstValue(retryAfter),
338
+ detail,
269
339
  });
270
340
  } catch {
271
341
  return cursorError('Cursor returned an invalid end-stream frame', { code: 'protocol_error' });
@@ -14,6 +14,7 @@ import {
14
14
  } from './cursor-wire-protobuf.mjs';
15
15
  import {
16
16
  assertCursorUserImages,
17
+ createCursorCredentialRefresher,
17
18
  createCursorStreamWatchdog,
18
19
  resolveCursorStreamTuning,
19
20
  storeCursorBlob,
@@ -41,6 +42,7 @@ import {
41
42
  import { handleExecMessage, heartbeatFrame, sendToolResult } from './cursor-wire-exec.mjs';
42
43
  import { completionChunk, createStreamSink } from './cursor-wire-stream-sink.mjs';
43
44
  import { createBridgeController } from './cursor-wire-stream-bridge.mjs';
45
+ import { TRANSPORT_SCOPE_DISPOSED } from './transport-scope.mjs';
44
46
 
45
47
  export { clearModelCache, getCursorModels, getCursorUsage } from './cursor-wire-models.mjs';
46
48
 
@@ -116,6 +118,8 @@ globalThis.__mixdogCloseProviderConnectionsForSession = (sessionId, reason) => {
116
118
  _priorCursorSessionCloseHook?.(sessionId, reason);
117
119
  } finally {
118
120
  closeCursorRunsForSession(sessionId, reason);
121
+ // A disposed transport scope never sends again: drop its conversation.
122
+ if (reason === TRANSPORT_SCOPE_DISPOSED && sessionId) conversations.delete(conversationKey([], sessionId));
119
123
  }
120
124
  };
121
125
  const _priorCursorDrainHook = globalThis.__mixdogDrainProviderConnections;
@@ -358,12 +362,16 @@ function createStreamResponse({
358
362
  sessionId = '',
359
363
  sawTurnEnded = false,
360
364
  restart = null,
365
+ sleep = undefined,
366
+ onStageChange = null,
361
367
  }) {
362
368
  const id = newCompletionId();
363
369
  // The bridge currently feeding the stream; a restart swaps it in place.
364
370
  const live = { bridge, heartbeat };
365
371
  let watchdog = null;
366
372
  let cancelled = false;
373
+ // Cuts a pending retry backoff short when the caller cancels.
374
+ const cancelController = new AbortController();
367
375
  const runs = { get: (runKey) => activeRuns.get(runKey), store: storeActiveRun, forget: forgetActiveRun };
368
376
  const stream = new ReadableStream({
369
377
  start(controller) {
@@ -395,12 +403,16 @@ function createStreamResponse({
395
403
  run: { key, conversation, tools, cloudRule, modelParameters, maxMode, sessionId },
396
404
  runs,
397
405
  restart,
406
+ sleep,
407
+ onStageChange,
408
+ cancelSignal: cancelController.signal,
398
409
  isCancelled: () => cancelled,
399
410
  });
400
411
  bridges.attach(bridge, heartbeat);
401
412
  },
402
413
  cancel(reason) {
403
414
  cancelled = true;
415
+ cancelController.abort();
404
416
  watchdog?.stop();
405
417
  clearInterval(live.heartbeat);
406
418
  const active = activeRuns.get(key);
@@ -459,7 +471,7 @@ function noUserMessageResponse() {
459
471
  });
460
472
  }
461
473
 
462
- export async function handleChatCompletion(body, accessToken) {
474
+ export async function handleChatCompletion(body, accessToken, { onStageChange = null, getAccessToken = null } = {}) {
463
475
  await warmCursorClientVersion();
464
476
  const parsed = parseMessages(body.messages);
465
477
  if (
@@ -504,6 +516,10 @@ export async function handleChatCompletion(body, accessToken) {
504
516
  };
505
517
  const requestBytes = buildRunRequest(runInput);
506
518
  const { bridge, heartbeat } = startRun(accessToken, requestBytes);
519
+ // Every retry force-refreshes credentials and falls back to the previous
520
+ // token if the refresh fails. A rejected credential is only
521
+ // retried when the refresh produced a different token.
522
+ const credentials = createCursorCredentialRefresher(accessToken, getAccessToken);
507
523
  return createStreamResponse({
508
524
  bridge,
509
525
  heartbeat,
@@ -515,11 +531,21 @@ export async function handleChatCompletion(body, accessToken) {
515
531
  maxMode,
516
532
  key,
517
533
  sessionId,
518
- restart: ({ fromCheckpoint }) =>
519
- startRun(
520
- accessToken,
521
- fromCheckpoint ? buildRunRequest({ ...runInput, userText: '', userImages: [] }) : requestBytes
522
- ),
534
+ onStageChange,
535
+ restart: async ({ fromCheckpoint, reason, error }) => {
536
+ if (reason === 'throttle') {
537
+ // Cursor can pin resource_exhausted to one conversation id (conversation
538
+ // rotation after a rate limit): start a fresh one from the full
539
+ // history instead of the checkpoint it refused.
540
+ conversation.id = crypto.randomUUID();
541
+ conversation.checkpoint = null;
542
+ }
543
+ const token = await credentials.refresh(reason, error);
544
+ let bytes = requestBytes;
545
+ if (fromCheckpoint) bytes = buildRunRequest({ ...runInput, userText: '', userImages: [] });
546
+ else if (reason === 'throttle') bytes = buildRunRequest(runInput);
547
+ return startRun(token, bytes);
548
+ },
523
549
  });
524
550
  }
525
551
 
@@ -10,7 +10,9 @@ import {
10
10
  } from './openai-compat-wire.mjs';
11
11
  import { preparePdfTextForProvider } from './media-normalization.mjs';
12
12
  import { consumeCompatChatCompletionStream } from './openai-compat-stream.mjs';
13
+ import { retryAfterMsFromError } from './retry-classifier.mjs';
13
14
  import { ensureChatToolPairs } from './lib/wire-pairing.mjs';
15
+ import { acceptedWireTools } from './lib/claude-bridge-schema.mjs';
14
16
  import { cursorTokenExpiry, exchangeCursorToken, resolveCursorOAuthAccessToken } from './cursor-auth.mjs';
15
17
  import {
16
18
  canonicalCursorModelId,
@@ -62,14 +64,24 @@ function toCursorToolChoice(toolChoice) {
62
64
  async function responseError(response, label) {
63
65
  const text = await response.text().catch(() => '');
64
66
  let message = text;
67
+ let code = '';
65
68
  try {
66
69
  const parsed = JSON.parse(text);
67
70
  message = parsed?.error?.message || parsed?.message || text;
71
+ code = String(parsed?.error?.code || parsed?.code || '').toLowerCase();
68
72
  } catch {
69
73
  /* plain text */
70
74
  }
71
75
  const error = new Error(`${label} request failed (${response.status})${message ? `: ${message}` : ''}`);
72
76
  error.status = response.status;
77
+ error.httpStatus = response.status;
78
+ if (code) error.code = code;
79
+ const headers = Object.fromEntries(response.headers?.entries?.() ?? []);
80
+ if (Object.keys(headers).length) {
81
+ error.headers = headers;
82
+ const retryAfterMs = retryAfterMsFromError(error);
83
+ if (Number.isFinite(retryAfterMs)) error.retryAfterMs = retryAfterMs;
84
+ }
73
85
  throw error;
74
86
  }
75
87
 
@@ -535,6 +547,9 @@ class CursorProviderBase {
535
547
  nativePdf = false;
536
548
 
537
549
  static inputExcludesCache = false;
550
+ // The in-place stream retry budget is the whole recovery: a spent budget
551
+ // surfaces instead of being re-run by the outer recovery cycles.
552
+ retryPolicy = Object.freeze({ recoveryCycles: false });
538
553
  name;
539
554
  config;
540
555
  _runtimeOverride;
@@ -622,7 +637,10 @@ class CursorProviderBase {
622
637
  sendOpts.onStageChange?.('requesting');
623
638
  } catch {}
624
639
  try {
625
- const response = await runtime.handleChatCompletion(body, accessToken);
640
+ const response = await runtime.handleChatCompletion(body, accessToken, {
641
+ onStageChange: sendOpts.onStageChange,
642
+ getAccessToken: (options) => this._accessToken({ signal, ...options }),
643
+ });
626
644
  try {
627
645
  sendOpts.onStageChange?.('streaming');
628
646
  } catch {}
@@ -676,10 +694,16 @@ class CursorProviderBase {
676
694
  const runtime = await this._runtime();
677
695
  let accessToken = await this._accessToken({ signal });
678
696
  let cursorSelection = await this._resolveCursorModel(model, sendOpts, runtime, accessToken);
697
+ // The Cursor run and conversation (checkpoint, pending tool batch) live
698
+ // under this scope; a forked request keeps its own (transport-scope.mjs).
679
699
  const sessionScope = String(
680
- sendOpts.sessionId || sendOpts.providerCacheKey || sendOpts.promptCacheKey || `cursor-call:${randomUUID()}`
700
+ sendOpts.transportScope ||
701
+ sendOpts.sessionId ||
702
+ sendOpts.providerCacheKey || sendOpts.promptCacheKey || `cursor-call:${randomUUID()}`
681
703
  );
682
- const openAiTools = tools?.length ? toOpenAITools(tools) : undefined;
704
+ const openAiTools = tools?.length
705
+ ? toOpenAITools(acceptedWireTools(tools, 'openai', sendOpts.onToolExcluded, '[cursor] tool'))
706
+ : undefined;
683
707
  const dispatch = async () => {
684
708
  noteRequestServiceTier(cursorSelectionIsFast(cursorSelection) ? 'fast' : '');
685
709
  // Every attempt (retries included) prepares right before it lowers.
@@ -9,7 +9,7 @@ export const CUSTOM_PROVIDER_PROTOCOLS = Object.freeze(['openai-chat', 'openai-r
9
9
  export const CUSTOM_PROVIDER_SCHEMA_VERSION = 2;
10
10
  // Ranked weakest → strongest.
11
11
  export const CUSTOM_REASONING_LEVELS = CUSTOM_EFFORT_LEVELS;
12
- export const CUSTOM_REASONING_WIRE_FORMATS = Object.freeze(['standard', 'deepseek']);
12
+ export const CUSTOM_REASONING_WIRE_FORMATS = Object.freeze(['standard', 'deepseek', 'together', 'vllm']);
13
13
  export const CUSTOM_PROVIDER_AUTH_MODES = Object.freeze(['key', 'none']);
14
14
  export const CUSTOM_MODEL_OVERRIDE_LIMIT = 500;
15
15
 
@@ -74,10 +74,22 @@ const V2_TOP_LEVEL_KEYS = new Set([
74
74
  'auth',
75
75
  'discovery',
76
76
  'modelOverrides',
77
+ 'wires',
78
+ 'modelWires',
77
79
  'id',
78
80
  'apiKey',
81
+ 'apiKeyEnv',
79
82
  'preconnect',
80
83
  ]);
84
+ export const CUSTOM_API_KEY_ENV_RE = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
85
+
86
+ // Env var name that supplies the key, or '' when unset (undefined / null).
87
+ function normalizeApiKeyEnv(raw) {
88
+ if (raw === undefined || raw === null) return '';
89
+ if (typeof raw !== 'string' || !CUSTOM_API_KEY_ENV_RE.test(raw))
90
+ fail('apiKeyEnv must be a valid environment variable name');
91
+ return raw;
92
+ }
81
93
  const MAX_BASE_URL = 2048;
82
94
 
83
95
  function onlyKnownKeys(value, allowed, label) {
@@ -229,6 +241,49 @@ function normalizeOverrides(raw, protocol, legacy) {
229
241
  return out;
230
242
  }
231
243
 
244
+ // The wire each OpenAI-compatible protocol speaks (wire-route.mjs WIRES).
245
+ export const CUSTOM_PROTOCOL_WIRES = Object.freeze({
246
+ 'openai-chat': 'chat',
247
+ 'openai-responses': 'responses',
248
+ anthropic: 'messages',
249
+ });
250
+ // Wires a user-defined OpenAI-compatible endpoint can be declared to serve.
251
+ export const CUSTOM_PROVIDER_WIRES = Object.freeze(['chat', 'responses', 'messages']);
252
+
253
+ // `wires`: the wires the endpoint serves (the protocol's own wire included);
254
+ // absent = the protocol's wire only. Anthropic-protocol providers speak one wire.
255
+ function normalizeWires(raw, protocol) {
256
+ if (raw === undefined) return null;
257
+ if (protocol === 'anthropic') fail("wires requires an 'openai-chat' or 'openai-responses' protocol");
258
+ if (!Array.isArray(raw) || raw.length === 0) fail('wires must be a non-empty array');
259
+ const seen = new Set();
260
+ for (const wire of raw) {
261
+ if (!CUSTOM_PROVIDER_WIRES.includes(wire)) fail(`wires has unsupported wire: ${String(wire)}`);
262
+ if (seen.has(wire)) fail(`wires has duplicate wire: ${wire}`);
263
+ seen.add(wire);
264
+ }
265
+ if (!seen.has(CUSTOM_PROTOCOL_WIRES[protocol]))
266
+ fail(`wires must include the protocol wire: ${CUSTOM_PROTOCOL_WIRES[protocol]}`);
267
+ return CUSTOM_PROVIDER_WIRES.filter((wire) => seen.has(wire));
268
+ }
269
+
270
+ // `modelWires`: per-model wire override, each one of the served wires.
271
+ function normalizeModelWires(raw, protocol, wires) {
272
+ if (raw === undefined) return {};
273
+ if (protocol === 'anthropic') fail("modelWires requires an 'openai-chat' or 'openai-responses' protocol");
274
+ if (!isPlainObject(raw)) fail('modelWires must be an object');
275
+ const keys = Object.keys(raw);
276
+ if (keys.length > CUSTOM_MODEL_OVERRIDE_LIMIT) fail(`at most ${CUSTOM_MODEL_OVERRIDE_LIMIT} model wires are allowed`);
277
+ const served = wires || [CUSTOM_PROTOCOL_WIRES[protocol]];
278
+ const out = {};
279
+ for (const key of keys) {
280
+ const id = modelIdKey(key, 'modelWires key');
281
+ if (!served.includes(raw[key])) fail(`modelWires[${id}] must be one of the served wires: ${served.join(', ')}`);
282
+ out[id] = raw[key];
283
+ }
284
+ return out;
285
+ }
286
+
232
287
  // Legacy (pre-v2) `models: [{ id, name?, contextWindow?, maxOutputTokens? }]`
233
288
  // become manual, visible overrides; their presence hides newly discovered ids.
234
289
  function migrateLegacy(input) {
@@ -268,6 +323,8 @@ export function normalizeCustomProviderConfig(input) {
268
323
  // v2 fields without the version would be silently dropped by migration.
269
324
  if (input.modelOverrides !== undefined || input.discovery !== undefined)
270
325
  fail('modelOverrides and discovery require schemaVersion 2');
326
+ if (input.wires !== undefined || input.modelWires !== undefined)
327
+ fail('wires and modelWires require schemaVersion 2');
271
328
  source = migrateLegacy(input);
272
329
  } else if (Number.isInteger(version) && version > CUSTOM_PROVIDER_SCHEMA_VERSION)
273
330
  fail(`unsupported schemaVersion ${version}; update mixdog to use this provider`);
@@ -293,10 +350,13 @@ export function normalizeCustomProviderConfig(input) {
293
350
  if (!CUSTOM_PROVIDER_PROTOCOLS.includes(protocol)) fail(`unsupported protocol: ${protocol}`);
294
351
  const auth = source.auth === undefined || source.auth === null ? 'key' : text(source.auth);
295
352
  if (!CUSTOM_PROVIDER_AUTH_MODES.includes(auth)) fail(`invalid auth: ${String(source.auth)}`);
353
+ const apiKeyEnv = normalizeApiKeyEnv(source.apiKeyEnv);
296
354
  const discovery = source.discovery === undefined ? {} : source.discovery;
297
355
  if (!isPlainObject(discovery)) fail('discovery must be an object');
298
356
  onlyKnownKeys(discovery, DISCOVERY_KEYS, 'discovery');
299
357
  const newModelsHidden = optionalBoolean(discovery.newModelsHidden, 'discovery.newModelsHidden') ?? false;
358
+ const wires = normalizeWires(source.wires, protocol);
359
+ const modelWires = normalizeModelWires(source.modelWires, protocol, wires);
300
360
  return {
301
361
  type: 'custom',
302
362
  schemaVersion: CUSTOM_PROVIDER_SCHEMA_VERSION,
@@ -305,8 +365,11 @@ export function normalizeCustomProviderConfig(input) {
305
365
  baseURL: normalizeBaseURL(source.baseURL),
306
366
  enabled: source.enabled !== false,
307
367
  auth,
368
+ ...(apiKeyEnv && auth === 'key' ? { apiKeyEnv } : {}),
308
369
  discovery: { newModelsHidden },
309
370
  modelOverrides: normalizeOverrides(source.modelOverrides, protocol, legacy),
371
+ ...(wires ? { wires } : {}),
372
+ ...(Object.keys(modelWires).length ? { modelWires } : {}),
310
373
  };
311
374
  }
312
375
 
@@ -5,7 +5,19 @@
5
5
  // under the custom id so model routes and provider identity stay `custom-…`.
6
6
 
7
7
  import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
8
- import { effectiveCustomModelRows, normalizeCustomProviderConfig } from './custom-provider-config.mjs';
8
+ import {
9
+ CUSTOM_PROTOCOL_WIRES,
10
+ effectiveCustomModelRows,
11
+ normalizeCustomProviderConfig,
12
+ } from './custom-provider-config.mjs';
13
+ import {
14
+ customWireProfile,
15
+ providerOffersWire,
16
+ registerWireProfile,
17
+ resolveWireRoute,
18
+ sendOnRoute,
19
+ } from './wire-route.mjs';
20
+ import { withInclusiveUsage } from './lib/inclusive-usage.mjs';
9
21
  import { ANTHROPIC_VERSION } from './lib/anthropic-models.mjs';
10
22
  import { enrichModels } from './model-catalog.mjs';
11
23
  import { fetchCompatModelItems, listCompatModels } from './openai-compat-models.mjs';
@@ -45,6 +57,22 @@ function withCustomModels(Base, discover) {
45
57
  _customKey() {
46
58
  return this._customKeyless ? '' : resolveProviderApiKey(this.name, this.config);
47
59
  }
60
+ // Env mode with the variable unset: fail before any request is sent.
61
+ _assertCustomKey() {
62
+ const name = this.customConfig?.apiKeyEnv;
63
+ if (name && !this._customKeyless && !resolveProviderApiKey(this.name, this.config))
64
+ throw new Error(`API key environment variable ${name} is not set`);
65
+ }
66
+ // A 401 reload must not fall back to the stale key once the variable is
67
+ // gone; with a new value the base reload picks it up for the retry.
68
+ reloadApiKey() {
69
+ this._assertCustomKey();
70
+ return super.reloadApiKey();
71
+ }
72
+ async send(...args) {
73
+ this._assertCustomKey();
74
+ return super.send(...args);
75
+ }
48
76
  _customRows() {
49
77
  return effectiveCustomModelRows(this.customConfig, this._customDiscovered || [], { providerId: this.name });
50
78
  }
@@ -57,6 +85,7 @@ function withCustomModels(Base, discover) {
57
85
  async listModels({ signal = null, throwOnError = false } = {}) {
58
86
  let failure = null;
59
87
  try {
88
+ this._assertCustomKey();
60
89
  this._customDiscovered = await discover(this, signal);
61
90
  this.customDiscoveryError = null;
62
91
  } catch (error) {
@@ -112,10 +141,14 @@ export async function createCustomProvider(id, config) {
112
141
  // `auth: 'none'` is authoritative: a supplied/injected key is ignored and
113
142
  // `keyless` makes the engine omit every credential header.
114
143
  const keyless = cfg.auth === 'none';
115
- const apiKey = keyless ? '' : config.apiKey || '';
144
+ // Env mode: the named variable is the only key source.
145
+ let apiKey = '';
146
+ if (cfg.apiKeyEnv) apiKey = process.env[cfg.apiKeyEnv] || '';
147
+ else if (!keyless) apiKey = config.apiKey || '';
116
148
  const engine = {
117
149
  baseURL: cfg.baseURL,
118
150
  apiKey: apiKey || undefined,
151
+ ...(cfg.apiKeyEnv ? { apiKeyEnv: cfg.apiKeyEnv } : {}),
119
152
  ...(keyless ? { keyless: true } : {}),
120
153
  ...(config.preconnect === false ? { preconnect: false } : {}),
121
154
  };
@@ -129,14 +162,41 @@ export async function createCustomProvider(id, config) {
129
162
  provider.nativePdf = false;
130
163
  } else {
131
164
  const { OpenAICompatProvider } = await import('./openai-compat.mjs');
132
- const responses = cfg.protocol === 'openai-responses';
165
+ // The wires the endpoint is declared to serve (default: the protocol's
166
+ // own) and the per-model `modelWires` override pick each model's wire
167
+ // through the shared wire-route table, pinned per session like a gateway.
168
+ registerWireProfile(
169
+ providerId,
170
+ customWireProfile({ protocolWire: CUSTOM_PROTOCOL_WIRES[cfg.protocol], wires: cfg.wires })
171
+ );
172
+ let messagesEngine = null;
173
+ if (providerOffersWire(providerId, 'messages')) {
174
+ const { AnthropicProvider } = await import('./anthropic.mjs');
175
+ // The Anthropic SDK appends /v1/messages itself; the endpoint's /v1 base maps to it.
176
+ messagesEngine = new AnthropicProvider({ ...engine, name: providerId, baseURL: cfg.baseURL.replace(/\/v1$/i, '') });
177
+ messagesEngine.nativePdf = false;
178
+ }
133
179
  class CompatBase extends OpenAICompatProvider {
180
+ messagesEngine = messagesEngine;
134
181
  _fetchModelItems({ signal = null } = {}) {
135
182
  return fetchCompatModelItems(this, { signal, keyless: this._customKeyless });
136
183
  }
137
- _doSend(messages, model, tools, sendOpts) {
138
- if (!responses) return super._doSend(messages, model, tools, sendOpts);
139
- return super._doSend(messages, model, tools, { ...(sendOpts || {}), compatWireApi: 'responses' });
184
+ resolveWireRoute(model, ctx = {}) {
185
+ return resolveWireRoute(this.name, model, { ...ctx, override: cfg.modelWires?.[model] ?? null });
186
+ }
187
+ send(messages, model, tools, sendOpts) {
188
+ const useModel = model || this.defaultModel;
189
+ // The resolved route is the only wire authority: a caller's own wire
190
+ // switches never reach the engine. A custom provider declares the
191
+ // wires its endpoint serves, so a clear "unsupported" refusal moves
192
+ // the send to another declared wire once (sendOnRoute).
193
+ return sendOnRoute(this, useModel, tools, sendOpts, messages, (wire, history, opts) => {
194
+ if (wire === 'messages' && this.messagesEngine) {
195
+ return withInclusiveUsage(() => this.messagesEngine.send(history, useModel, tools, opts));
196
+ }
197
+ if (wire === 'responses') return super.send(history, model, tools, { ...opts, compatWireApi: 'responses' });
198
+ return super.send(history, model, tools, opts);
199
+ });
140
200
  }
141
201
  }
142
202
  const Custom = withCustomModels(CompatBase, (self, signal) =>
@@ -144,6 +204,10 @@ export async function createCustomProvider(id, config) {
144
204
  );
145
205
  provider = new Custom(providerId, engine);
146
206
  }
207
+ // The bound Messages engine reads the parent's model rows, so a routed
208
+ // Messages request carries the same row-driven thinking/effort as the
209
+ // Anthropic protocol.
210
+ if (provider.messagesEngine) provider.messagesEngine.getCachedModelInfo = (model) => provider.getCachedModelInfo(model);
147
211
  provider.customConfig = cfg;
148
212
  provider._customKeyless = keyless;
149
213
  provider._applyCustomRows(provider._customRows());