mixdog 1.2.3 → 1.2.4

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 (465) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +6 -4
  3. package/scripts/lib/gate-scheduler.mjs +29 -11
  4. package/scripts/probe-provider-catalog.mjs +31 -0
  5. package/scripts/provider-pingpong-live.mjs +128 -38
  6. package/scripts/session-sweep.mjs +5 -25
  7. package/src/agents/maintainer/AGENT.md +1 -1
  8. package/src/agents/maintainer/agent.json +1 -1
  9. package/src/agents/reviewer/AGENT.md +2 -4
  10. package/src/agents/security/AGENT.md +1 -2
  11. package/src/agents/writer/AGENT.md +2 -3
  12. package/src/defaults/shipped-history.json +6 -1
  13. package/src/defaults/skills/image/SKILL.md +21 -23
  14. package/src/lib/keychain-cjs.cjs +13 -11
  15. package/src/rules/agent/AGENT.md +7 -5
  16. package/src/rules/lead/LEAD.md +7 -9
  17. package/src/rules/shared/45-destructive.md +4 -0
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  20. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  21. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  22. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
  29. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  31. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  32. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
  33. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
  34. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
  35. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
  36. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
  37. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
  38. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  41. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
  44. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
  48. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
  49. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
  50. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
  51. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
  52. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
  53. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  54. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  55. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
  56. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
  57. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
  58. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
  59. package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
  60. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
  62. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
  63. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  64. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
  65. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
  66. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  67. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
  68. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
  79. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  80. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
  81. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
  83. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
  84. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
  86. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
  87. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
  88. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
  89. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
  90. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
  91. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
  92. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
  93. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
  94. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
  95. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
  96. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  97. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  99. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  100. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  101. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
  102. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  103. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
  104. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
  106. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  107. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  108. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  109. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  110. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  111. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
  112. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  113. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
  114. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  115. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
  116. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  117. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  118. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
  120. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  121. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  122. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
  123. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
  124. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
  125. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  127. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  128. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
  129. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
  130. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
  131. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  132. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
  134. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  135. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
  136. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
  137. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
  138. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  139. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  146. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  148. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
  150. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  151. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  152. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
  153. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
  154. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
  155. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  156. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
  157. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  158. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
  159. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  160. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
  161. package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
  162. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
  163. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  164. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  165. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  167. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  169. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
  170. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  171. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  172. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  177. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  179. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +136 -196
  180. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
  181. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  182. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
  184. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  185. package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
  186. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
  187. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
  188. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  189. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
  190. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  191. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  192. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  195. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
  196. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  198. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  200. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  201. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  202. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  203. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  206. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  207. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  208. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  209. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  210. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  211. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  212. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  213. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  214. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  215. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  216. package/src/runtime/attachments/store.d.mts +9 -0
  217. package/src/runtime/attachments/store.mjs +80 -9
  218. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  219. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  220. package/src/runtime/computer-bridge/action-schema.mjs +178 -178
  221. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  222. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  223. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  224. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  225. package/src/runtime/github/tool.mjs +118 -17
  226. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  227. package/src/runtime/media/catalog.mjs +4 -4
  228. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  229. package/src/runtime/media/defaults.mjs +37 -0
  230. package/src/runtime/media/jobs.mjs +12 -2
  231. package/src/runtime/media/lanes.mjs +12 -0
  232. package/src/runtime/media/tool-defs.mjs +15 -4
  233. package/src/runtime/media/tool.mjs +41 -12
  234. package/src/runtime/media/upstream-error.mjs +72 -6
  235. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  236. package/src/runtime/memory/lib/pg/process.mjs +5 -7
  237. package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
  238. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  239. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  240. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  241. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  242. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  243. package/src/runtime/office/core/office-write-ledger.mjs +1 -5
  244. package/src/runtime/office/index.mjs +2 -3
  245. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  246. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  247. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  248. package/src/runtime/shared/atomic-file.d.mts +1 -1
  249. package/src/runtime/shared/background-tasks.mjs +6 -1
  250. package/src/runtime/shared/bounded-line-diff.mjs +11 -2
  251. package/src/runtime/shared/browser-launch.mjs +15 -0
  252. package/src/runtime/shared/err-text.mjs +42 -4
  253. package/src/runtime/shared/file-lock.mjs +6 -5
  254. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  255. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  256. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  257. package/src/runtime/shared/model-route-chain.mjs +48 -0
  258. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  259. package/src/runtime/shared/pid-liveness.mjs +12 -0
  260. package/src/runtime/shared/provider-api-key.mjs +6 -0
  261. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  262. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  263. package/src/runtime/shared/quiet-progress.mjs +101 -0
  264. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  265. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  266. package/src/runtime/shared/remote-intent.mjs +2 -42
  267. package/src/runtime/shared/slow-span.mjs +8 -1
  268. package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
  269. package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
  270. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  271. package/src/runtime/shared/tool-result-summary.mjs +16 -2
  272. package/src/runtime/shared/tool-status.mjs +3 -2
  273. package/src/runtime/shared/tool-surface.mjs +2 -3
  274. package/src/runtime/shared/tool-work-units.mjs +22 -15
  275. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  276. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  277. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  278. package/src/runtime/shared/transcript-writer.mjs +14 -4
  279. package/src/runtime/shared/turn-changes.mjs +2 -1
  280. package/src/runtime/shared/turn-snapshot.mjs +11 -3
  281. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  282. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  283. package/src/runtime/shared/worktrees/carry.mjs +6 -4
  284. package/src/runtime/shared/worktrees/contract.mjs +27 -1
  285. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  286. package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
  287. package/src/runtime/shared/worktrees/git-head.mjs +22 -13
  288. package/src/runtime/shared/worktrees/include.mjs +125 -7
  289. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  290. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  291. package/src/runtime/shared/worktrees/manager.mjs +378 -32
  292. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  293. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  294. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  295. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  296. package/src/runtime/terminal-bridge/client.mjs +14 -10
  297. package/src/session-runtime/boot/apis.mjs +7 -1
  298. package/src/session-runtime/boot/config.mjs +2 -0
  299. package/src/session-runtime/boot/facade.mjs +3 -0
  300. package/src/session-runtime/boot/session.mjs +3 -2
  301. package/src/session-runtime/boot/tools.mjs +3 -0
  302. package/src/session-runtime/conversation-import/service.mjs +1 -1
  303. package/src/session-runtime/goal-facade-api.mjs +2 -2
  304. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
  305. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
  306. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
  307. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  308. package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
  309. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
  310. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
  311. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  312. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  313. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  314. package/src/session-runtime/lifecycle/teardown.mjs +7 -16
  315. package/src/session-runtime/media-api.mjs +8 -0
  316. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  317. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  318. package/src/session-runtime/model-route/set-route.mjs +114 -34
  319. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  320. package/src/session-runtime/model-route-api.mjs +4 -2
  321. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  322. package/src/session-runtime/native-web-search.mjs +9 -8
  323. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  324. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  325. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  326. package/src/session-runtime/provider-models.mjs +1 -0
  327. package/src/session-runtime/provider-usage.mjs +4 -0
  328. package/src/session-runtime/route-resolution.mjs +3 -1
  329. package/src/session-runtime/runtime-core.mjs +4 -0
  330. package/src/session-runtime/runtime-facade.mjs +6 -0
  331. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  336. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  337. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  338. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  339. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  340. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  341. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  342. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  343. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
  344. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  345. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  346. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  347. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  348. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  349. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  350. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  351. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  352. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  353. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
  354. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  355. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  356. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  357. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  358. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  359. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  360. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  361. package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
  362. package/src/session-runtime/services/agent-tool.mjs +4 -0
  363. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  364. package/src/session-runtime/services/provider-admin.mjs +77 -0
  365. package/src/session-runtime/session-create/session-options.mjs +7 -5
  366. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  367. package/src/session-runtime/session-transcript.mjs +4 -5
  368. package/src/session-runtime/session-turn-api.mjs +3 -9
  369. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
  370. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  371. package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
  372. package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
  373. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
  374. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  375. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  376. package/src/session-runtime/turn/side-question-ops.mjs +1 -4
  377. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
  378. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
  379. package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
  380. package/src/session-runtime/turn/turn-run.mjs +1 -36
  381. package/src/session-runtime/usage-stats-api.mjs +4 -1
  382. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  383. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  384. package/src/session-runtime/workspace/project-branches.mjs +28 -36
  385. package/src/session-runtime/workspace/transition-steps.mjs +96 -95
  386. package/src/session-runtime/workspace/transitions.mjs +248 -68
  387. package/src/standalone/channel-transport.mjs +3 -4
  388. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  389. package/src/standalone/daemon-pg-stop.mjs +1 -3
  390. package/src/standalone/daemon-session-control.mjs +88 -0
  391. package/src/standalone/daemon-stored-session-views.mjs +23 -3
  392. package/src/standalone/daemon.mjs +2 -0
  393. package/src/standalone/local-session-runtime.mjs +11 -12
  394. package/src/standalone/session-protocol.mjs +7 -0
  395. package/src/standalone/session-runtime-inline-host.mjs +2 -0
  396. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  397. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  398. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  399. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  400. package/src/standalone/session-service/agent-tree.mjs +28 -2
  401. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  402. package/src/standalone/session-service/entries.mjs +1 -0
  403. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  404. package/src/standalone/session-service/quota-resume.mjs +28 -13
  405. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  406. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  407. package/src/standalone/session-tool-output.mjs +82 -0
  408. package/src/tui/app/app-view.jsx +2 -1
  409. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  410. package/src/tui/app/provider-setup-picker.mjs +15 -3
  411. package/src/tui/app/slash-commands.mjs +6 -0
  412. package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
  413. package/src/tui/components/Spinner.jsx +4 -0
  414. package/src/tui/components/SpinnerByline.jsx +21 -2
  415. package/src/tui/components/status-line/local-line.mjs +10 -2
  416. package/src/tui/dist/index.mjs +478 -251
  417. package/src/tui/session/active-tool-summary.mjs +1 -1
  418. package/src/tui/session/context-state.mjs +0 -3
  419. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  420. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  421. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  422. package/src/tui/session/quota-resume.mjs +140 -61
  423. package/src/tui/session/session-api/commands.mjs +4 -10
  424. package/src/tui/session/session-api/controls.mjs +12 -0
  425. package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
  426. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  427. package/src/tui/session/session-api/lifecycle.mjs +13 -11
  428. package/src/tui/session/session-api/media.mjs +2 -0
  429. package/src/tui/session/session-api/routes.mjs +81 -57
  430. package/src/tui/session/session-api/settings.mjs +3 -3
  431. package/src/tui/session/session-api/worktree.mjs +3 -2
  432. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  433. package/src/tui/session/session-flow/drain.mjs +6 -4
  434. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  435. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  436. package/src/tui/session/session-flow/queue.mjs +1 -1
  437. package/src/tui/session/tool-approval.mjs +17 -8
  438. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  439. package/src/tui/session/tui-steering-persist.mjs +3 -5
  440. package/src/tui/session/turn-quiet.mjs +53 -0
  441. package/src/tui/session/turn-stream.mjs +5 -1
  442. package/src/tui/session/turn.mjs +38 -31
  443. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  444. package/src/tui/spinner-meta.mjs +10 -0
  445. package/src/tui/spinner-verbs.mjs +3 -0
  446. package/src/ui/statusline.mjs +13 -3
  447. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  448. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  449. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  450. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
  451. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  452. package/src/session-runtime/turn/share-ops.mjs +0 -77
  453. package/src/tui/session/cross-surface-share.mjs +0 -406
  454. package/src/tui/session/live-share/delta.mjs +0 -153
  455. package/src/tui/session/live-share/mirror.mjs +0 -106
  456. package/src/tui/session/live-share/owner.mjs +0 -254
  457. package/src/tui/session/live-share/retry.mjs +0 -54
  458. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  459. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  460. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  461. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  462. package/src/tui/session/live-share/viewer.mjs +0 -99
  463. package/src/tui/session/live-share/wire.mjs +0 -44
  464. package/src/tui/session/live-share.mjs +0 -154
  465. package/src/tui/session/shared-dir-watch.mjs +0 -69
