mixdog 0.9.3 → 0.9.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 (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,20 +1,15 @@
1
1
  import { createRequire } from 'node:module';
2
- import { createHash } from 'crypto';
3
2
  import { loadConfig } from '../config.mjs';
4
- import { shouldFallbackTransport, withRetry } from './retry-classifier.mjs';
3
+ import { withRetry } from './retry-classifier.mjs';
5
4
  import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
6
5
  import { sendViaWebSocket } from './openai-oauth-ws.mjs';
7
6
  import {
8
7
  consumeCompatChatCompletionStream,
9
8
  consumeCompatResponsesStream,
10
- parseCompletedToolCallArgumentsJson,
11
9
  } from './openai-compat-stream.mjs';
12
10
  import { enrichModels, getModelMetadataSync } from './model-catalog.mjs';
11
+ import { sanitizeModelList } from './model-list-sanitize.mjs';
13
12
  import { appendAgentTrace, traceAgentUsage } from '../agent-trace.mjs';
14
- import {
15
- resolveProviderCacheKey,
16
- resolveProviderPromptCacheLane,
17
- } from '../agent-runtime/cache-strategy.mjs';
18
13
  import {
19
14
  PROVIDER_FIRST_BYTE_TIMEOUT_MS,
20
15
  PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
@@ -22,17 +17,40 @@ import {
22
17
  createPassthroughSignal,
23
18
  resolveTimeoutMs,
24
19
  } from '../stall-policy.mjs';
25
- import { traceHash, stableTraceStringify, summarizeTraceTools, traceTextShape } from './trace-utils.mjs';
20
+ import { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
26
21
  import {
27
- normalizeContentForOpenAIChat,
28
- normalizeContentForOpenAIResponses,
29
- splitToolContentForOpenAIChat,
30
- splitToolContentForOpenAIResponses,
31
- } from './media-normalization.mjs';
22
+ summarizeTraceMessages,
23
+ extractCompatCachedTokens,
24
+ } from './openai-compat-trace.mjs';
32
25
  import {
33
- customToolCallFromResponseItem,
34
- } from './custom-tool-wire.mjs';
35
- import { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
26
+ resolveCompatMaxOutputTokens,
27
+ toOpenAIMessages,
28
+ toOpenAITools,
29
+ toResponsesTools,
30
+ nativeResponsesTools,
31
+ knownToolNamesFromOpenAITools,
32
+ knownToolNamesFromResponsesTools,
33
+ parseToolCalls,
34
+ parseResponsesToolCalls,
35
+ responseOutputText,
36
+ collectCompatResponseSearchSources,
37
+ xaiSystemInstructions,
38
+ toXaiResponsesInput,
39
+ } from './openai-compat-wire.mjs';
40
+ import {
41
+ xaiCacheRouting,
42
+ xaiResponsesCacheRouting,
43
+ normalizeXaiReasoningEffort,
44
+ normalizeOpencodeGoReasoningEffort,
45
+ useXaiResponsesApi,
46
+ useXaiResponsesWebSocket,
47
+ useXaiResponsesWebSocketWarmup,
48
+ _shouldFallbackXaiWsToHttp,
49
+ withXaiResponsesCacheLane,
50
+ writeCompatCacheTrace,
51
+ traceXaiResponsesCacheContext,
52
+ writeXaiResponsesCacheTrace,
53
+ } from './openai-compat-xai.mjs';
36
54
 
37
55
  const requireOpenAI = createRequire(import.meta.url);
38
56
  let _OpenAI = null;
@@ -79,1132 +97,11 @@ function assertSafeBaseURL(rawURL, providerName) {
79
97
  }
80
98
 
81
99
 
82
- function summarizeTraceMessages(messages) {
83
- const summaries = (messages || []).map((m, index) => {
84
- const content = typeof m?.content === 'string'
85
- ? { type: 'text', ...traceTextShape(m.content) }
86
- : { type: m?.content == null ? 'null' : typeof m.content, hash: traceHash(stableTraceStringify(m?.content ?? null)) };
87
- const toolCalls = Array.isArray(m?.tool_calls)
88
- ? m.tool_calls.map(tc => ({
89
- name: tc?.function?.name || null,
90
- argsHash: traceHash(tc?.function?.arguments || ''),
91
- }))
92
- : [];
93
- return {
94
- index,
95
- role: m?.role || null,
96
- content,
97
- ...(typeof m?.reasoning_content === 'string'
98
- ? { reasoningContent: traceTextShape(m.reasoning_content) }
99
- : {}),
100
- toolCallCount: toolCalls.length,
101
- ...(toolCalls.length ? { toolCalls } : {}),
102
- };
103
- });
104
- if (summaries.length <= 12) return summaries;
105
- return [
106
- ...summaries.slice(0, 8),
107
- { omittedTurns: summaries.length - 12 },
108
- ...summaries.slice(-4),
109
- ];
110
- }
111
-
112
-
113
- function extractCompatCachedTokens(usage) {
114
- const candidates = [
115
- usage?.prompt_tokens_details?.cached_tokens,
116
- usage?.input_tokens_details?.cached_tokens,
117
- usage?.prompt_cache_hit_tokens,
118
- usage?.cached_prompt_text_tokens,
119
- ];
120
- for (const v of candidates) {
121
- const n = Number(v);
122
- if (Number.isFinite(n) && n > 0) return n;
123
- }
124
- for (const v of candidates) {
125
- const n = Number(v);
126
- if (Number.isFinite(n)) return n;
127
- }
128
- return 0;
129
- }
130
-
131
- function positiveTokenInt(value) {
132
- const n = Number(value);
133
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
134
- }
135
-
136
- function resolveCompatMaxOutputTokens(opts = {}) {
137
- return positiveTokenInt(
138
- opts.maxOutputTokens
139
- ?? opts.outputTokens
140
- ?? opts.max_output_tokens
141
- ?? opts.maxTokens
142
- ?? opts.max_tokens,
143
- );
144
- }
145
-
146
- function xaiPrefixSeed({ opts, params, rawTools, model }) {
147
- const providerKey = resolveProviderCacheKey(opts, 'xai');
148
- const systemMessages = (params?.messages || [])
149
- .filter(m => m?.role === 'system')
150
- .map(m => String(m?.content ?? ''));
151
- return stableTraceStringify({
152
- scope: 'xai-prefix-model-system-tools',
153
- providerKey: String(providerKey),
154
- model: model || null,
155
- systemMessages,
156
- tools: summarizeTraceTools(rawTools),
157
- });
158
- }
159
-
160
- function xaiCacheRouting(opts, params, rawTools, model) {
161
- const sessionId = String(opts?.sessionId || opts?.session?.id || '').trim();
162
- const providerKey = resolveProviderCacheKey(opts, 'xai');
163
- const prefixSeed = xaiPrefixSeed({ opts, params, rawTools, model });
164
- const prefixHash = traceHash(prefixSeed);
165
- const routingSeed = stableTraceStringify({
166
- scope: 'xai-chat-session-v1',
167
- providerKey: String(providerKey),
168
- model: model || null,
169
- sessionId: sessionId || `ephemeral:${process.pid}`,
170
- });
171
- return {
172
- key: deterministicUuidFromKey(routingSeed),
173
- mode: sessionId ? 'session' : 'ephemeral',
174
- seedHash: traceHash(routingSeed),
175
- prefixHash,
176
- ownerSessionHash: sessionId ? traceHash(sessionId) : null,
177
- };
178
- }
179
-
180
- function xaiResponsesCacheRouting(opts, params, rawTools, model) {
181
- // Default to 'prefix' so parallel workers sharing the same model + system
182
- // + tools land on a common prompt_cache_key, letting xAI's server-side
183
- // prefix cache hit across sessions instead of cold-starting per worker.
184
- // Override with 'session' (env or opts) for legacy session-isolated lanes.
185
- const scope = String(opts?.xaiResponsesCacheScope || process.env.MIXDOG_XAI_RESPONSES_CACHE_SCOPE || 'prefix')
186
- .trim()
187
- .toLowerCase();
188
- if (scope !== 'prefix') {
189
- return xaiCacheRouting(opts, params, rawTools, model);
190
- }
191
- const sessionId = String(opts?.sessionId || opts?.session?.id || '').trim();
192
- const providerKey = resolveProviderCacheKey(opts, 'xai');
193
- const prefixSeed = xaiPrefixSeed({ opts, params, rawTools, model });
194
- const prefixHash = traceHash(prefixSeed);
195
- const routingSeed = stableTraceStringify({
196
- scope: 'xai-responses-prefix-v1',
197
- providerKey: String(providerKey),
198
- model: model || null,
199
- prefixHash,
200
- });
201
- return {
202
- key: deterministicUuidFromKey(routingSeed),
203
- mode: 'prefix',
204
- seedHash: traceHash(routingSeed),
205
- prefixHash,
206
- ownerSessionHash: sessionId ? traceHash(sessionId) : null,
207
- };
208
- }
209
-
210
- function normalizeXaiReasoningEffort(value) {
211
- const effort = String(value || '').trim().toLowerCase();
212
- return ['none', 'low', 'medium', 'high'].includes(effort) ? effort : null;
213
- }
214
-
215
- function opencodeGoReasoningEffortValues(modelInfo) {
216
- const effort = (modelInfo?.reasoningOptions || []).find((option) => option?.type === 'effort');
217
- return Array.isArray(effort?.values)
218
- ? effort.values.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean)
219
- : [];
220
- }
221
-
222
- function normalizeOpencodeGoReasoningEffort(value, modelInfo) {
223
- const allowed = opencodeGoReasoningEffortValues(modelInfo);
224
- if (!allowed.length) return null;
225
- const effort = String(value || '').trim().toLowerCase();
226
- if (allowed.includes(effort)) return effort;
227
- if ((effort === 'max' || effort === 'xhigh') && allowed.includes('max')) return 'max';
228
- if (['high', 'medium', 'low'].includes(effort) && allowed.includes('high')) return 'high';
229
- return null;
230
- }
231
-
232
- function useXaiResponsesApi(opts, config) {
233
- const raw = opts?.xaiApiMode
234
- ?? config?.apiMode
235
- ?? config?.xaiApiMode
236
- ?? process.env.MIXDOG_XAI_API_MODE
237
- ?? process.env.MIXDOG_XAI_RESPONSES;
238
- if (raw == null || raw === '') return true;
239
- const mode = String(raw).trim().toLowerCase();
240
- return !['0', 'false', 'off', 'chat', 'chat-completions', 'chat_completions'].includes(mode);
241
- }
242
-
243
- function useXaiResponsesWebSocket(opts, config) {
244
- const raw = opts?.xaiResponsesTransport
245
- ?? opts?.xaiTransport
246
- ?? config?.responsesTransport
247
- ?? config?.transport
248
- ?? process.env.MIXDOG_XAI_RESPONSES_TRANSPORT
249
- ?? process.env.MIXDOG_XAI_TRANSPORT;
250
- if (raw == null || raw === '') return true;
251
- const transport = String(raw).trim().toLowerCase();
252
- return !['0', 'false', 'off', 'http', 'https', 'responses-http', 'sdk'].includes(transport);
253
- }
254
-
255
- function _envFlag(name, fallback = true) {
256
- const raw = process.env[name];
257
- if (raw == null || raw === '') return fallback;
258
- return !['0', 'false', 'off', 'no'].includes(String(raw).toLowerCase());
259
- }
260
-
261
- // xAI WS→HTTP transport fallback → shared shouldFallbackTransport
262
- // (retry-classifier.mjs). Identical deny-order + allow-list; the per-provider
263
- // env flag is computed here and passed via `enabled`.
264
- function _shouldFallbackXaiWsToHttp(err, signal) {
265
- return shouldFallbackTransport(err, {
266
- signal,
267
- enabled: _envFlag('MIXDOG_XAI_WS_HTTP_FALLBACK', true),
268
- });
269
- }
270
-
271
- function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseId, instructions, rawTools }) {
272
- if (previousResponseId) return false;
273
- const raw = opts?.xaiResponsesWarmup
274
- ?? opts?.xaiWsWarmup
275
- ?? config?.responsesWarmup
276
- ?? config?.wsWarmup
277
- ?? process.env.MIXDOG_XAI_RESPONSES_WARMUP
278
- ?? process.env.MIXDOG_XAI_WS_WARMUP;
279
- if (raw != null && raw !== '') {
280
- const mode = String(raw).trim().toLowerCase();
281
- if (['0', 'false', 'off', 'none', 'disabled'].includes(mode)) return false;
282
- if (['1', 'true', 'on', 'always', 'force'].includes(mode)) return true;
283
- }
284
- return String(instructions || '').length >= 2048 || (Array.isArray(rawTools) && rawTools.length >= 10);
285
- }
286
-
287
- // Match OpenAI OAuth/API cache-lane semantics: default to 12 stable shards (via
288
- // resolveProviderPromptCacheLane) and serialize each final shard. This gives
289
- // Grok/xAI 10+ worker fanout without concurrent same-key cache contention.
290
- const XAI_RESPONSES_CACHE_LANE_DEFAULT_MAX_IN_FLIGHT = 1;
291
- const xaiResponsesCacheLanes = new Map();
292
-
293
- function parseXaiPositiveInt(value, fallback) {
294
- if (value == null || value === '') return fallback;
295
- const text = String(value).trim().toLowerCase();
296
- if (['0', 'false', 'off', 'none', 'disabled', 'unlimited', 'unbounded', 'auto'].includes(text)) return 0;
297
- const n = Number(text);
298
- if (!Number.isFinite(n)) return fallback;
299
- return Math.max(0, Math.floor(n));
300
- }
301
-
302
- function xaiResponsesCacheLaneMaxInFlight(opts, config) {
303
- return parseXaiPositiveInt(
304
- opts?.xaiCacheMaxInFlight
305
- ?? opts?.xaiResponsesCacheMaxInFlight
306
- ?? opts?.grokCacheMaxInFlight
307
- ?? opts?.grokResponsesCacheMaxInFlight
308
- ?? config?.xaiCacheMaxInFlight
309
- ?? config?.xaiResponsesCacheMaxInFlight
310
- ?? config?.grokCacheMaxInFlight
311
- ?? config?.grokResponsesCacheMaxInFlight
312
- ?? process.env.MIXDOG_XAI_CACHE_MAX_INFLIGHT
313
- ?? process.env.MIXDOG_XAI_RESPONSES_CACHE_MAX_INFLIGHT
314
- ?? process.env.MIXDOG_GROK_CACHE_MAX_INFLIGHT
315
- ?? process.env.MIXDOG_GROK_RESPONSES_CACHE_MAX_INFLIGHT
316
- ?? process.env.MIXDOG_GROK_OAUTH_CACHE_MAX_INFLIGHT
317
- ?? process.env.MIXDOG_GROK_OAUTH_RESPONSES_CACHE_MAX_INFLIGHT,
318
- XAI_RESPONSES_CACHE_LANE_DEFAULT_MAX_IN_FLIGHT,
319
- );
320
- }
321
-
322
- function xaiResponsesCacheLaneQueueTimeoutMs(opts, config) {
323
- return parseXaiPositiveInt(
324
- opts?.xaiCacheQueueTimeoutMs
325
- ?? opts?.xaiResponsesCacheQueueTimeoutMs
326
- ?? opts?.grokCacheQueueTimeoutMs
327
- ?? opts?.grokResponsesCacheQueueTimeoutMs
328
- ?? config?.xaiCacheQueueTimeoutMs
329
- ?? config?.xaiResponsesCacheQueueTimeoutMs
330
- ?? config?.grokCacheQueueTimeoutMs
331
- ?? config?.grokResponsesCacheQueueTimeoutMs
332
- ?? process.env.MIXDOG_XAI_CACHE_QUEUE_TIMEOUT_MS
333
- ?? process.env.MIXDOG_XAI_RESPONSES_CACHE_QUEUE_TIMEOUT_MS
334
- ?? process.env.MIXDOG_GROK_CACHE_QUEUE_TIMEOUT_MS
335
- ?? process.env.MIXDOG_GROK_RESPONSES_CACHE_QUEUE_TIMEOUT_MS
336
- ?? process.env.MIXDOG_GROK_OAUTH_CACHE_QUEUE_TIMEOUT_MS
337
- ?? process.env.MIXDOG_GROK_OAUTH_RESPONSES_CACHE_QUEUE_TIMEOUT_MS,
338
- 0,
339
- );
340
- }
341
-
342
- function xaiResponsesPromptCacheLane(opts, config, cacheRouting) {
343
- const shardOverride =
344
- opts?.xaiCacheLaneShards
345
- ?? opts?.xaiResponsesCacheLaneShards
346
- ?? opts?.xaiCacheMaxParallel
347
- ?? opts?.xaiResponsesCacheMaxParallel
348
- ?? opts?.grokCacheLaneShards
349
- ?? opts?.grokResponsesCacheLaneShards
350
- ?? opts?.grokCacheMaxParallel
351
- ?? opts?.grokResponsesCacheMaxParallel
352
- ?? config?.xaiCacheLaneShards
353
- ?? config?.xaiResponsesCacheLaneShards
354
- ?? config?.xaiCacheMaxParallel
355
- ?? config?.xaiResponsesCacheMaxParallel
356
- ?? config?.grokCacheLaneShards
357
- ?? config?.grokResponsesCacheLaneShards
358
- ?? config?.grokCacheMaxParallel
359
- ?? config?.grokResponsesCacheMaxParallel
360
- ?? process.env.MIXDOG_XAI_RESPONSES_CACHE_MAX_PARALLEL
361
- ?? process.env.MIXDOG_XAI_RESPONSES_CACHE_LANE_SHARDS
362
- ?? process.env.MIXDOG_GROK_CACHE_MAX_PARALLEL
363
- ?? process.env.MIXDOG_GROK_CACHE_LANE_SHARDS
364
- ?? process.env.MIXDOG_GROK_RESPONSES_CACHE_MAX_PARALLEL
365
- ?? process.env.MIXDOG_GROK_RESPONSES_CACHE_LANE_SHARDS
366
- ?? process.env.MIXDOG_GROK_OAUTH_CACHE_MAX_PARALLEL
367
- ?? process.env.MIXDOG_GROK_OAUTH_CACHE_LANE_SHARDS
368
- ?? process.env.MIXDOG_GROK_OAUTH_RESPONSES_CACHE_MAX_PARALLEL
369
- ?? process.env.MIXDOG_GROK_OAUTH_RESPONSES_CACHE_LANE_SHARDS;
370
- const autoOverride =
371
- opts?.xaiCacheLaneAuto
372
- ?? opts?.xaiResponsesCacheLaneAuto
373
- ?? opts?.grokCacheLaneAuto
374
- ?? opts?.grokResponsesCacheLaneAuto
375
- ?? config?.xaiCacheLaneAuto
376
- ?? config?.xaiResponsesCacheLaneAuto
377
- ?? config?.grokCacheLaneAuto
378
- ?? config?.grokResponsesCacheLaneAuto
379
- ?? process.env.MIXDOG_XAI_RESPONSES_CACHE_LANE_AUTO
380
- ?? process.env.MIXDOG_GROK_CACHE_LANE_AUTO
381
- ?? process.env.MIXDOG_GROK_RESPONSES_CACHE_LANE_AUTO
382
- ?? process.env.MIXDOG_GROK_OAUTH_CACHE_LANE_AUTO
383
- ?? process.env.MIXDOG_GROK_OAUTH_RESPONSES_CACHE_LANE_AUTO;
384
- const slotOverride =
385
- opts?.xaiCacheLaneSlot
386
- ?? opts?.xaiResponsesCacheLaneSlot
387
- ?? opts?.grokCacheLaneSlot
388
- ?? opts?.grokResponsesCacheLaneSlot;
389
- const seed = String(
390
- opts?.xaiCacheLaneSeed
391
- ?? opts?.xaiResponsesCacheLaneSeed
392
- ?? opts?.grokCacheLaneSeed
393
- ?? opts?.grokResponsesCacheLaneSeed
394
- ?? opts?.promptCacheLaneSeed
395
- ?? opts?.sessionId
396
- ?? opts?.session?.id
397
- ?? cacheRouting?.ownerSessionHash
398
- ?? cacheRouting?.key
399
- ?? '',
400
- );
401
- return resolveProviderPromptCacheLane('xai', {
402
- ...opts,
403
- ...(shardOverride !== undefined ? { promptCacheLaneShards: shardOverride } : {}),
404
- ...(autoOverride !== undefined ? { promptCacheLaneAuto: autoOverride } : {}),
405
- ...(slotOverride !== undefined ? { promptCacheLaneSlot: slotOverride } : {}),
406
- promptCacheLaneSeed: seed || 'xai-cache-lane',
407
- }, config);
408
- }
409
-
410
- function xaiResponsesCacheLaneKey({ model, cacheRouting, opts, config }) {
411
- const prefix = cacheRouting?.prefixHash || cacheRouting?.seedHash || cacheRouting?.key || 'unknown-prefix';
412
- const lane = xaiResponsesPromptCacheLane(opts, config, cacheRouting);
413
- const shard = Number.isFinite(Number(lane?.slot)) ? Number(lane.slot) : 0;
414
- return {
415
- key: `xai-responses:${model || 'default'}:${prefix}:shard-${shard}`,
416
- shard,
417
- lane,
418
- };
419
- }
420
-
421
- function getXaiResponsesCacheLaneState(key, maxInFlight) {
422
- let state = xaiResponsesCacheLanes.get(key);
423
- if (!state) {
424
- state = { key, active: 0, queue: [], maxInFlight, nextId: 0 };
425
- xaiResponsesCacheLanes.set(key, state);
426
- }
427
- state.maxInFlight = maxInFlight;
428
- return state;
429
- }
430
-
431
- function cleanupXaiResponsesCacheLane(state) {
432
- if (state.active === 0 && state.queue.length === 0) {
433
- xaiResponsesCacheLanes.delete(state.key);
434
- }
435
- }
436
-
437
- function removeQueuedXaiCacheLaneRequest(state, request) {
438
- const index = state.queue.indexOf(request);
439
- if (index >= 0) state.queue.splice(index, 1);
440
- cleanupXaiResponsesCacheLane(state);
441
- }
442
-
443
- function makeXaiCacheLaneHandle(state, requestId, enqueuedAt) {
444
- let released = false;
445
- return {
446
- requestId,
447
- waitedMs: Date.now() - enqueuedAt,
448
- activeCount: state.active,
449
- queueDepth: state.queue.length,
450
- release() {
451
- if (released) return;
452
- released = true;
453
- releaseXaiResponsesCacheLane(state);
454
- },
455
- };
456
- }
457
-
458
- function releaseXaiResponsesCacheLane(state) {
459
- state.active = Math.max(0, state.active - 1);
460
- while (state.queue.length > 0 && state.active < state.maxInFlight) {
461
- const next = state.queue.shift();
462
- next.cleanup?.();
463
- state.active += 1;
464
- next.resolve(makeXaiCacheLaneHandle(state, next.requestId, next.enqueuedAt));
465
- }
466
- cleanupXaiResponsesCacheLane(state);
467
- }
468
-
469
- function acquireXaiResponsesCacheLane({ key, maxInFlight, signal, timeoutMs }) {
470
- const state = getXaiResponsesCacheLaneState(key, maxInFlight);
471
- const requestId = ++state.nextId;
472
- const enqueuedAt = Date.now();
473
- if (state.active < state.maxInFlight) {
474
- state.active += 1;
475
- return Promise.resolve(makeXaiCacheLaneHandle(state, requestId, enqueuedAt));
476
- }
477
- return new Promise((resolve, reject) => {
478
- const request = {
479
- requestId,
480
- enqueuedAt,
481
- resolve,
482
- reject,
483
- cleanup: null,
484
- };
485
- const cleanup = () => {
486
- if (request.timer) clearTimeout(request.timer);
487
- if (signal && request.abortListener) signal.removeEventListener('abort', request.abortListener);
488
- };
489
- request.cleanup = cleanup;
490
- request.abortListener = () => {
491
- cleanup();
492
- removeQueuedXaiCacheLaneRequest(state, request);
493
- const reason = signal?.reason;
494
- reject(reason instanceof Error ? reason : new Error('xAI cache lane wait aborted'));
495
- };
496
- if (signal?.aborted) {
497
- request.abortListener();
498
- return;
499
- }
500
- if (signal) signal.addEventListener('abort', request.abortListener, { once: true });
501
- if (timeoutMs > 0) {
502
- request.timer = setTimeout(() => {
503
- cleanup();
504
- removeQueuedXaiCacheLaneRequest(state, request);
505
- reject(new Error(`xAI cache lane wait timed out after ${timeoutMs}ms`));
506
- }, timeoutMs);
507
- request.timer.unref?.();
508
- }
509
- state.queue.push(request);
510
- });
511
- }
512
-
513
- function traceXaiCacheLane(opts, payload) {
514
- if (!compatCacheTraceEnabled('xai')) return;
515
- try {
516
- appendAgentTrace({
517
- sessionId: opts?.sessionId || opts?.session?.id || null,
518
- iteration: Number.isFinite(Number(opts?.iteration)) ? Number(opts.iteration) : null,
519
- kind: 'cache_lane',
520
- ...payload,
521
- payload,
522
- });
523
- } catch {}
524
- }
525
-
526
- async function withXaiResponsesCacheLane({ opts, config, cacheRouting, model, transport, previousResponseId, inputCount, signal }, fn) {
527
- const maxInFlight = xaiResponsesCacheLaneMaxInFlight(opts, config);
528
- if (maxInFlight <= 0) {
529
- const laneMeta = { enabled: false, maxInFlight: 0 };
530
- return { value: await fn(laneMeta), laneMeta };
531
- }
532
- const { key: laneKey, shard, lane } = xaiResponsesCacheLaneKey({ model, cacheRouting, opts, config });
533
- const timeoutMs = xaiResponsesCacheLaneQueueTimeoutMs(opts, config);
534
- const state = getXaiResponsesCacheLaneState(laneKey, maxInFlight);
535
- const queued = state.active >= state.maxInFlight;
536
- if (queued) {
537
- traceXaiCacheLane(opts, {
538
- provider: 'xai',
539
- api: 'responses',
540
- transport,
541
- event: 'queued',
542
- lane_key_hash: traceHash(laneKey),
543
- lane_shard: shard,
544
- lane_shards: Number.isFinite(Number(lane?.shards)) ? Number(lane.shards) : null,
545
- lane_auto: lane?.auto === true,
546
- lane_seed_hash: lane?.seedHash || null,
547
- max_in_flight: maxInFlight,
548
- active: state.active,
549
- queue_depth: state.queue.length,
550
- previous_response_used: !!previousResponseId,
551
- input_count: inputCount,
552
- });
553
- }
554
- const handle = await acquireXaiResponsesCacheLane({ key: laneKey, maxInFlight, signal, timeoutMs });
555
- const laneMeta = {
556
- enabled: true,
557
- laneKeyHash: traceHash(laneKey),
558
- shard,
559
- shards: Number.isFinite(Number(lane?.shards)) ? Number(lane.shards) : null,
560
- auto: lane?.auto === true,
561
- seedHash: lane?.seedHash || null,
562
- maxInFlight,
563
- queued,
564
- waitMs: handle.waitedMs,
565
- activeAfterAcquire: handle.activeCount,
566
- queueDepthAfterAcquire: handle.queueDepth,
567
- };
568
- traceXaiCacheLane(opts, {
569
- provider: 'xai',
570
- api: 'responses',
571
- transport,
572
- event: 'acquired',
573
- lane_key_hash: laneMeta.laneKeyHash,
574
- lane_shard: shard,
575
- lane_shards: laneMeta.shards,
576
- lane_auto: laneMeta.auto,
577
- lane_seed_hash: laneMeta.seedHash,
578
- max_in_flight: maxInFlight,
579
- wait_ms: laneMeta.waitMs,
580
- active: laneMeta.activeAfterAcquire,
581
- queue_depth: laneMeta.queueDepthAfterAcquire,
582
- previous_response_used: !!previousResponseId,
583
- input_count: inputCount,
584
- });
585
- const startedAt = Date.now();
586
- try {
587
- return { value: await fn(laneMeta), laneMeta };
588
- } finally {
589
- handle.release();
590
- traceXaiCacheLane(opts, {
591
- provider: 'xai',
592
- api: 'responses',
593
- transport,
594
- event: 'released',
595
- lane_key_hash: laneMeta.laneKeyHash,
596
- lane_shard: shard,
597
- lane_shards: laneMeta.shards,
598
- lane_auto: laneMeta.auto,
599
- lane_seed_hash: laneMeta.seedHash,
600
- max_in_flight: maxInFlight,
601
- held_ms: Date.now() - startedAt,
602
- previous_response_used: !!previousResponseId,
603
- input_count: inputCount,
604
- });
605
- }
606
- }
607
-
608
- function deterministicUuidFromKey(key) {
609
- const hex = createHash('sha256').update(String(key ?? '')).digest('hex');
610
- const variant = ((Number.parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
611
- return [
612
- hex.slice(0, 8),
613
- hex.slice(8, 12),
614
- '4' + hex.slice(13, 16),
615
- variant + hex.slice(17, 20),
616
- hex.slice(20, 32),
617
- ].join('-');
618
- }
619
-
620
- function compatCacheTraceEnabled(provider) {
621
- return process.env.MIXDOG_COMPAT_CACHE_TRACE === '1'
622
- || process.env.MIXDOG_PROVIDER_CACHE_TRACE === '1'
623
- || (provider === 'xai' && process.env.MIXDOG_XAI_CACHE_TRACE === '1');
624
- }
625
-
626
- function writeCompatCacheTrace({ provider, model, opts, params, rawTools, response, cacheRoutingKey, cacheRouting }) {
627
- if (!compatCacheTraceEnabled(provider)) return;
628
- try {
629
- const usage = response?.usage || {};
630
- const inputTokens = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0);
631
- const cachedTokens = extractCompatCachedTokens(usage);
632
- const toolShape = summarizeTraceTools(rawTools);
633
- const traceMessages = Array.isArray(params?.messages) ? params.messages : [];
634
- const trace = {
635
- event: 'chat.completions',
636
- provider,
637
- model,
638
- responseModel: response?.model || null,
639
- owner: opts?.session?.owner || null,
640
- role: opts?.session?.role || opts?.role || null,
641
- permission: opts?.session?.permission || null,
642
- toolPermission: opts?.session?.toolPermission || null,
643
- profileId: opts?.session?.profileId || null,
644
- sourceType: opts?.session?.sourceType || null,
645
- sourceName: opts?.session?.sourceName || null,
646
- sessionIdHash: opts?.sessionId ? traceHash(opts.sessionId) : null,
647
- providerCacheKeyHash: opts?.providerCacheKey ? traceHash(opts.providerCacheKey) : null,
648
- promptCacheKeyHash: opts?.promptCacheKey ? traceHash(opts.promptCacheKey) : null,
649
- xGrokConvIdHash: provider === 'xai' && cacheRoutingKey ? traceHash(cacheRoutingKey) : null,
650
- xGrokConvIdSeedHash: provider === 'xai' ? cacheRouting?.seedHash || null : null,
651
- xGrokPromptPrefixHash: provider === 'xai' ? cacheRouting?.prefixHash || null : null,
652
- xGrokConvIdMode: provider === 'xai' ? cacheRouting?.mode || null : null,
653
- xGrokConvIdLaneIndex: provider === 'xai' ? cacheRouting?.laneIndex ?? null : null,
654
- xGrokConvIdActiveLanes: provider === 'xai' ? cacheRouting?.activeLanes ?? null : null,
655
- xGrokConvIdIdleLanes: provider === 'xai' ? cacheRouting?.idleLanes ?? null : null,
656
- xGrokConvIdOwnerSessionHash: provider === 'xai' ? cacheRouting?.ownerSessionHash || null : null,
657
- xaiReasoningEffort: provider === 'xai' ? params?.reasoning_effort || null : null,
658
- messageCount: traceMessages.length,
659
- messageFullHash: traceHash(stableTraceStringify(traceMessages)),
660
- messagePrefixHash: traceHash(stableTraceStringify(traceMessages.slice(0, -1))),
661
- lastMessageHash: traceMessages.length ? traceHash(stableTraceStringify(traceMessages.at(-1))) : null,
662
- messages: summarizeTraceMessages(traceMessages),
663
- toolCount: Array.isArray(rawTools) ? rawTools.length : 0,
664
- toolSchemaHash: traceHash(stableTraceStringify(toolShape)),
665
- usageKeys: Object.keys(usage || {}).sort(),
666
- promptTokenDetailsKeys: Object.keys(usage?.prompt_tokens_details || {}).sort(),
667
- inputTokenDetailsKeys: Object.keys(usage?.input_tokens_details || {}).sort(),
668
- choiceMessageKeys: Object.keys(response?.choices?.[0]?.message || {}).sort(),
669
- responseReasoningContent: typeof response?.choices?.[0]?.message?.reasoning_content === 'string'
670
- ? traceTextShape(response.choices[0].message.reasoning_content)
671
- : null,
672
- responseReasoningTokens: Number(usage?.completion_tokens_details?.reasoning_tokens ?? 0),
673
- inputTokens,
674
- outputTokens: Number(usage.completion_tokens ?? usage.output_tokens ?? 0),
675
- cachedTokens,
676
- cacheHitRate: inputTokens > 0 ? Number((cachedTokens / inputTokens).toFixed(6)) : null,
677
- costInUsdTicks: typeof usage.cost_in_usd_ticks === 'number' ? usage.cost_in_usd_ticks : null,
678
- };
679
- process.stderr.write(`[compat-cache-trace] ${JSON.stringify(trace)}\n`);
680
- } catch (err) {
681
- process.stderr.write(`[compat-cache-trace] failed: ${err?.message || err}\n`);
682
- }
683
- }
684
-
685
- function summarizeResponsesInput(input) {
686
- return (input || []).map((item, index) => ({
687
- index,
688
- type: item?.type || null,
689
- role: item?.role || null,
690
- callIdHash: item?.call_id ? traceHash(item.call_id) : null,
691
- name: item?.name || null,
692
- content: typeof item?.content === 'string'
693
- ? { type: 'text', ...traceTextShape(item.content) }
694
- : { type: item?.content == null ? 'null' : typeof item.content, hash: traceHash(stableTraceStringify(item?.content ?? null)) },
695
- output: typeof item?.output === 'string' ? traceTextShape(item.output) : null,
696
- }));
697
- }
698
-
699
- function xaiUsageStats(usage) {
700
- const inputTokens = Number(usage?.input_tokens ?? usage?.prompt_tokens ?? 0);
701
- const outputTokens = Number(usage?.output_tokens ?? usage?.completion_tokens ?? 0);
702
- const cachedTokens = extractCompatCachedTokens(usage);
703
- const hitRate = inputTokens > 0 ? Number((cachedTokens / inputTokens).toFixed(6)) : null;
704
- return { inputTokens, outputTokens, cachedTokens, hitRate };
705
- }
706
-
707
- function xaiSanitizedRequestSansInput(params) {
708
- const { input: _input, ...rest } = params || {};
709
- const out = { ...rest };
710
- if (out.prompt_cache_key) out.prompt_cache_key = traceHash(out.prompt_cache_key);
711
- if (out.previous_response_id) out.previous_response_id = traceHash(out.previous_response_id);
712
- if (typeof out.instructions === 'string') out.instructions = traceHash(out.instructions);
713
- return out;
714
- }
715
-
716
- function xaiResponsesFingerprintPayload({ model, opts, params, rawTools, response, cacheRouting, previousResponseId, inputStartIndex, continuationResetReason, transport, cacheLane }) {
717
- const usage = response?.usage || {};
718
- const { inputTokens, outputTokens, cachedTokens, hitRate } = xaiUsageStats(usage);
719
- const toolShape = summarizeTraceTools(rawTools);
720
- const instructions = typeof params?.instructions === 'string' ? params.instructions : '';
721
- const requestSansInput = xaiSanitizedRequestSansInput(params);
722
- const contextShape = {
723
- provider: 'xai',
724
- api: 'responses',
725
- model: model || null,
726
- promptCacheKeyHash: params?.prompt_cache_key ? traceHash(params.prompt_cache_key) : null,
727
- instructions,
728
- tools: toolShape,
729
- reasoning: params?.reasoning || null,
730
- store: params?.store ?? null,
731
- };
732
- const previousResponseUsed = Boolean(previousResponseId);
733
- const midTurnCold = previousResponseUsed
734
- && inputTokens >= 1024
735
- && (cachedTokens <= 512 || (hitRate != null && hitRate < 0.1));
736
- return {
737
- provider: 'xai',
738
- api: 'responses',
739
- transport: transport || null,
740
- model: model || null,
741
- response_model: response?.model || null,
742
- session_id_hash: opts?.sessionId ? traceHash(opts.sessionId) : null,
743
- provider_cache_key_hash: opts?.providerCacheKey ? traceHash(opts.providerCacheKey) : null,
744
- prompt_cache_key_option_hash: opts?.promptCacheKey ? traceHash(opts.promptCacheKey) : null,
745
- prompt_cache_key_hash: params?.prompt_cache_key ? traceHash(params.prompt_cache_key) : null,
746
- xai_prompt_prefix_hash: cacheRouting?.prefixHash || null,
747
- xai_cache_mode: cacheRouting?.mode || null,
748
- xai_cache_seed_hash: cacheRouting?.seedHash || null,
749
- owner_session_hash: cacheRouting?.ownerSessionHash || null,
750
- response_id_hash: response?.id ? traceHash(response.id) : null,
751
- previous_response_id_hash: previousResponseId ? traceHash(previousResponseId) : null,
752
- previous_response_used: previousResponseUsed,
753
- continuation_reset_reason: continuationResetReason || null,
754
- input_start_index: inputStartIndex,
755
- input_count: Array.isArray(params?.input) ? params.input.length : 0,
756
- input_hash: traceHash(stableTraceStringify(params?.input || [])),
757
- request_sans_input_hash: traceHash(stableTraceStringify(requestSansInput)),
758
- context_prefix_hash: traceHash(stableTraceStringify(contextShape)),
759
- has_instructions: instructions.length > 0,
760
- instructions_chars: instructions.length,
761
- instructions_hash: instructions ? traceHash(instructions) : null,
762
- reasoning_effort: params?.reasoning?.effort || null,
763
- tool_count: Array.isArray(rawTools) ? rawTools.length : 0,
764
- tool_schema_hash: traceHash(stableTraceStringify(toolShape)),
765
- tool_names_hash: traceHash(stableTraceStringify(toolShape.map(t => t?.name || null))),
766
- xai_cache_lane_enabled: cacheLane?.enabled === true,
767
- xai_cache_lane_hash: cacheLane?.laneKeyHash || null,
768
- xai_cache_lane_shard: Number.isFinite(Number(cacheLane?.shard)) ? Number(cacheLane.shard) : null,
769
- xai_cache_lane_shards: Number.isFinite(Number(cacheLane?.shards)) ? Number(cacheLane.shards) : null,
770
- xai_cache_lane_auto: cacheLane?.auto === true,
771
- xai_cache_lane_seed_hash: cacheLane?.seedHash || null,
772
- xai_cache_lane_max_in_flight: Number.isFinite(Number(cacheLane?.maxInFlight)) ? Number(cacheLane.maxInFlight) : null,
773
- xai_cache_lane_wait_ms: Number.isFinite(Number(cacheLane?.waitMs)) ? Number(cacheLane.waitMs) : null,
774
- xai_cache_lane_queued: cacheLane?.queued === true,
775
- input_tokens: inputTokens,
776
- output_tokens: outputTokens,
777
- cached_tokens: cachedTokens,
778
- cache_hit_rate: hitRate,
779
- mid_turn_cold: midTurnCold,
780
- };
781
- }
782
-
783
- function traceXaiResponsesCacheContext(args) {
784
- if (!compatCacheTraceEnabled('xai')) return;
785
- try {
786
- const payload = xaiResponsesFingerprintPayload(args);
787
- const sessionId = args?.opts?.sessionId || args?.opts?.session?.id || null;
788
- const iteration = Number.isFinite(Number(args?.opts?.iteration)) ? Number(args.opts.iteration) : null;
789
- appendAgentTrace({
790
- sessionId,
791
- iteration,
792
- kind: 'cache_context',
793
- ...payload,
794
- payload,
795
- });
796
- if (payload.mid_turn_cold) {
797
- const anomalyPayload = {
798
- ...payload,
799
- anomaly: 'xai_mid_turn_cold_cache',
800
- reason: 'previous_response_id_present_but_cached_tokens_low',
801
- };
802
- appendAgentTrace({
803
- sessionId,
804
- iteration,
805
- kind: 'cache_anomaly',
806
- ...anomalyPayload,
807
- payload: anomalyPayload,
808
- });
809
- }
810
- } catch (err) {
811
- process.stderr.write(`[compat-cache-trace] xai context trace failed: ${err?.message || err}\n`);
812
- }
813
- }
814
-
815
- function writeXaiResponsesCacheTrace({ model, opts, params, rawTools, response, cacheRouting, previousResponseId, inputStartIndex, continuationResetReason, transport, cacheLane }) {
816
- if (!compatCacheTraceEnabled('xai')) return;
817
- try {
818
- const usage = response?.usage || {};
819
- const fingerprint = xaiResponsesFingerprintPayload({
820
- model,
821
- opts,
822
- params,
823
- rawTools,
824
- response,
825
- cacheRouting,
826
- previousResponseId,
827
- inputStartIndex,
828
- continuationResetReason,
829
- transport,
830
- cacheLane,
831
- });
832
- const inputTokens = fingerprint.input_tokens;
833
- const cachedTokens = fingerprint.cached_tokens;
834
- const toolShape = summarizeTraceTools(rawTools);
835
- const trace = {
836
- event: 'responses',
837
- provider: 'xai',
838
- transport: transport || null,
839
- model,
840
- responseModel: response?.model || null,
841
- responseIdHash: response?.id ? traceHash(response.id) : null,
842
- previousResponseIdHash: previousResponseId ? traceHash(previousResponseId) : null,
843
- owner: opts?.session?.owner || null,
844
- role: opts?.session?.role || opts?.role || null,
845
- permission: opts?.session?.permission || null,
846
- toolPermission: opts?.session?.toolPermission || null,
847
- profileId: opts?.session?.profileId || null,
848
- sourceType: opts?.session?.sourceType || null,
849
- sourceName: opts?.session?.sourceName || null,
850
- sessionIdHash: opts?.sessionId ? traceHash(opts.sessionId) : null,
851
- promptCacheKeyHash: params?.prompt_cache_key ? traceHash(params.prompt_cache_key) : null,
852
- xGrokPromptPrefixHash: cacheRouting?.prefixHash || null,
853
- xGrokConvIdMode: cacheRouting?.mode || null,
854
- xaiReasoningEffort: params?.reasoning?.effort || null,
855
- previousResponseUsed: Boolean(previousResponseId),
856
- inputStartIndex,
857
- inputCount: Array.isArray(params?.input) ? params.input.length : 0,
858
- cacheLaneEnabled: fingerprint.xai_cache_lane_enabled,
859
- cacheLaneHash: fingerprint.xai_cache_lane_hash,
860
- cacheLaneShard: fingerprint.xai_cache_lane_shard,
861
- cacheLaneMaxInFlight: fingerprint.xai_cache_lane_max_in_flight,
862
- cacheLaneWaitMs: fingerprint.xai_cache_lane_wait_ms,
863
- cacheLaneQueued: fingerprint.xai_cache_lane_queued,
864
- input: summarizeResponsesInput(params?.input || []),
865
- toolCount: Array.isArray(rawTools) ? rawTools.length : 0,
866
- toolSchemaHash: traceHash(stableTraceStringify(toolShape)),
867
- toolNamesHash: fingerprint.tool_names_hash,
868
- requestSansInputHash: fingerprint.request_sans_input_hash,
869
- contextPrefixHash: fingerprint.context_prefix_hash,
870
- instructionsHash: fingerprint.instructions_hash,
871
- instructionsChars: fingerprint.instructions_chars,
872
- usageKeys: Object.keys(usage || {}).sort(),
873
- inputTokenDetailsKeys: Object.keys(usage?.input_tokens_details || {}).sort(),
874
- outputTokenDetailsKeys: Object.keys(usage?.output_tokens_details || {}).sort(),
875
- outputTypes: (response?.output || []).map(item => item?.type || null),
876
- inputTokens,
877
- outputTokens: fingerprint.output_tokens,
878
- cachedTokens,
879
- cacheHitRate: fingerprint.cache_hit_rate,
880
- midTurnCold: fingerprint.mid_turn_cold,
881
- costInUsdTicks: typeof usage.cost_in_usd_ticks === 'number' ? usage.cost_in_usd_ticks : null,
882
- };
883
- process.stderr.write(`[compat-cache-trace] ${JSON.stringify(trace)}\n`);
884
- } catch (err) {
885
- process.stderr.write(`[compat-cache-trace] failed: ${err?.message || err}\n`);
886
- }
887
- }
888
-
889
- function toOpenAIMessages(messages, providerName, options = {}) {
890
- // NOTE: chat.completions has no equivalent slot for replaying reasoning
891
- // encrypted_content the way the Responses API does (no `type:'reasoning'`
892
- // input item). Whatever reasoningItems may be attached to assistant
893
- // messages by the openai-oauth provider is intentionally dropped here —
894
- // strict providers (xai) reject unknown roles/types and would 400 the
895
- // request. Documented in v0.1.160 (GPT reasoning replay).
896
- //
897
- // DeepSeek thinking models require the prior turn's `reasoning_content`
898
- // string to be echoed back inside the assistant message, otherwise the API
899
- // returns 400. xAI reasoning models also preserve their official multi-turn
900
- // shape and cache prefix stability when prior assistant reasoning_content
901
- // is replayed; reasoning_effort itself remains caller/user-selected.
902
- const replaysReasoningContent = options.replaysReasoningContent === true
903
- || providerName === 'deepseek'
904
- || providerName === 'xai';
905
- const out = [];
906
- const pendingToolMedia = [];
907
- const flushToolMedia = () => {
908
- if (!pendingToolMedia.length) return;
909
- out.push({ role: 'user', content: pendingToolMedia.splice(0) });
910
- };
911
- for (const m of messages) {
912
- if (m.role === 'tool') {
913
- const { output, mediaContent } = splitToolContentForOpenAIChat(m.content);
914
- out.push({
915
- role: 'tool',
916
- tool_call_id: m.toolCallId || '',
917
- content: output,
918
- });
919
- if (mediaContent) pendingToolMedia.push(...mediaContent);
920
- continue;
921
- }
922
- flushToolMedia();
923
- if (m.role === 'assistant' && m.toolCalls?.length) {
924
- const msg = {
925
- role: 'assistant',
926
- content: normalizeContentForOpenAIChat(m.content, { role: 'assistant' }) || null,
927
- tool_calls: m.toolCalls.map((tc) => ({
928
- id: tc.id,
929
- type: 'function',
930
- function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
931
- })),
932
- };
933
- if (replaysReasoningContent && m.reasoningContent) msg.reasoning_content = m.reasoningContent;
934
- out.push(msg);
935
- continue;
936
- }
937
- if (m.role === 'assistant' && replaysReasoningContent && m.reasoningContent) {
938
- out.push({ role: m.role, content: normalizeContentForOpenAIChat(m.content, { role: 'assistant' }), reasoning_content: m.reasoningContent });
939
- continue;
940
- }
941
- out.push({ role: m.role, content: normalizeContentForOpenAIChat(m.content, { role: m.role }) });
942
- }
943
- flushToolMedia();
944
- return out;
945
- }
946
-
947
- function toOpenAITools(tools) {
948
- return tools.map((t) => ({
949
- type: 'function',
950
- function: {
951
- name: t.name,
952
- description: t.description,
953
- parameters: t.inputSchema,
954
- },
955
- }));
956
- }
957
- function toResponsesTools(tools) {
958
- return tools.map((t) => {
959
- if (t?.name === 'tool_search') {
960
- return {
961
- type: 'tool_search',
962
- execution: 'client',
963
- description: t.description,
964
- parameters: t.inputSchema,
965
- };
966
- }
967
- // xAI/Grok Responses rejects the OpenAI-only `type:'custom'` freeform
968
- // variant ("unknown variant 'custom'"). Serialize freeform/grammar
969
- // tools (e.g. apply_patch) as ordinary function tools instead. Grammar
970
- // tools may carry no usable inputSchema, so fall back to a permissive
971
- // object schema so grok still registers a valid function tool.
972
- return {
973
- type: 'function',
974
- name: t.name,
975
- description: t.description,
976
- parameters: t.inputSchema || { type: 'object', additionalProperties: true },
977
- };
978
- });
979
- }
980
- function nativeResponsesTools(opts) {
981
- return Array.isArray(opts?.nativeTools)
982
- ? opts.nativeTools.filter(t => t && typeof t === 'object')
983
- : [];
984
- }
985
- // Known tool-name sets for the leaked-tool-call guard, derived from the exact
986
- // request body so a recovered leaked call is only synthesized when it names a
987
- // tool the model was actually offered. Chat tools nest the name under
988
- // `function.name`; Responses tools carry a top-level `name`.
989
- function knownToolNamesFromOpenAITools(tools) {
990
- return new Set(
991
- (Array.isArray(tools) ? tools : [])
992
- .map((t) => (typeof t?.function?.name === 'string' ? t.function.name
993
- : typeof t?.name === 'string' ? t.name : null))
994
- .filter(Boolean),
995
- );
996
- }
997
- function knownToolNamesFromResponsesTools(tools) {
998
- return new Set(
999
- (Array.isArray(tools) ? tools : [])
1000
- .map((t) => (typeof t?.name === 'string' ? t.name : null))
1001
- .filter(Boolean),
1002
- );
1003
- }
1004
- export function parseToolCalls(choice, label) {
1005
- const calls = choice.message?.tool_calls;
1006
- if (!calls?.length)
1007
- return undefined;
1008
- // finish_reason present ⇒ the turn completed; a JSON.parse failure on the
1009
- // arguments is deterministic bad JSON (permanent), not stream truncation.
1010
- const finishReason = choice.finish_reason || null;
1011
- return calls
1012
- .filter((tc) => tc.type === 'function')
1013
- .map((tc) => ({
1014
- id: tc.id,
1015
- name: tc.function.name,
1016
- arguments: parseCompletedToolCallArgumentsJson(tc.function.arguments, label, { id: tc.id, name: tc.function.name, finishReason }),
1017
- }));
1018
- }
1019
- export function parseResponsesToolCalls(response, label) {
1020
- const out = [];
1021
- // A Responses tool call is only parsed off a completed/done item, so any
1022
- // malformed-JSON failure here is deterministic, not mid-stream truncation.
1023
- const finishReason = response?.status || 'completed';
1024
- for (const item of response?.output || []) {
1025
- if (item?.type === 'function_call') {
1026
- out.push({
1027
- id: item.call_id || item.id,
1028
- name: item.name,
1029
- arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id || item.id, name: item.name, finishReason }),
1030
- });
1031
- } else if (item?.type === 'custom_tool_call') {
1032
- const call = customToolCallFromResponseItem(item);
1033
- if (call) out.push(call);
1034
- } else if (item?.type === 'tool_search_call') {
1035
- const _tsArgs = item.arguments && typeof item.arguments === 'object' && !Array.isArray(item.arguments)
1036
- ? item.arguments
1037
- : parseCompletedToolCallArgumentsJson(item.arguments || '{}', label, { id: item.call_id || item.id, name: 'tool_search', finishReason });
1038
- out.push({
1039
- id: item.call_id || item.id,
1040
- name: 'tool_search',
1041
- // Schema is a plain object ({query,select,limit}); an array
1042
- // (parsed JSON or passthrough) must never pass through as args.
1043
- arguments: (_tsArgs && typeof _tsArgs === 'object' && !Array.isArray(_tsArgs)) ? _tsArgs : {},
1044
- nativeType: 'tool_search_call',
1045
- });
1046
- }
1047
- }
1048
- return out.length ? out : undefined;
1049
- }
1050
- function responseOutputText(response) {
1051
- if (typeof response?.output_text === 'string') return response.output_text;
1052
- const chunks = [];
1053
- for (const item of response?.output || []) {
1054
- if (item?.type !== 'message' || !Array.isArray(item.content)) continue;
1055
- for (const part of item.content) {
1056
- if (part?.type === 'output_text' && typeof part.text === 'string') chunks.push(part.text);
1057
- }
1058
- }
1059
- return chunks.join('');
1060
- }
1061
- function collectCompatResponseSearchSources(response) {
1062
- const citations = [];
1063
- const webSearchCalls = [];
1064
- const seen = new Set();
1065
- const addCitation = (source, fallback = {}) => {
1066
- if (!source) return;
1067
- if (typeof source === 'string') {
1068
- const url = source.trim();
1069
- if (!url || seen.has(url)) return;
1070
- seen.add(url);
1071
- citations.push({ title: url, url, snippet: '', source: fallback.source || 'citation', provider: 'xai' });
1072
- return;
1073
- }
1074
- if (typeof source !== 'object') return;
1075
- const url = String(
1076
- source.url
1077
- || source.uri
1078
- || source.href
1079
- || source.source_url
1080
- || source.url_citation?.url
1081
- || '',
1082
- ).trim();
1083
- if (!url || seen.has(url)) return;
1084
- seen.add(url);
1085
- citations.push({
1086
- title: String(source.title || source.name || source.query || source.url_citation?.title || fallback.title || url).trim(),
1087
- url,
1088
- snippet: String(source.snippet || source.text || source.description || '').trim(),
1089
- source: source.source || fallback.source || 'citation',
1090
- provider: source.provider || 'xai',
1091
- });
1092
- };
1093
- for (const citation of Array.isArray(response?.citations) ? response.citations : []) addCitation(citation);
1094
- for (const item of Array.isArray(response?.output) ? response.output : []) {
1095
- if (item?.type === 'web_search_call') {
1096
- webSearchCalls.push({ id: item.id || '', status: item.status || '', action: item.action || null });
1097
- const action = item.action || {};
1098
- for (const source of Array.isArray(action.sources) ? action.sources : []) addCitation(source, { title: action.query || '', source: 'web_search_call' });
1099
- if (action.url) addCitation({ url: action.url, title: action.query || '' }, { source: 'web_search_call' });
1100
- for (const url of Array.isArray(action.urls) ? action.urls : []) addCitation({ url, title: action.query || '' }, { source: 'web_search_call' });
1101
- }
1102
- for (const citation of Array.isArray(item?.citations) ? item.citations : []) addCitation(citation);
1103
- for (const part of Array.isArray(item?.content) ? item.content : []) {
1104
- for (const annotation of Array.isArray(part?.annotations) ? part.annotations : []) {
1105
- addCitation(annotation, { source: 'annotation' });
1106
- }
1107
- }
1108
- }
1109
- return { citations, webSearchCalls };
1110
- }
1111
- function toResponsesInputMessage(m, pendingToolMedia = null, customToolCallNameById = null) {
1112
- if (m.role === 'tool') {
1113
- if (Array.isArray(m.nativeToolSearch?.openaiTools)) {
1114
- return {
1115
- type: 'tool_search_output',
1116
- call_id: m.toolCallId || '',
1117
- status: 'completed',
1118
- execution: 'client',
1119
- tools: m.nativeToolSearch.openaiTools,
1120
- };
1121
- }
1122
- const { output, mediaContent } = splitToolContentForOpenAIResponses(m.content);
1123
- // xai path: never emit `custom_tool_call_output` (the `custom` variant
1124
- // is rejected by grok). Replay prior tool outputs as the standard
1125
- // `function_call_output` item regardless of original native type.
1126
- const item = {
1127
- type: 'function_call_output',
1128
- call_id: m.toolCallId || '',
1129
- output: output,
1130
- };
1131
- if (mediaContent && pendingToolMedia) pendingToolMedia.push(...mediaContent);
1132
- return item;
1133
- }
1134
- if (m.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
1135
- const items = [];
1136
- if (m.content) items.push({ role: 'assistant', content: normalizeContentForOpenAIResponses(m.content, { role: 'assistant' }) });
1137
- for (const tc of m.toolCalls) {
1138
- if (tc.nativeType === 'tool_search_call' || tc.name === 'tool_search') {
1139
- items.push({
1140
- type: 'tool_search_call',
1141
- call_id: tc.id,
1142
- execution: 'client',
1143
- arguments: tc.arguments || {},
1144
- });
1145
- } else {
1146
- // xai path: prior native `custom_tool_call` history is replayed
1147
- // as a standard `function_call` (grok rejects the `custom`
1148
- // variant). tc.arguments already holds the recovered object
1149
- // form, so the same stringify path as regular calls applies.
1150
- items.push({
1151
- type: 'function_call',
1152
- call_id: tc.id,
1153
- name: tc.name,
1154
- arguments: JSON.stringify(tc.arguments || {}),
1155
- });
1156
- }
1157
- }
1158
- return items;
1159
- }
1160
- return { role: m.role, content: normalizeContentForOpenAIResponses(m.content || '', { role: m.role }) };
1161
- }
1162
- function xaiSystemInstructions(messages) {
1163
- const instructions = (messages || [])
1164
- .filter(m => m?.role === 'system')
1165
- .map(m => String(m.content || ''))
1166
- .filter(Boolean)
1167
- .join('\n\n');
1168
- return instructions || undefined;
1169
- }
1170
- function toXaiResponsesInput(messages, providerState, options = {}) {
1171
- const includeSystem = options.includeSystem !== false;
1172
- const state = providerState?.xaiResponses || null;
1173
- let startIndex = 0;
1174
- let resetReason = null;
1175
- let previousResponseId = typeof state?.previousResponseId === 'string' ? state.previousResponseId : null;
1176
- const expectedModel = options.model ? String(options.model) : '';
1177
- const stateModel = state?.model ? String(state.model) : '';
1178
- const seen = Number.isInteger(state?.seenMessageCount) ? state.seenMessageCount : null;
1179
- if (previousResponseId && expectedModel && stateModel && stateModel !== expectedModel) {
1180
- previousResponseId = null;
1181
- resetReason = 'model_changed';
1182
- }
1183
- if (previousResponseId && (seen == null || seen < 0 || seen > messages.length)) {
1184
- previousResponseId = null;
1185
- resetReason = seen == null ? 'missing_seen_message_count' : 'seen_message_count_out_of_range';
1186
- }
1187
- if (previousResponseId) {
1188
- startIndex = Math.max(0, Math.min(seen, messages.length));
1189
- if (messages[startIndex]?.role === 'assistant') startIndex += 1;
1190
- }
1191
- const input = [];
1192
- const pendingToolMedia = [];
1193
- const customToolCallNameById = new Map();
1194
- const flushToolMedia = () => {
1195
- if (!pendingToolMedia.length) return;
1196
- input.push({ role: 'user', content: pendingToolMedia.splice(0) });
1197
- };
1198
- for (const m of messages.slice(startIndex)) {
1199
- if (!includeSystem && m.role === 'system') continue;
1200
- if (m.role !== 'tool') flushToolMedia();
1201
- const converted = toResponsesInputMessage(m, pendingToolMedia, customToolCallNameById);
1202
- if (Array.isArray(converted)) input.push(...converted);
1203
- else input.push(converted);
1204
- }
1205
- flushToolMedia();
1206
- return { input, previousResponseId, startIndex, continuationResetReason: resetReason };
1207
- }
100
+ // summarizeTraceMessages / extractCompatCachedTokens → openai-compat-trace.mjs
101
+ // resolveCompatMaxOutputTokens + message/tool wire converters and response
102
+ // parsers openai-compat-wire.mjs. Re-exported below for existing importers.
103
+ export { summarizeTraceMessages, extractCompatCachedTokens } from './openai-compat-trace.mjs';
104
+ export { parseToolCalls, parseResponsesToolCalls } from './openai-compat-wire.mjs';
1208
105
  export class OpenAICompatProvider {
1209
106
  // Chat Completions prompt_tokens is already the total (includes cached).
1210
107
  // Covers grok-oauth and all OPENAI_COMPAT_PRESETS. See registry.mjs.
@@ -1898,7 +795,7 @@ export class OpenAICompatProvider {
1898
795
  });
1899
796
  }
1900
797
  const filtered = models.filter(m => m.id);
1901
- const enriched = await enrichModels(filtered);
798
+ const enriched = sanitizeModelList(await enrichModels(filtered), { provider: this.name });
1902
799
  this._enrichedModels = enriched;
1903
800
  return enriched;
1904
801
  }