@@ -2,12 +2,51 @@ import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
2
2
  import { enrichModels } from './model-catalog.mjs';
3
3
  import { sanitizeModelList } from './model-list-sanitize.mjs';
4
4
  import { PROVIDER_GENERATE_TOTAL_TIMEOUT_MS, createTimeoutSignal, resolveTimeoutMs } from '../stall-policy.mjs';
5
+ import { CUSTOM_EFFORT_LEVELS, normalizeCustomEffort } from './custom-reasoning.mjs';
5
6
 
6
7
  const MODEL_LIST_TIMEOUT_MS = resolveTimeoutMs('MIXDOG_COMPAT_MODEL_LIST_TIMEOUT_MS', 10_000, {
7
8
  minMs: 1_000,
8
9
  maxMs: PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
9
10
  });
10
11
 
12
+ /** Preserve endpoint-declared effort levels without inventing a default ladder. */
13
+ export function extractCompatReasoningEffort(model) {
14
+ if (!model || typeof model !== 'object') return null;
15
+ const rawLevels = [
16
+ model.reasoning_efforts,
17
+ model.reasoning_levels,
18
+ model.reasoningLevels,
19
+ model.capabilities?.reasoning_efforts,
20
+ model.capabilities?.reasoning_effort,
21
+ ].find(Array.isArray);
22
+ if (rawLevels) {
23
+ const declared = new Set(
24
+ rawLevels
25
+ .map((level) => (typeof level === 'string' ? level : level?.value))
26
+ .filter((level) => typeof level === 'string')
27
+ .map(normalizeCustomEffort)
28
+ .filter(Boolean)
29
+ );
30
+ const levels = CUSTOM_EFFORT_LEVELS.filter((level) => declared.has(level));
31
+ if (levels.length > 0) {
32
+ return {
33
+ supportsReasoning: levels.some((level) => level !== 'none'),
34
+ reasoningLevels: levels,
35
+ reasoningOptions: [{ type: 'effort', values: [...levels] }],
36
+ };
37
+ }
38
+ return null;
39
+ }
40
+ if (
41
+ model.supports_reasoning_effort === true ||
42
+ model.capabilities?.reasoning_effort === true ||
43
+ model.capabilities?.reasoning_efforts === true
44
+ ) {
45
+ return { supportsReasoning: true };
46
+ }
47
+ return null;
48
+ }
49
+
11
50
  // `keyless` omits the Authorization header entirely (keyless endpoints);
12
51
  // `signal` aborts the request together with the list timeout.
13
52
  export async function fetchCompatModelItems(provider, { signal = null, keyless = false } = {}) {
@@ -70,17 +109,14 @@ export async function listCompatModels(provider, { throwOnError = false, signal
70
109
  0
71
110
  );
72
111
  const outputTokens = Number(
73
- m?.max_output_tokens ??
74
- m?.output_tokens ??
75
- m?.maxOutputTokens ??
76
- m?.top_provider?.max_completion_tokens ??
77
- 0
112
+ m?.max_output_tokens ?? m?.output_tokens ?? m?.maxOutputTokens ?? m?.top_provider?.max_completion_tokens ?? 0
78
113
  );
79
114
  const mode = nativeMode(m);
80
115
  // A catalog alias (OpenRouter `~anthropic/claude-opus-latest`) keeps its
81
116
  // own slug for routing; the slug it currently points at is retained for
82
117
  // capability lookup only.
83
118
  const aliasTarget = typeof m?.alias_target === 'string' ? m.alias_target : m?.alias_target?.slug;
119
+ const effortInfo = extractCompatReasoningEffort(m);
84
120
  models.push({
85
121
  id: m?.id,
86
122
  name: m?.id,
@@ -90,6 +126,7 @@ export async function listCompatModels(provider, { throwOnError = false, signal
90
126
  created: typeof m?.created === 'number' ? m.created : null,
91
127
  ...(mode ? { mode } : {}),
92
128
  ...(typeof aliasTarget === 'string' && aliasTarget ? { aliasTarget } : {}),
129
+ ...(effortInfo || {}),
93
130
  });
94
131
  }
95
132
  const filtered = models.filter((m) => m.id);
@@ -98,7 +98,10 @@ function thinkingText(thinking) {
98
98
  if (typeof thinking === 'string') return thinking;
99
99
  if (!Array.isArray(thinking)) return '';
100
100
  return thinking
101
- .map((inner) => (typeof inner === 'string' ? inner : typeof inner?.text === 'string' ? inner.text : ''))
101
+ .map((inner) => {
102
+ if (typeof inner === 'string') return inner;
103
+ return typeof inner?.text === 'string' ? inner.text : '';
104
+ })
102
105
  .join('');
103
106
  }
104
107
 
@@ -129,14 +132,15 @@ export function chatContentParts(content) {
129
132
  */
130
133
  export function readChatMessage(message, { shape = null } = {}) {
131
134
  const parts = chatContentParts(message?.content);
132
- const content = parts
133
- ? parts
134
- .filter((part) => part.kind === 'text')
135
- .map((part) => part.text)
136
- .join('')
137
- : typeof message?.content === 'string'
138
- ? message.content
139
- : '';
135
+ let content;
136
+ if (parts) {
137
+ content = parts
138
+ .filter((part) => part.kind === 'text')
139
+ .map((part) => part.text)
140
+ .join('');
141
+ } else {
142
+ content = typeof message?.content === 'string' ? message.content : '';
143
+ }
140
144
  if (typeof message?.reasoning_content === 'string') {
141
145
  return { content, reasoningContent: message.reasoning_content, reasoningSource: 'reasoning_content' };
142
146
  }
@@ -225,9 +225,7 @@ export function normalizeCompatChatResponse({
225
225
  // Replay sends reasoning back under the field it arrived on.
226
226
  const providerMetadata = {
227
227
  ...(reasoningDetails?.length ? { openrouter: { reasoning_details: reasoningDetails } } : {}),
228
- ...(reasoningContent !== null && reasoningField
229
- ? { [COMPAT_REASONING_METADATA_KEY]: { reasoningField } }
230
- : {}),
228
+ ...(reasoningContent !== null && reasoningField ? { [COMPAT_REASONING_METADATA_KEY]: { reasoningField } } : {}),
231
229
  };
232
230
  return {
233
231
  content: assembled.content || '',
@@ -13,7 +13,13 @@ export const SERVER_CALL = {
13
13
  execution: 'server',
14
14
  arguments: { query: 'q' },
15
15
  };
16
- export const SERVER_OUTPUT = { type: 'tool_search_output', id: 'tso_srv', call_id: null, execution: 'server', tools: [] };
16
+ export const SERVER_OUTPUT = {
17
+ type: 'tool_search_output',
18
+ id: 'tso_srv',
19
+ call_id: null,
20
+ execution: 'server',
21
+ tools: [],
22
+ };
17
23
  export const REPLY = { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'ok' }] };
18
24
 
19
25
  export const ROUTES = [
@@ -115,11 +121,14 @@ export function serverSearchReplayHistory(owner) {
115
121
  export const COMPAT_CASES = [
116
122
  ['native', () => nativeHistory()],
117
123
  ['oauth', () => oauthHistory()],
118
- ['own-search-output', (name) => {
119
- const h = nativeHistory();
120
- h[2] = { ...h[2], nativeToolSearch: search(name) };
121
- return h;
122
- }],
124
+ [
125
+ 'own-search-output',
126
+ (name) => {
127
+ const h = nativeHistory();
128
+ h[2] = { ...h[2], nativeToolSearch: search(name) };
129
+ return h;
130
+ },
131
+ ],
123
132
  ['server-replay', (name, owner = replayOwner(name)) => serverSearchReplayHistory(owner)],
124
133
  ];
125
134
 
@@ -153,7 +162,9 @@ export async function captureCompat(name, model, history, opts = {}, config = {}
153
162
  }
154
163
 
155
164
  export function captureFirstParty(name, model, history) {
156
- return JSON.parse(JSON.stringify(buildRequestBody(history, model, [], { promptCacheProvider: name, sessionId: 'sess-1' })));
165
+ return JSON.parse(
166
+ JSON.stringify(buildRequestBody(history, model, [], { promptCacheProvider: name, sessionId: 'sess-1' }))
167
+ );
157
168
  }
158
169
 
159
170
  export async function captureAll() {
@@ -66,6 +66,7 @@ function resolveReasoning(provider, useModel, opts) {
66
66
  function customEffortProjection(provider, useModel, messages, opts) {
67
67
  if (!isCustomProviderName(provider.name)) return null;
68
68
  const projection = projectEffortConfiguration(messages, provider.name, useModel, {
69
+ ...provider.config,
69
70
  ...opts,
70
71
  protocol: 'openai-responses',
71
72
  });
@@ -101,7 +102,10 @@ function supportsPromptCacheKey(provider, opts) {
101
102
  // `responsesNativeItems: true` (opts or config) gets the first-party lowering.
102
103
  // Every other route keeps the function-only lowering byte for byte.
103
104
  function supportsNativeResponsesItems(provider, opts) {
104
- return (opts.responsesNativeItems ?? provider.config?.responsesNativeItems) === true || hasNativeDeferredTools(provider, opts);
105
+ return (
106
+ (opts.responsesNativeItems ?? provider.config?.responsesNativeItems) === true ||
107
+ hasNativeDeferredTools(provider, opts)
108
+ );
105
109
  }
106
110
 
107
111
  // A session whose pinned route verified native deferred tools on the Responses
@@ -126,6 +130,33 @@ function customSessionCacheKey(provider, opts) {
126
130
  );
127
131
  }
128
132
 
133
+ // HTTP header names are case-insensitive. Do not shadow explicit SDK
134
+ // defaults, combine differently-cased identities, or stringify null deletions.
135
+ function customSessionHeaders(provider, sessionCacheKey, requestHeaders) {
136
+ if (!sessionCacheKey) return requestHeaders;
137
+ const headers = { 'session-id': sessionCacheKey, 'x-client-request-id': sessionCacheKey };
138
+ const clientOptions = provider.config?.clientOptions || {};
139
+ const configured = Object.hasOwn(clientOptions, 'defaultHeaders')
140
+ ? clientOptions.defaultHeaders
141
+ : provider.defaultHeaders;
142
+ const entries = (source) =>
143
+ source instanceof Headers || Array.isArray(source) ? source : Object.entries(source || {});
144
+ const hasValue = (value) => (Array.isArray(value) ? value.some((item) => item !== undefined) : value !== undefined);
145
+ for (const source of [configured, requestHeaders]) {
146
+ for (const [name, value] of entries(source)) {
147
+ if (hasValue(value)) delete headers[name.toLowerCase()];
148
+ }
149
+ }
150
+ if (requestHeaders instanceof Headers) {
151
+ const merged = new Headers(requestHeaders);
152
+ for (const [name, value] of Object.entries(headers)) merged.set(name, value);
153
+ return merged;
154
+ }
155
+ if (Array.isArray(requestHeaders)) return [...Object.entries(headers), ...requestHeaders];
156
+ const supplied = Object.fromEntries(Object.entries(requestHeaders || {}).filter(([, value]) => hasValue(value)));
157
+ return { ...headers, ...supplied };
158
+ }
159
+
129
160
  /** The request body: lowered transcript plus the per-turn knobs (output cap,
130
161
  * tools and tool choice, reasoning effort, session cache key). */
131
162
  function buildCompatResponsesParams({
@@ -300,8 +331,10 @@ async function sendCompatResponsesOnce(provider, messages, useModel, tools, opts
300
331
  sessionCacheKey,
301
332
  });
302
333
  // The session identity also travels as headers; caller headers win.
303
- const headers = sessionCacheKey && isCustomProviderName(provider.name)
304
- ? { 'session-id': sessionCacheKey, 'x-client-request-id': sessionCacheKey, ...opts.requestHeaders }
334
+ // Custom routes only: a gateway route that opts into prompt_cache_key keeps
335
+ // its own headers.
336
+ const headers = isCustomProviderName(provider.name)
337
+ ? customSessionHeaders(provider, sessionCacheKey, opts.requestHeaders)
305
338
  : opts.requestHeaders;
306
339
 
307
340
  try {
@@ -21,13 +21,18 @@
21
21
  // and jittered by jitterRatio (the advice of the excepted statuses stays raw).
22
22
  // recycleDispatcherOn5xx — a retried 5xx rebuilds the shared HTTP pool.
23
23
  // adviceAtLeastBackoff — the advice is a floor under the jittered backoff step.
24
+ // firstByteRetries — a header or first-chunk timeout (`timeoutPhase`) is
25
+ // retried at most this many times, each retry on a doubled window capped
26
+ // at PROVIDER_MAX_BEFORE_WARN_MS; other transient errors keep the ladder.
24
27
  // Every stop is provider-terminal: the profile's limits are final, so the
25
28
  // agent loop never re-runs a stopped or exhausted request (see
26
29
  // COMPAT_RETRY_POLICY).
27
30
  import { markProviderRecoveryExhausted } from './retry-classifier.mjs';
28
31
  import { retryAfterMsFromError } from './provider-error-kind.mjs';
29
32
  import { STREAM_TRANSPORTS, stampStreamOutcome } from './lib/stream-outcome.mjs';
30
- import { jitterDelayMs } from './retry-backoff.mjs';
33
+ import { DEFAULT_BACKOFF_MS, DEFAULT_MAX_ATTEMPTS, jitterDelayMs } from './retry-backoff.mjs';
34
+ import { PROVIDER_MAX_BEFORE_WARN_MS, firstBytePolicyBudget, firstByteWindowMs } from '../stall-policy.mjs';
35
+ import { isFirstByteTimeout } from '../../../shared/provider-timeout-phase.mjs';
31
36
 
32
37
  const statusOf = (err) => Number(err?.httpStatus || err?.status || err?.response?.status || 0);
33
38
 
@@ -75,9 +80,11 @@ export const COMPAT_RETRY_PROFILES = Object.freeze({
75
80
  }),
76
81
  // server budget 8 retries (2s doubling to 30s,
77
82
  // ±10%), rate-limit budget 5 retries; 408, 409, 501 and 505 are terminal
78
- // (425 stays retryable); a Retry-After beyond 300s is not waited out.
83
+ // (425 stays retryable); a Retry-After beyond 300s is not waited out. A
84
+ // first-byte timeout gets one longer retry instead of the whole ladder.
79
85
  'mimo-oauth': Object.freeze({
80
86
  maxAttempts: 9,
87
+ firstByteRetries: 1,
81
88
  backoffMs: [2000, 2000, 4000, 8000, 16000, 30000],
82
89
  retryJitterRatio: 0.1,
83
90
  statusRetryCaps: { 429: 5 },
@@ -112,6 +119,40 @@ export function compatRetryOptions(providerName) {
112
119
  };
113
120
  }
114
121
 
122
+ // The provider's first-byte policy (see firstByteWindowMs) on a base window:
123
+ // a profile's `firstByteRetries` caps first-byte retries on a doubling window;
124
+ // without it every attempt keeps the base window.
125
+ function compatFirstBytePolicy(providerName, baseMs) {
126
+ return {
127
+ timeoutMs: baseMs,
128
+ maxMs: PROVIDER_MAX_BEFORE_WARN_MS,
129
+ retries: COMPAT_RETRY_PROFILES[providerName]?.firstByteRetries ?? null,
130
+ };
131
+ }
132
+
133
+ /** First-byte window of an attempt after `timeouts` first-byte timeouts. */
134
+ export function compatFirstByteWindowMs(providerName, baseMs, timeouts = 0) {
135
+ return firstByteWindowMs(compatFirstBytePolicy(providerName, baseMs), timeouts);
136
+ }
137
+
138
+ /**
139
+ * First-response budget of a chat profile that sets its own first-byte window
140
+ * (`firstByteTimeoutMs`, applied to the headers and to the first chunk): the
141
+ * worst case over the profile's whole ladder, where drops and 5xx use up
142
+ * attempts and backoff alongside the first-byte timeouts the profile permits
143
+ * (see firstBytePolicyBudget). Null for a profile on the shared window, which
144
+ * the agent watchdog's default already covers.
145
+ */
146
+ export function compatFirstResponseBudget(providerName, chatProfile) {
147
+ const windowMs = Number(chatProfile?.firstByteTimeoutMs);
148
+ if (!(windowMs > 0)) return null;
149
+ const options = compatRetryOptions(providerName);
150
+ return firstBytePolicyBudget(compatFirstBytePolicy(providerName, windowMs), {
151
+ attempts: options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
152
+ backoffMs: options.backoffMs ?? DEFAULT_BACKOFF_MS,
153
+ });
154
+ }
155
+
115
156
  function profileStops(profile, err, { attempt, elapsedMs }) {
116
157
  const status = statusOf(err);
117
158
  if (profile.terminalStatuses?.includes(status)) return true;
@@ -170,10 +211,11 @@ export function withCompatRetryProfile(providerName, attemptFn) {
170
211
  const profile = COMPAT_RETRY_PROFILES[providerName];
171
212
  if (!profile) return attemptFn;
172
213
  let emptyRetries = 0;
214
+ let firstByteTimeouts = 0;
173
215
  return async (ctx) => {
174
216
  const startedAt = Date.now();
175
217
  try {
176
- const result = await attemptFn(ctx);
218
+ const result = await attemptFn({ ...ctx, firstByteTimeouts });
177
219
  if (profile.retryEmptyResponse && isEmptyNormalFinish(result)) {
178
220
  const bound = profile.retryEmptyResponse === true ? Infinity : profile.retryEmptyResponse;
179
221
  if (emptyRetries < bound) {
@@ -183,6 +225,12 @@ export function withCompatRetryProfile(providerName, attemptFn) {
183
225
  }
184
226
  return result;
185
227
  } catch (err) {
228
+ if (profile.firstByteRetries != null && isFirstByteTimeout(err) && !ctx?.signal?.aborted) {
229
+ firstByteTimeouts += 1;
230
+ if (firstByteTimeouts > profile.firstByteRetries) {
231
+ throw markProviderRecoveryExhausted(err, { owner: `${providerName}-first-byte`, terminal: true });
232
+ }
233
+ }
186
234
  if (profileStops(profile, err, { attempt: ctx?.attempt | 0, elapsedMs: Date.now() - startedAt })) {
187
235
  throw markProviderRecoveryExhausted(err, { owner: `${providerName}-retry-profile`, terminal: true });
188
236
  }
@@ -15,7 +15,12 @@ import {
15
15
  import { withEffortRejectionFallback } from './effort-rejection.mjs';
16
16
  import { applyCompatErrorContract } from './openai-compat-error-contract.mjs';
17
17
  import { sendCompatResponses } from './openai-compat-responses.mjs';
18
- import { COMPAT_RETRY_POLICY, COMPAT_RETRY_PROFILES, XAI_COMPAT_RETRY_POLICY } from './openai-compat-retry-profile.mjs';
18
+ import {
19
+ COMPAT_RETRY_POLICY,
20
+ COMPAT_RETRY_PROFILES,
21
+ XAI_COMPAT_RETRY_POLICY,
22
+ compatFirstResponseBudget,
23
+ } from './openai-compat-retry-profile.mjs';
19
24
  import { sendCompatChat, recoverCompatNonStreaming } from './openai-compat-chat-send.mjs';
20
25
  import { sendXaiResponses, sendXaiResponsesWebSocket } from './openai-compat-xai-send.mjs';
21
26
  import {
@@ -100,6 +105,11 @@ export class OpenAICompatProvider {
100
105
  }
101
106
  }
102
107
  }
108
+ // First-response budget of a chat profile with its own first-byte window;
109
+ // null keeps the agent watchdog default.
110
+ firstResponseBudget() {
111
+ return compatFirstResponseBudget(this.name, this.config?.chatProfile);
112
+ }
103
113
  get _preconnectFn() {
104
114
  return typeof this.config?.preconnectFn === 'function' ? this.config.preconnectFn : preconnect;
105
115
  }
@@ -6,7 +6,16 @@
6
6
  * Returns an OK response whose body is ready to stream; every failure path
7
7
  * throws with the initial-response markers the retry classifier reads.
8
8
  */
9
+ import { promisify } from 'node:util';
9
10
  import zlib from 'node:zlib';
11
+ import {
12
+ REQ_COMPRESSION_MIN_BYTES,
13
+ compressBody,
14
+ createCompressionLatch,
15
+ hasContentEncoding,
16
+ resendIfEncodingRejected,
17
+ withContentEncoding,
18
+ } from './lib/request-compression.mjs';
10
19
  import { traceAgentFetch } from '../agent-trace.mjs';
11
20
  import { PROVIDER_HTTP_RESPONSE_TIMEOUT_MS, createTimeoutSignal } from '../stall-policy.mjs';
12
21
  import { CODEX_REQUEST_RETRY_BACKOFF_MS, CODEX_RETRY_JITTER_RATIO } from './openai-retry-policy.mjs';
@@ -37,7 +46,6 @@ const OPENAI_REQUEST_RETRY_OPTS = Object.freeze({
37
46
  retryJitterRatio: CODEX_RETRY_JITTER_RATIO,
38
47
  });
39
48
 
40
-
41
49
  // Request-layer retry: 5xx and typed transport
42
50
  // failures only. 408 and 429 are NOT retried here; they surface to the outer
43
51
  // stream-retry layer, which owns their budget and honors Retry-After.
@@ -52,31 +60,29 @@ function isRetryableHttpFailure(err) {
52
60
  }
53
61
  // Request-body zstd gate. Namespace import: on Node runtimes without zlib
54
62
  // zstd bindings the named export would fail at module load, so the call site
55
- // typeof-guards zlib.zstdCompressSync instead.
56
- const OPENAI_REQ_ZSTD_MIN_BYTES = 8 * 1024;
57
- let _openaiReqZstdLatch = false;
58
- function _openaiReqZstdDisabled() {
59
- return _openaiReqZstdLatch || process.env.MIXDOG_OPENAI_REQ_ZSTD === '0';
60
- }
61
- function _disableOpenaiReqZstd() {
62
- _openaiReqZstdLatch = true;
63
- }
64
- function _zstdHeaders(headers, bodyForSend) {
65
- return bodyForSend.encoding ? { ...headers, 'Content-Encoding': bodyForSend.encoding } : headers;
66
- }
63
+ // typeof-guards zlib.zstdCompress instead.
64
+ const _openaiReqZstdLatch = createCompressionLatch('MIXDOG_OPENAI_REQ_ZSTD');
65
+ export const _resetOpenaiReqZstdLatchForTest = _openaiReqZstdLatch.reset;
67
66
 
68
67
  // Request-body zstd: compression is enabled for the codex backend on the
69
68
  // OpenAI provider — the server decompresses Content-Encoding: zstd.
70
- // openai-direct is excluded: only the codex backend is verified. Env
71
- // kill-switch plus a process-wide latch flipped on the first 400 seen on
72
- // a compressed request, which then replays that attempt uncompressed.
73
- function encodeRequestBody(auth, rawBytes) {
74
- return auth?.type !== 'openai-direct' &&
75
- !_openaiReqZstdDisabled() &&
76
- typeof zlib.zstdCompressSync === 'function' &&
77
- rawBytes.length >= OPENAI_REQ_ZSTD_MIN_BYTES
78
- ? { bytes: zlib.zstdCompressSync(rawBytes), encoding: 'zstd' }
79
- : { bytes: rawBytes, encoding: null };
69
+ // openai-direct is excluded: only the codex backend is verified. Compression
70
+ // runs on the libuv threadpool (zlib.zstdCompress, promisified) so it never
71
+ // blocks the main thread; runtimes without the async zstd binding send plain.
72
+ // Env kill-switch; the 400/415 resend + latch policy is shared
73
+ // (lib/request-compression.mjs).
74
+ async function encodeRequestBody(auth, headers, rawBytes) {
75
+ const plain = { bytes: rawBytes, encoding: null };
76
+ if (
77
+ auth?.type === 'openai-direct' ||
78
+ _openaiReqZstdLatch.disabled() ||
79
+ typeof zlib.zstdCompress !== 'function' ||
80
+ rawBytes.length < REQ_COMPRESSION_MIN_BYTES ||
81
+ hasContentEncoding(headers)
82
+ ) {
83
+ return plain;
84
+ }
85
+ return compressBody(rawBytes, promisify(zlib.zstdCompress), 'zstd');
80
86
  }
81
87
 
82
88
  async function postWithRetries({
@@ -91,15 +97,32 @@ async function postWithRetries({
91
97
  _sleepFn,
92
98
  }) {
93
99
  const label = httpLabel(auth);
94
- let bodyForSend = encodeRequestBody(auth, rawBytes);
95
- let response;
96
- let httpError = null;
97
- for (let attempt = 0; attempt <= CODEX_REQUEST_MAX_RETRIES; attempt++) {
100
+ const bodyForSend = await encodeRequestBody(auth, headers, rawBytes);
101
+ const fetchOnce = async (bytes, sendHeaders) => {
98
102
  const headerTimeout = createTimeoutSignal(
99
103
  totalTimeout.signal,
100
104
  PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
101
105
  `${label} initial response`
102
106
  );
107
+ try {
108
+ return await fetchFn(responsesUrl, {
109
+ method: 'POST',
110
+ headers: sendHeaders,
111
+ body: bytes,
112
+ signal: headerTimeout.signal,
113
+ dispatcher: getLlmDispatcher(),
114
+ });
115
+ } catch (err) {
116
+ throw headerTimeout.signal?.aborted && headerTimeout.signal.reason instanceof Error
117
+ ? headerTimeout.signal.reason
118
+ : err;
119
+ } finally {
120
+ headerTimeout.cleanup();
121
+ }
122
+ };
123
+ let response;
124
+ let httpError = null;
125
+ for (let attempt = 0; attempt <= CODEX_REQUEST_MAX_RETRIES; attempt++) {
103
126
  let requestError = null;
104
127
  try {
105
128
  // Keep the established stage name for consumers, while making each
@@ -113,32 +136,20 @@ async function postWithRetries({
113
136
  retry: attempt > 0,
114
137
  });
115
138
  } catch {}
116
- response = await fetchFn(responsesUrl, {
117
- method: 'POST',
118
- headers: _zstdHeaders(headers, bodyForSend),
119
- body: bodyForSend.bytes,
120
- signal: headerTimeout.signal,
121
- dispatcher: getLlmDispatcher(),
139
+ response = bodyForSend.encoding
140
+ ? await fetchOnce(bodyForSend.bytes, withContentEncoding(headers, bodyForSend.encoding))
141
+ : await fetchOnce(rawBytes, headers);
142
+ // Encoding rejection (400/415): resent uncompressed inside this attempt,
143
+ // so it never consumes a retry; the latch flips only if that succeeds.
144
+ response = await resendIfEncodingRejected({
145
+ response,
146
+ encoded: Boolean(bodyForSend.encoding),
147
+ resend: () => fetchOnce(rawBytes, headers),
148
+ latch: _openaiReqZstdLatch,
122
149
  });
123
150
  } catch (err) {
124
- requestError =
125
- headerTimeout.signal?.aborted && headerTimeout.signal.reason instanceof Error
126
- ? headerTimeout.signal.reason
127
- : err;
128
- } finally {
129
- headerTimeout.cleanup();
130
- }
131
-
132
- // zstd rejection fallback: a 400 on a compressed request latches
133
- // compression OFF process-wide and replays this attempt uncompressed.
134
- if (response && response.status === 400 && bodyForSend.encoding) {
135
- _disableOpenaiReqZstd();
136
- bodyForSend = { bytes: rawBytes, encoding: null };
137
- await response.arrayBuffer().catch(() => {});
138
151
  response = undefined;
139
- // The uncompressed replay is not a retry: it must not consume an attempt.
140
- attempt--;
141
- continue;
152
+ requestError = err;
142
153
  }
143
154
  // The error body is read exactly once, here, into the contract fields.
144
155
  httpError = null;
@@ -183,8 +194,9 @@ async function postWithRetries({
183
194
  // Uncapped for retryable errors; the attempt count is
184
195
  // the bound, and the abort signal cancels the wait.
185
196
  // The shared schedule carries a leading duplicate (withRetry convention).
186
- const raw = OPENAI_REQUEST_RETRY_OPTS.backoffMs[attempt + 1];
187
- const delayMs = retryAfterMsFromError(attemptFailure) ?? jitterDelayMs(raw, OPENAI_REQUEST_RETRY_OPTS.retryJitterRatio);
197
+ const raw = OPENAI_REQUEST_RETRY_OPTS.backoffMs[attempt + 1];
198
+ const delayMs =
199
+ retryAfterMsFromError(attemptFailure) ?? jitterDelayMs(raw, OPENAI_REQUEST_RETRY_OPTS.retryJitterRatio);
188
200
  emitProviderRetryStage(onStageChange, {
189
201
  attempt: attempt + 2,
190
202
  maxAttempts: CODEX_REQUEST_MAX_RETRIES + 1,
@@ -119,7 +119,9 @@ export function createTerminalFrameEvents({ state, items, text, outcome, meaning
119
119
  // Same carried state as the WS path: text (leak-guard tail flushed),
120
120
  // the calls already complete and dispatched, model and billed usage.
121
121
  text.flushLeak();
122
- const err = outcome.stampToolSafety(new Error(`${state.label} ${frame} (max_output_tokens) with tool call in flight`));
122
+ const err = outcome.stampToolSafety(
123
+ new Error(`${state.label} ${frame} (max_output_tokens) with tool call in flight`)
124
+ );
123
125
  outcome.attachStreamPartial(err);
124
126
  err.streamStalled = true;
125
127
  err.pendingToolUse = true;
@@ -254,7 +254,8 @@ export function convertMessagesToResponsesInput(messages, opts = {}) {
254
254
  pushReasoningItems(m, 'after');
255
255
  }
256
256
  flushToolMedia();
257
- if (opts.reasoningByMessageIndex?.has(messages.length)) out.push(...opts.reasoningByMessageIndex.get(messages.length));
257
+ if (opts.reasoningByMessageIndex?.has(messages.length))
258
+ out.push(...opts.reasoningByMessageIndex.get(messages.length));
258
259
  out.push(...effortUpdates.end());
259
260
  // Wire-level pairing guard: replay envelopes can carry a call whose
260
261
  // result never committed (cancel/abort). The provider hard-rejects the
@@ -119,7 +119,13 @@ function responsesToolsList(tools, opts, nativeToolSearch) {
119
119
  // must be a valid 2020-12 object schema -- lowered when it is not, left out
120
120
  // and reported when it still is not.
121
121
  const sendable = tools?.length
122
- ? acceptedWireTools(tools, 'openai', opts.onToolExcluded, `[${opts.promptCacheProvider || 'openai-oauth'}] tool`, isResponsesFreeformTool)
122
+ ? acceptedWireTools(
123
+ tools,
124
+ 'openai',
125
+ opts.onToolExcluded,
126
+ `[${opts.promptCacheProvider || 'openai-oauth'}] tool`,
127
+ isResponsesFreeformTool
128
+ )
123
129
  : [];
124
130
  const functionTools = sendable.map((t) => toOpenAIResponsesTool(t, { nativeToolSearch }));
125
131
  const nativeTools = Array.isArray(opts.nativeTools) ? opts.nativeTools.filter((t) => t && typeof t === 'object') : [];
@@ -293,10 +293,7 @@ const TRANSPORT_ONLY_FRAME_FIELDS = new Set(['stream', 'background']);
293
293
  // response, so dropping them here strips the system/lead prompt from every
294
294
  // continuation turn. Only an empty instructions string is omitted.
295
295
  // Full frames pass the body unchanged and keep every key in place.
296
- export function _buildResponseCreateFrame(
297
- body,
298
- { previousResponseId = null, inputOverride } = {}
299
- ) {
296
+ export function _buildResponseCreateFrame(body, { previousResponseId = null, inputOverride } = {}) {
300
297
  const src = body && typeof body === 'object' ? body : {};
301
298
  if (previousResponseId == null && inputOverride === undefined) {
302
299
  const frame = { type: 'response.create' };
@@ -217,7 +217,6 @@ export function createWsToolCalls({ onToolCall, midState, replayItems }) {
217
217
  settle();
218
218
  }
219
219
 
220
-
221
220
  return {
222
221
  get calls() {
223
222
  return toolCalls;
@@ -29,10 +29,13 @@ import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
29
29
  import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
30
30
  import { resolveProviderCacheKey, resolveProviderPromptCacheLane } from '../agent-runtime/cache-strategy.mjs';
31
31
 
32
+ function isUnsafeToRetry(err) {
33
+ return err?.liveTextEmitted === true || err?.emittedToolCall === true || err?.unsafeToRetry === true;
34
+ }
35
+
32
36
  function shouldFallbackDirectTransport(err, options) {
33
37
  const status = Number(err?.httpStatus || err?.status || 0);
34
- const unsafeToRetry = err?.liveTextEmitted === true || err?.emittedToolCall === true || err?.unsafeToRetry === true;
35
- if (unsafeToRetry || options?.signal?.aborted || options?.enabled === false) return false;
38
+ if (isUnsafeToRetry(err) || options?.signal?.aborted || options?.enabled === false) return false;
36
39
  // Public OpenAI can reject the WebSocket upgrade while still supporting
37
40
  // the same request over HTTP/SSE. Only the shared transport's explicit
38
41
  // handshake marker may turn these application-looking statuses into a
@@ -205,8 +208,7 @@ export class OpenAIDirectProvider {
205
208
  try {
206
209
  return await sendHttp({ ...common, auth: a, opts, fetchFn: opts._fetchFn });
207
210
  } catch (err) {
208
- const unsafe = err?.liveTextEmitted === true || err?.emittedToolCall === true || err?.unsafeToRetry === true;
209
- if (!reload || err?.httpStatus !== 401 || unsafe) throw err;
211
+ if (!reload || err?.httpStatus !== 401 || isUnsafeToRetry(err)) throw err;
210
212
  // The replay discards this attempt; its reported usage stays billed.
211
213
  noteErrorUsage(err);
212
214
  process.stderr.write('[openai-ws] 401 — reloading apiKey and retrying once over HTTP\n');
@@ -252,8 +254,7 @@ export class OpenAIDirectProvider {
252
254
  const status = err?.httpStatus;
253
255
  // Live-text/tool invariant: never reissue a turn that already
254
256
  // relayed visible output or dispatched a tool call.
255
- const unsafeToRetry =
256
- err?.liveTextEmitted === true || err?.emittedToolCall === true || err?.unsafeToRetry === true;
257
+ const unsafeToRetry = isUnsafeToRetry(err);
257
258
  // (1) 401 → reload only OpenAI auth and retry once over WS.
258
259
  // Ordinary 403 is a permission/policy decision, not evidence that
259
260
  // an API key rotated. Match the OAuth path and keep it terminal.