mixdog 0.7.17 → 0.8.0

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 (836) hide show
  1. package/README.md +37 -331
  2. package/package.json +67 -99
  3. package/scripts/boot-smoke.mjs +94 -0
  4. package/scripts/build-tui.mjs +52 -0
  5. package/scripts/compact-smoke.mjs +199 -0
  6. package/scripts/lead-workflow-smoke.mjs +598 -0
  7. package/scripts/live-worker-smoke.mjs +239 -0
  8. package/scripts/output-style-smoke.mjs +101 -0
  9. package/scripts/smoke-loop-report.mjs +221 -0
  10. package/scripts/smoke-loop.mjs +201 -0
  11. package/scripts/smoke.mjs +113 -0
  12. package/scripts/tool-failures.mjs +143 -0
  13. package/scripts/tool-smoke.mjs +456 -0
  14. package/src/agents/debugger/AGENT.md +3 -0
  15. package/src/agents/debugger/agent.json +6 -0
  16. package/src/agents/explore/AGENT.md +4 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/agents/heavy-worker/AGENT.md +3 -0
  19. package/src/agents/heavy-worker/agent.json +6 -0
  20. package/src/agents/maintainer/AGENT.md +3 -0
  21. package/src/agents/maintainer/agent.json +6 -0
  22. package/src/agents/reviewer/AGENT.md +3 -0
  23. package/src/agents/reviewer/agent.json +6 -0
  24. package/src/agents/scheduler-task.md +3 -0
  25. package/src/agents/web-researcher/AGENT.md +3 -0
  26. package/src/agents/web-researcher/agent.json +6 -0
  27. package/src/agents/webhook-handler.md +3 -0
  28. package/src/agents/worker/AGENT.md +3 -0
  29. package/src/agents/worker/agent.json +6 -0
  30. package/src/app.mjs +90 -0
  31. package/src/cli.mjs +11 -0
  32. package/src/defaults/hidden-roles.json +72 -0
  33. package/src/defaults/mixdog-config.template.json +15 -0
  34. package/src/hooks/lib/permission-evaluator.cjs +488 -0
  35. package/src/hooks/lib/settings-loader.cjs +112 -0
  36. package/src/lib/keychain-cjs.cjs +332 -0
  37. package/src/lib/plugin-paths.cjs +28 -0
  38. package/src/lib/rules-builder.cjs +315 -0
  39. package/src/mixdog-session-runtime.mjs +3704 -0
  40. package/src/output-styles/default.md +38 -0
  41. package/src/output-styles/extreme-simple.md +17 -0
  42. package/src/output-styles/simple.md +17 -0
  43. package/src/repl.mjs +322 -0
  44. package/src/rules/bridge/00-common.md +5 -0
  45. package/src/rules/bridge/20-skip-protocol.md +11 -0
  46. package/src/rules/bridge/30-explorer.md +4 -0
  47. package/src/rules/bridge/40-cycle1-agent.md +28 -0
  48. package/src/rules/bridge/41-cycle2-agent.md +59 -0
  49. package/src/rules/lead/00-tool-lead.md +5 -0
  50. package/src/rules/lead/01-general.md +5 -0
  51. package/src/rules/lead/02-channels.md +3 -0
  52. package/src/rules/lead/04-workflow.md +12 -0
  53. package/src/rules/shared/00-language.md +3 -0
  54. package/src/rules/shared/01-tool.md +3 -0
  55. package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
  56. package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
  57. package/src/runtime/agent/orchestrator/config.mjs +446 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
  59. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
  60. package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
  65. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
  66. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
  67. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
  68. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
  69. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
  73. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
  77. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
  78. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
  79. package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
  80. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
  81. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
  83. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  84. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
  85. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
  86. package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
  88. package/src/runtime/agent/orchestrator/session/loop.mjs +2320 -0
  89. package/src/runtime/agent/orchestrator/session/manager.mjs +2960 -0
  90. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  91. package/src/runtime/agent/orchestrator/session/store.mjs +663 -0
  92. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  93. package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
  94. package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
  95. package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
  96. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
  97. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
  100. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
  118. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  119. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
  120. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  121. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
  122. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
  123. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
  124. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
  125. package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
  128. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
  129. package/src/runtime/channels/backends/discord.mjs +781 -0
  130. package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
  131. package/src/runtime/channels/index.mjs +3309 -0
  132. package/src/runtime/channels/lib/config.mjs +285 -0
  133. package/src/runtime/channels/lib/drop-trace.mjs +71 -0
  134. package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
  135. package/src/runtime/channels/lib/holidays.mjs +138 -0
  136. package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
  137. package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
  138. package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
  139. package/src/runtime/channels/lib/scheduler.mjs +710 -0
  140. package/src/runtime/channels/lib/session-discovery.mjs +102 -0
  141. package/src/runtime/channels/lib/state-file.mjs +68 -0
  142. package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
  143. package/src/runtime/channels/lib/tool-format.mjs +124 -0
  144. package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
  146. package/src/runtime/channels/lib/webhook.mjs +1288 -0
  147. package/src/runtime/channels/tool-defs.mjs +177 -0
  148. package/src/runtime/lib/keychain-cjs.cjs +289 -0
  149. package/src/runtime/memory/data/runtime-manifest.json +40 -0
  150. package/src/runtime/memory/index.mjs +3600 -0
  151. package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
  152. package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
  153. package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
  154. package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
  155. package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
  156. package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
  157. package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
  158. package/src/runtime/memory/lib/memory-embed.mjs +300 -0
  159. package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
  160. package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
  161. package/src/runtime/memory/lib/memory.mjs +418 -0
  162. package/src/runtime/memory/lib/pg/adapter.mjs +314 -0
  163. package/src/runtime/memory/lib/pg/process.mjs +366 -0
  164. package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
  165. package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
  166. package/src/runtime/memory/lib/trace-store.mjs +734 -0
  167. package/src/runtime/memory/tool-defs.mjs +79 -0
  168. package/src/runtime/search/index.mjs +925 -0
  169. package/src/runtime/search/lib/config.mjs +61 -0
  170. package/src/runtime/search/lib/web-tools.mjs +1278 -0
  171. package/src/runtime/search/tool-defs.mjs +64 -0
  172. package/src/runtime/shared/atomic-file.mjs +435 -0
  173. package/src/runtime/shared/background-tasks.mjs +376 -0
  174. package/src/runtime/shared/child-guardian.mjs +98 -0
  175. package/src/runtime/shared/config.mjs +393 -0
  176. package/src/runtime/shared/err-text.mjs +121 -0
  177. package/src/runtime/shared/launcher-control.mjs +259 -0
  178. package/src/runtime/shared/llm/cost.mjs +66 -0
  179. package/src/runtime/shared/llm/http-agent.mjs +129 -0
  180. package/src/runtime/shared/open-url.mjs +37 -0
  181. package/src/runtime/shared/plugin-paths.mjs +25 -0
  182. package/src/runtime/shared/process-shutdown.mjs +147 -0
  183. package/src/runtime/shared/schedules-store.mjs +70 -0
  184. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  185. package/src/runtime/shared/tool-surface.mjs +950 -0
  186. package/src/runtime/shared/user-cwd.mjs +221 -0
  187. package/src/runtime/shared/user-data-guard.mjs +232 -0
  188. package/src/runtime/shared/workspace-router.mjs +259 -0
  189. package/src/standalone/bridge-tool.mjs +1414 -0
  190. package/src/standalone/channel-admin.mjs +366 -0
  191. package/src/standalone/channel-worker-preload.cjs +3 -0
  192. package/src/standalone/channel-worker.mjs +353 -0
  193. package/src/standalone/explore-tool.mjs +233 -0
  194. package/src/standalone/hook-bus.mjs +246 -0
  195. package/src/standalone/plugin-admin.mjs +247 -0
  196. package/src/standalone/provider-admin.mjs +338 -0
  197. package/src/standalone/seeds.mjs +94 -0
  198. package/src/standalone/usage-dashboard.mjs +510 -0
  199. package/src/tui/App.jsx +5438 -0
  200. package/src/tui/components/AnsiText.jsx +199 -0
  201. package/src/tui/components/ContextPanel.jsx +217 -0
  202. package/src/tui/components/Markdown.jsx +205 -0
  203. package/src/tui/components/MarkdownTable.jsx +204 -0
  204. package/src/tui/components/Message.jsx +103 -0
  205. package/src/tui/components/Picker.jsx +317 -0
  206. package/src/tui/components/PromptInput.jsx +584 -0
  207. package/src/tui/components/QueuedCommands.jsx +47 -0
  208. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  209. package/src/tui/components/Spinner.jsx +317 -0
  210. package/src/tui/components/StatusLine.jsx +87 -0
  211. package/src/tui/components/TextEntryPanel.jsx +323 -0
  212. package/src/tui/components/ToolExecution.jsx +772 -0
  213. package/src/tui/components/TurnDone.jsx +78 -0
  214. package/src/tui/components/UsagePanel.jsx +331 -0
  215. package/src/tui/dist/index.mjs +12359 -0
  216. package/src/tui/engine.mjs +2410 -0
  217. package/src/tui/figures.mjs +50 -0
  218. package/src/tui/hooks/useEngine.mjs +16 -0
  219. package/src/tui/index.jsx +254 -0
  220. package/src/tui/input-editing.mjs +242 -0
  221. package/src/tui/markdown/format-token.mjs +194 -0
  222. package/src/tui/paste-attachments.mjs +198 -0
  223. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  224. package/src/tui/spinner-verbs.mjs +45 -0
  225. package/src/tui/theme.mjs +67 -0
  226. package/src/tui/time-format.mjs +53 -0
  227. package/src/ui/ansi.mjs +115 -0
  228. package/src/ui/markdown.mjs +195 -0
  229. package/src/ui/statusline.mjs +730 -0
  230. package/src/ui/tool-card.mjs +101 -0
  231. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  232. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  233. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  234. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  235. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  236. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  237. package/src/workflows/default/WORKFLOW.md +7 -0
  238. package/src/workflows/default/workflow.json +14 -0
  239. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  240. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  241. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  242. package/vendor/ink/build/colorize.d.ts +3 -0
  243. package/vendor/ink/build/colorize.js +48 -0
  244. package/vendor/ink/build/colorize.js.map +1 -0
  245. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  246. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  247. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  248. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  249. package/vendor/ink/build/components/AnimationContext.js +13 -0
  250. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  251. package/vendor/ink/build/components/App.d.ts +24 -0
  252. package/vendor/ink/build/components/App.js +554 -0
  253. package/vendor/ink/build/components/App.js.map +1 -0
  254. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  255. package/vendor/ink/build/components/AppContext.js +25 -0
  256. package/vendor/ink/build/components/AppContext.js.map +1 -0
  257. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  258. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  259. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  260. package/vendor/ink/build/components/Box.d.ts +130 -0
  261. package/vendor/ink/build/components/Box.js +34 -0
  262. package/vendor/ink/build/components/Box.js.map +1 -0
  263. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  264. package/vendor/ink/build/components/CursorContext.js +8 -0
  265. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  266. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  267. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  268. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  269. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  270. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  271. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  272. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  273. package/vendor/ink/build/components/FocusContext.js +17 -0
  274. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  275. package/vendor/ink/build/components/Newline.d.ts +13 -0
  276. package/vendor/ink/build/components/Newline.js +8 -0
  277. package/vendor/ink/build/components/Newline.js.map +1 -0
  278. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  279. package/vendor/ink/build/components/Spacer.js +11 -0
  280. package/vendor/ink/build/components/Spacer.js.map +1 -0
  281. package/vendor/ink/build/components/Static.d.ts +24 -0
  282. package/vendor/ink/build/components/Static.js +28 -0
  283. package/vendor/ink/build/components/Static.js.map +1 -0
  284. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  285. package/vendor/ink/build/components/StderrContext.js +13 -0
  286. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  287. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  288. package/vendor/ink/build/components/StdinContext.js +20 -0
  289. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  290. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  291. package/vendor/ink/build/components/StdoutContext.js +13 -0
  292. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  293. package/vendor/ink/build/components/Text.d.ts +55 -0
  294. package/vendor/ink/build/components/Text.js +50 -0
  295. package/vendor/ink/build/components/Text.js.map +1 -0
  296. package/vendor/ink/build/components/Transform.d.ts +16 -0
  297. package/vendor/ink/build/components/Transform.js +15 -0
  298. package/vendor/ink/build/components/Transform.js.map +1 -0
  299. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  300. package/vendor/ink/build/cursor-helpers.js +62 -0
  301. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  302. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  303. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  304. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  305. package/vendor/ink/build/devtools.d.ts +1 -0
  306. package/vendor/ink/build/devtools.js +36 -0
  307. package/vendor/ink/build/devtools.js.map +1 -0
  308. package/vendor/ink/build/dom.d.ts +62 -0
  309. package/vendor/ink/build/dom.js +143 -0
  310. package/vendor/ink/build/dom.js.map +1 -0
  311. package/vendor/ink/build/get-max-width.d.ts +3 -0
  312. package/vendor/ink/build/get-max-width.js +10 -0
  313. package/vendor/ink/build/get-max-width.js.map +1 -0
  314. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  315. package/vendor/ink/build/hooks/use-animation.js +87 -0
  316. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  317. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  318. package/vendor/ink/build/hooks/use-app.js +8 -0
  319. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  320. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  321. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  322. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  323. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  324. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  325. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  326. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  327. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  328. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  329. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  330. package/vendor/ink/build/hooks/use-focus.js +43 -0
  331. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  332. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  333. package/vendor/ink/build/hooks/use-input.js +126 -0
  334. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  335. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  336. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  337. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  338. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  339. package/vendor/ink/build/hooks/use-paste.js +62 -0
  340. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  341. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  342. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  343. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  344. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  345. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  346. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  347. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  348. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  349. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  350. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  351. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  352. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  353. package/vendor/ink/build/index.d.ts +42 -0
  354. package/vendor/ink/build/index.js +24 -0
  355. package/vendor/ink/build/index.js.map +1 -0
  356. package/vendor/ink/build/ink.d.ts +146 -0
  357. package/vendor/ink/build/ink.js +1022 -0
  358. package/vendor/ink/build/ink.js.map +1 -0
  359. package/vendor/ink/build/input-parser.d.ts +10 -0
  360. package/vendor/ink/build/input-parser.js +194 -0
  361. package/vendor/ink/build/input-parser.js.map +1 -0
  362. package/vendor/ink/build/instances.d.ts +3 -0
  363. package/vendor/ink/build/instances.js +8 -0
  364. package/vendor/ink/build/instances.js.map +1 -0
  365. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  366. package/vendor/ink/build/kitty-keyboard.js +32 -0
  367. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  368. package/vendor/ink/build/log-update.d.ts +20 -0
  369. package/vendor/ink/build/log-update.js +261 -0
  370. package/vendor/ink/build/log-update.js.map +1 -0
  371. package/vendor/ink/build/measure-element.d.ts +20 -0
  372. package/vendor/ink/build/measure-element.js +13 -0
  373. package/vendor/ink/build/measure-element.js.map +1 -0
  374. package/vendor/ink/build/measure-text.d.ts +6 -0
  375. package/vendor/ink/build/measure-text.js +21 -0
  376. package/vendor/ink/build/measure-text.js.map +1 -0
  377. package/vendor/ink/build/output.d.ts +35 -0
  378. package/vendor/ink/build/output.js +328 -0
  379. package/vendor/ink/build/output.js.map +1 -0
  380. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  381. package/vendor/ink/build/parse-keypress.js +495 -0
  382. package/vendor/ink/build/parse-keypress.js.map +1 -0
  383. package/vendor/ink/build/reconciler.d.ts +4 -0
  384. package/vendor/ink/build/reconciler.js +306 -0
  385. package/vendor/ink/build/reconciler.js.map +1 -0
  386. package/vendor/ink/build/render-background.d.ts +4 -0
  387. package/vendor/ink/build/render-background.js +25 -0
  388. package/vendor/ink/build/render-background.js.map +1 -0
  389. package/vendor/ink/build/render-border.d.ts +4 -0
  390. package/vendor/ink/build/render-border.js +84 -0
  391. package/vendor/ink/build/render-border.js.map +1 -0
  392. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  393. package/vendor/ink/build/render-node-to-output.js +162 -0
  394. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  395. package/vendor/ink/build/render-to-string.d.ts +38 -0
  396. package/vendor/ink/build/render-to-string.js +116 -0
  397. package/vendor/ink/build/render-to-string.js.map +1 -0
  398. package/vendor/ink/build/render.d.ts +176 -0
  399. package/vendor/ink/build/render.js +71 -0
  400. package/vendor/ink/build/render.js.map +1 -0
  401. package/vendor/ink/build/renderer.d.ts +8 -0
  402. package/vendor/ink/build/renderer.js +64 -0
  403. package/vendor/ink/build/renderer.js.map +1 -0
  404. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  405. package/vendor/ink/build/sanitize-ansi.js +27 -0
  406. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  407. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  408. package/vendor/ink/build/squash-text-nodes.js +36 -0
  409. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  410. package/vendor/ink/build/styles.d.ts +302 -0
  411. package/vendor/ink/build/styles.js +303 -0
  412. package/vendor/ink/build/styles.js.map +1 -0
  413. package/vendor/ink/build/utils.d.ts +9 -0
  414. package/vendor/ink/build/utils.js +19 -0
  415. package/vendor/ink/build/utils.js.map +1 -0
  416. package/vendor/ink/build/wrap-text.d.ts +3 -0
  417. package/vendor/ink/build/wrap-text.js +38 -0
  418. package/vendor/ink/build/wrap-text.js.map +1 -0
  419. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  420. package/vendor/ink/build/write-synchronized.js +9 -0
  421. package/vendor/ink/build/write-synchronized.js.map +1 -0
  422. package/vendor/ink/license +10 -0
  423. package/vendor/ink/package.json +137 -0
  424. package/.claude-plugin/marketplace.json +0 -34
  425. package/.claude-plugin/plugin.json +0 -20
  426. package/.gitattributes +0 -34
  427. package/.mcp.json +0 -14
  428. package/ARCHITECTURE.md +0 -77
  429. package/CHANGELOG.md +0 -30
  430. package/CONTRIBUTING.md +0 -45
  431. package/DATA-FLOW.md +0 -79
  432. package/LICENSE +0 -21
  433. package/SECURITY.md +0 -138
  434. package/UNINSTALL.md +0 -112
  435. package/agents/maintenance.md +0 -5
  436. package/agents/memory-classification.md +0 -30
  437. package/agents/scheduler-task.md +0 -18
  438. package/agents/webhook-handler.md +0 -27
  439. package/agents/worker.md +0 -24
  440. package/bin/bridge +0 -133
  441. package/bin/statusline-launcher.mjs +0 -82
  442. package/bin/statusline-lib.mjs +0 -558
  443. package/bin/statusline.mjs +0 -615
  444. package/bun.lock +0 -927
  445. package/commands/config.md +0 -16
  446. package/commands/doctor.md +0 -13
  447. package/commands/setup.md +0 -17
  448. package/defaults/hidden-roles.json +0 -68
  449. package/defaults/memory-chunk-prompt.md +0 -63
  450. package/defaults/mixdog-config.template.json +0 -27
  451. package/defaults/user-workflow.json +0 -8
  452. package/defaults/user-workflow.md +0 -17
  453. package/hooks/hooks.json +0 -73
  454. package/hooks/lib/active-instance.cjs +0 -77
  455. package/hooks/lib/permission-evaluator.cjs +0 -411
  456. package/hooks/lib/permission-route.cjs +0 -63
  457. package/hooks/lib/settings-loader.cjs +0 -117
  458. package/hooks/post-tool-use.cjs +0 -84
  459. package/hooks/pre-mcp-sandbox.cjs +0 -158
  460. package/hooks/pre-tool-subagent.cjs +0 -258
  461. package/hooks/session-start.cjs +0 -1479
  462. package/hooks/shim-launcher.cjs +0 -65
  463. package/hooks/turn-timer.cjs +0 -82
  464. package/lib/claude-md-writer.cjs +0 -386
  465. package/lib/keychain-cjs.cjs +0 -290
  466. package/lib/plugin-paths.cjs +0 -69
  467. package/lib/rules-builder.cjs +0 -241
  468. package/native/README.md +0 -117
  469. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  470. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  471. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  472. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  473. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  474. package/prompts/code-review.txt +0 -16
  475. package/prompts/security-audit.txt +0 -17
  476. package/rules/bridge/00-common.md +0 -39
  477. package/rules/bridge/20-skip-protocol.md +0 -18
  478. package/rules/bridge/30-explorer.md +0 -33
  479. package/rules/bridge/40-cycle1-agent.md +0 -52
  480. package/rules/bridge/41-cycle2-agent.md +0 -62
  481. package/rules/lead/00-tool-lead.md +0 -61
  482. package/rules/lead/01-general.md +0 -23
  483. package/rules/lead/02-channels.md +0 -49
  484. package/rules/lead/03-team.md +0 -27
  485. package/rules/lead/04-workflow.md +0 -20
  486. package/rules/shared/00-language.md +0 -14
  487. package/rules/shared/01-tool.md +0 -138
  488. package/scripts/bootstrap.mjs +0 -130
  489. package/scripts/bridge-unify-smoke.mjs +0 -308
  490. package/scripts/build-runtime-linux.sh +0 -348
  491. package/scripts/build-runtime-macos.sh +0 -217
  492. package/scripts/build-runtime-windows.ps1 +0 -242
  493. package/scripts/builtin-utils-smoke.mjs +0 -398
  494. package/scripts/bump.mjs +0 -80
  495. package/scripts/check-json.mjs +0 -45
  496. package/scripts/check-syntax-changed.mjs +0 -102
  497. package/scripts/check-syntax.mjs +0 -58
  498. package/scripts/code-graph-batch.test.mjs +0 -33
  499. package/scripts/config-preserve-smoke.mjs +0 -180
  500. package/scripts/doctor.mjs +0 -489
  501. package/scripts/edit-normalize-fuzz.mjs +0 -130
  502. package/scripts/edit-normalize-smoke.mjs +0 -401
  503. package/scripts/edit-operation-smoke.mjs +0 -369
  504. package/scripts/edit2-smoke.mjs +0 -63
  505. package/scripts/ensure-deps.mjs +0 -259
  506. package/scripts/fuzzy-e2e.mjs +0 -28
  507. package/scripts/fuzzy-smoke.mjs +0 -26
  508. package/scripts/generate-runtime-manifest.mjs +0 -166
  509. package/scripts/guard-smoke.mjs +0 -66
  510. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  511. package/scripts/hook-routing-smoke.mjs +0 -29
  512. package/scripts/inject-input.ps1 +0 -204
  513. package/scripts/io-complex-smoke.mjs +0 -667
  514. package/scripts/io-explore-bench.mjs +0 -424
  515. package/scripts/io-guardrails-smoke.mjs +0 -205
  516. package/scripts/io-mini-bench-baseline.json +0 -11
  517. package/scripts/io-mini-bench.mjs +0 -216
  518. package/scripts/io-route-harness.mjs +0 -933
  519. package/scripts/io-telemetry-report.mjs +0 -691
  520. package/scripts/mutation-bench.mjs +0 -564
  521. package/scripts/mutation-io-smoke.mjs +0 -1097
  522. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  523. package/scripts/native-patch-smoke.mjs +0 -304
  524. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  525. package/scripts/patch-interior-context-smoke.mjs +0 -49
  526. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  527. package/scripts/perf-hook-smoke.mjs +0 -71
  528. package/scripts/permission-eval-smoke.mjs +0 -443
  529. package/scripts/prep-patch.mjs +0 -53
  530. package/scripts/prep-shim.mjs +0 -96
  531. package/scripts/provider-cache-smoke.mjs +0 -687
  532. package/scripts/report-runtime-health.mjs +0 -132
  533. package/scripts/resolve-bun.mjs +0 -60
  534. package/scripts/run-mcp.mjs +0 -1448
  535. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  536. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  537. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  538. package/scripts/smoke-runtime-negative.ps1 +0 -100
  539. package/scripts/smoke-runtime-negative.sh +0 -95
  540. package/scripts/stall-policy-smoke.mjs +0 -50
  541. package/scripts/start-memory-worker.mjs +0 -23
  542. package/scripts/statusline-launcher-smoke.mjs +0 -82
  543. package/scripts/stress-atomic-write.mjs +0 -1028
  544. package/scripts/test-fault-inject.mjs +0 -164
  545. package/scripts/test-large-file.mjs +0 -174
  546. package/scripts/tool-edge-smoke.mjs +0 -209
  547. package/scripts/uninstall.mjs +0 -201
  548. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  549. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  550. package/server-main.mjs +0 -3109
  551. package/server.mjs +0 -468
  552. package/setup/config-merge.mjs +0 -246
  553. package/setup/install.mjs +0 -574
  554. package/setup/launch-core.mjs +0 -617
  555. package/setup/launch.mjs +0 -101
  556. package/setup/locate-claude.mjs +0 -56
  557. package/setup/mixdog-cli.mjs +0 -122
  558. package/setup/setup-server.mjs +0 -3305
  559. package/setup/setup.html +0 -3740
  560. package/setup/tui.mjs +0 -325
  561. package/skills/retro-skill-proposer/SKILL.md +0 -92
  562. package/skills/schedule-add/SKILL.md +0 -77
  563. package/skills/setup/SKILL.md +0 -356
  564. package/skills/webhook-add/SKILL.md +0 -81
  565. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  566. package/src/agent/index.mjs +0 -2138
  567. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  568. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  569. package/src/agent/orchestrator/bridge-trace.mjs +0 -583
  570. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  571. package/src/agent/orchestrator/config.mjs +0 -405
  572. package/src/agent/orchestrator/context/collect.mjs +0 -651
  573. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  574. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  575. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  576. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  577. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  578. package/src/agent/orchestrator/jobs.mjs +0 -116
  579. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  580. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1881
  581. package/src/agent/orchestrator/providers/anthropic.mjs +0 -594
  582. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  583. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  584. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  585. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  586. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  587. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  588. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  589. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  590. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  591. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  592. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  593. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  594. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  595. package/src/agent/orchestrator/session/loop.mjs +0 -1478
  596. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  597. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  598. package/src/agent/orchestrator/session/store.mjs +0 -632
  599. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  600. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  601. package/src/agent/orchestrator/session/trim.mjs +0 -491
  602. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  603. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  604. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  605. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  606. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  607. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  608. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  609. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  610. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  611. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  612. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -455
  613. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  614. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  615. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  616. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  617. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  618. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  619. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  620. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  621. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  622. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  623. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  624. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  625. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  626. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  627. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  628. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  629. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  630. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  631. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  632. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  633. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  634. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  635. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  636. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  637. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  638. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  639. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  640. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  641. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  642. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  643. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  644. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  645. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  646. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  647. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  648. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  649. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  650. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  651. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  652. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  653. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  654. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  655. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  656. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  657. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  658. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  659. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  660. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  661. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  662. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  663. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  664. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  665. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  666. package/src/agent/tool-defs.mjs +0 -103
  667. package/src/channels/backends/discord.mjs +0 -784
  668. package/src/channels/data/voice-runtime-manifest.json +0 -138
  669. package/src/channels/index.mjs +0 -3268
  670. package/src/channels/lib/config.mjs +0 -292
  671. package/src/channels/lib/drop-trace.mjs +0 -71
  672. package/src/channels/lib/event-pipeline.mjs +0 -81
  673. package/src/channels/lib/holidays.mjs +0 -138
  674. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  675. package/src/channels/lib/output-forwarder.mjs +0 -765
  676. package/src/channels/lib/runtime-paths.mjs +0 -517
  677. package/src/channels/lib/scheduler.mjs +0 -723
  678. package/src/channels/lib/session-discovery.mjs +0 -103
  679. package/src/channels/lib/state-file.mjs +0 -68
  680. package/src/channels/lib/status-snapshot.mjs +0 -219
  681. package/src/channels/lib/tool-format.mjs +0 -140
  682. package/src/channels/lib/transcript-discovery.mjs +0 -195
  683. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  684. package/src/channels/lib/webhook.mjs +0 -1318
  685. package/src/channels/tool-defs.mjs +0 -170
  686. package/src/daemon/host.mjs +0 -118
  687. package/src/daemon/mcp-transport.mjs +0 -47
  688. package/src/daemon/session.mjs +0 -100
  689. package/src/daemon/thin-client.mjs +0 -71
  690. package/src/daemon/transport.mjs +0 -163
  691. package/src/memory/data/runtime-manifest.json +0 -40
  692. package/src/memory/index.mjs +0 -3332
  693. package/src/memory/lib/core-memory-store.mjs +0 -330
  694. package/src/memory/lib/embedding-provider.mjs +0 -269
  695. package/src/memory/lib/embedding-worker.mjs +0 -323
  696. package/src/memory/lib/memory-cycle1.mjs +0 -645
  697. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  698. package/src/memory/lib/memory-cycle3.mjs +0 -540
  699. package/src/memory/lib/memory-embed.mjs +0 -299
  700. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  701. package/src/memory/lib/memory-recall-store.mjs +0 -638
  702. package/src/memory/lib/memory.mjs +0 -412
  703. package/src/memory/lib/pg/adapter.mjs +0 -308
  704. package/src/memory/lib/pg/process.mjs +0 -360
  705. package/src/memory/lib/pg/supervisor.mjs +0 -396
  706. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  707. package/src/memory/lib/trace-store.mjs +0 -728
  708. package/src/memory/tool-defs.mjs +0 -79
  709. package/src/search/index.mjs +0 -1173
  710. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  711. package/src/search/lib/backends/exa.mjs +0 -50
  712. package/src/search/lib/backends/firecrawl.mjs +0 -61
  713. package/src/search/lib/backends/gemini-api.mjs +0 -83
  714. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  715. package/src/search/lib/backends/index.mjs +0 -150
  716. package/src/search/lib/backends/openai-api.mjs +0 -144
  717. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  718. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  719. package/src/search/lib/backends/tavily.mjs +0 -55
  720. package/src/search/lib/backends/xai-api.mjs +0 -113
  721. package/src/search/lib/config.mjs +0 -192
  722. package/src/search/lib/provider-usage.mjs +0 -67
  723. package/src/search/lib/providers.mjs +0 -47
  724. package/src/search/lib/search-intent.mjs +0 -109
  725. package/src/search/lib/setup-handler.mjs +0 -261
  726. package/src/search/lib/web-tools.mjs +0 -1219
  727. package/src/search/tool-defs.mjs +0 -83
  728. package/src/setup/defender-exclusion.mjs +0 -183
  729. package/src/shared/atomic-file.mjs +0 -436
  730. package/src/shared/config.mjs +0 -372
  731. package/src/shared/daemon-recycle.mjs +0 -108
  732. package/src/shared/disable-claude-builtins.mjs +0 -91
  733. package/src/shared/err-text.mjs +0 -12
  734. package/src/shared/llm/cost.mjs +0 -66
  735. package/src/shared/llm/http-agent.mjs +0 -123
  736. package/src/shared/open-url.mjs +0 -62
  737. package/src/shared/plugin-paths.mjs +0 -58
  738. package/src/shared/schedules-store.mjs +0 -70
  739. package/src/shared/seed.mjs +0 -136
  740. package/src/shared/user-cwd.mjs +0 -225
  741. package/src/shared/user-data-guard.mjs +0 -244
  742. package/src/status/aggregator.mjs +0 -584
  743. package/src/status/server.mjs +0 -413
  744. package/tools.json +0 -1653
  745. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  746. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  747. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  748. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  749. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  750. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  751. /package/{lib → src/lib}/text-utils.cjs +0 -0
  752. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  753. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  754. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  755. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  756. /package/src/{agent → runtime/agent}/orchestrator/session/cache/post-edit-marks.mjs +0 -0
  757. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  758. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  759. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  760. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  761. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  762. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  763. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/advisory-lock.mjs +0 -0
  764. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  805. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  806. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  807. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  808. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  809. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  810. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  811. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  812. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  813. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  814. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  815. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  816. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  817. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  818. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  819. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  820. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  821. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  822. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  823. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  824. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  829. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  830. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  831. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  832. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  833. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  834. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  835. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  836. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -0,0 +1,1953 @@
1
+ /**
2
+ * OpenAI ChatGPT OAuth (Codex) provider.
3
+ *
4
+ * Dispatches over the WebSocket upgrade of chatgpt.com/backend-api/codex/
5
+ * responses (responses_websockets=2026-02-06 beta). Authenticates via PKCE
6
+ * OAuth or reuses ~/.codex/auth.json. Streaming/framing lives in
7
+ * openai-oauth-ws.mjs; this file owns auth, model catalog, request-body
8
+ * shape, and HTTP/SSE fallback when WebSocket transport is unhealthy.
9
+ */
10
+ import { createServer } from 'http';
11
+ import { randomBytes, createHash } from 'crypto';
12
+ import { readFileSync, existsSync, mkdirSync, statSync, unlinkSync } from 'fs';
13
+ import { join } from 'path';
14
+ import { homedir } from 'os';
15
+ import { getPluginData } from '../config.mjs';
16
+ import { enrichModels } from './model-catalog.mjs';
17
+ import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
18
+ import { makeModelCache } from './model-cache.mjs';
19
+
20
+ import { sendViaWebSocket } from './openai-oauth-ws.mjs';
21
+ import {
22
+ buildStableProviderPromptCacheKey,
23
+ resolveProviderPromptCacheLane,
24
+ resolveProviderCacheKey,
25
+ } from '../smart-bridge/cache-strategy.mjs';
26
+ import {
27
+ appendBridgeTrace,
28
+ traceBridgeFetch,
29
+ traceBridgeSse,
30
+ traceBridgeUsage,
31
+ } from '../bridge-trace.mjs';
32
+ import {
33
+ PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
34
+ PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
35
+ createTimeoutSignal,
36
+ } from '../stall-policy.mjs';
37
+ import { populateHttpStatusFromMessage } from './retry-classifier.mjs';
38
+ import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
39
+ import {
40
+ contentHasImage,
41
+ normalizeContentForOpenAIResponses,
42
+ splitToolContentForOpenAIResponses,
43
+ } from './media-normalization.mjs';
44
+ // --- Constants ---
45
+ const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
46
+ const CODEX_OAUTH_ORIGINATOR = 'codex_cli_rs';
47
+ const TOKEN_URL = 'https://auth.openai.com/oauth/token';
48
+ const CODEX_RESPONSES_URL = 'https://chatgpt.com/backend-api/codex/responses';
49
+ const OPENAI_CODEX_REMOTE_COMPACT_FALLBACK = 'openai-codex';
50
+ const OPENAI_CODEX_REMOTE_COMPACT_VERSION = 1;
51
+ const REMOTE_COMPACT_RETAINED_TOKEN_BUDGET = 64_000;
52
+ // Version string baked into the models endpoint query — Codex rejects the
53
+ // request without it. Keep close to the latest published Codex CLI because
54
+ // older versions trigger a visibility-filtered catalog (e.g. only rollout
55
+ // models). Bump when the real CLI bumps.
56
+ // Codex backend gates new model exposures (e.g. gpt-5.5 only on >= 0.130.0)
57
+ // on the client_version header. Resolve dynamically from npm so newly-shipped
58
+ // models surface within a day instead of waiting on a hardcoded bump here.
59
+ // Cached 24h in-process; npm failure falls back to the floor below.
60
+ const CODEX_CLIENT_VERSION_FLOOR = '0.130.0';
61
+ const CODEX_VERSION_CACHE_TTL_MS = 24 * 60 * 60_000;
62
+ let _codexVersionCache = { value: null, fetchedAt: 0 };
63
+
64
+ async function _resolveCodexClientVersion() {
65
+ const now = Date.now();
66
+ if (_codexVersionCache.value && now - _codexVersionCache.fetchedAt < CODEX_VERSION_CACHE_TTL_MS) {
67
+ return _codexVersionCache.value;
68
+ }
69
+ try {
70
+ const res = await fetch('https://registry.npmjs.org/@openai/codex/latest', {
71
+ signal: AbortSignal.timeout(5_000),
72
+ });
73
+ if (res.ok) {
74
+ const j = await res.json();
75
+ const v = String(j?.version || '').trim();
76
+ if (/^\d+\.\d+\.\d+/.test(v)) {
77
+ _codexVersionCache = { value: v, fetchedAt: now };
78
+ return v;
79
+ }
80
+ }
81
+ } catch { /* network down / npm rejects — use floor */ }
82
+ _codexVersionCache = { value: CODEX_CLIENT_VERSION_FLOOR, fetchedAt: now };
83
+ return CODEX_CLIENT_VERSION_FLOOR;
84
+ }
85
+ const CODEX_MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
86
+ const CODEX_MODEL_CACHE_SCHEMA_VERSION = 2;
87
+ const TOKEN_REFRESH_SKEW_MS = 5 * 60_000;
88
+
89
+ const _codexModelCache = makeModelCache({
90
+ fileName: 'openai-oauth-models.json',
91
+ ttlMs: CODEX_MODEL_CACHE_TTL_MS,
92
+ version: CODEX_MODEL_CACHE_SCHEMA_VERSION,
93
+ onSave: (m) => { _inMemoryCodexCatalog = Array.isArray(m) ? m.slice() : null; },
94
+ });
95
+
96
+ function _loadCodexModelCacheSync() {
97
+ return _codexModelCache.loadSync();
98
+ }
99
+
100
+ async function _loadCodexModelCache() {
101
+ return _codexModelCache.loadSync();
102
+ }
103
+
104
+ async function _saveCodexModelCache(models) {
105
+ _codexModelCache.save(models);
106
+ }
107
+
108
+ // In-memory mirror of the on-disk catalog, same pattern as anthropic-oauth.
109
+ // Populated on first listModels() and after every _saveCodexModelCache.
110
+ let _inMemoryCodexCatalog = null;
111
+ let _codexRefreshInFlight = null;
112
+ let _oauthRefreshInFlight = null;
113
+ let _lastCodexListModelsError = '';
114
+
115
+ export function getOpenAIOAuthModelCatalogError() {
116
+ return _lastCodexListModelsError;
117
+ }
118
+
119
+ function _codexCatalogHas(id) {
120
+ if (!id || !Array.isArray(_inMemoryCodexCatalog)) return false;
121
+ return _inMemoryCodexCatalog.some(m => m.id === id);
122
+ }
123
+
124
+ function _findCachedCodexModel(id) {
125
+ if (!id) return null;
126
+ if (!Array.isArray(_inMemoryCodexCatalog)) {
127
+ _inMemoryCodexCatalog = _loadCodexModelCacheSync();
128
+ }
129
+ if (!Array.isArray(_inMemoryCodexCatalog)) return null;
130
+ return _inMemoryCodexCatalog.find(m => m?.id === id) || null;
131
+ }
132
+
133
+ function _codexServiceTiers(modelInfo) {
134
+ return Array.isArray(modelInfo?.serviceTiers) ? modelInfo.serviceTiers : [];
135
+ }
136
+
137
+ function _codexModelBlocksServiceTier(id, serviceTier) {
138
+ if (serviceTier !== 'priority') return false;
139
+ const family = _codexFamily(id);
140
+ return family === 'gpt-mini' || family === 'gpt-nano' || family === 'gpt-codex';
141
+ }
142
+
143
+ export function codexModelSupportsServiceTier(id, serviceTier) {
144
+ if (_codexModelBlocksServiceTier(id, serviceTier)) return false;
145
+ const info = _findCachedCodexModel(id);
146
+ if (!info) return true;
147
+ const tiers = _codexServiceTiers(info);
148
+ if (!tiers.length) return false;
149
+ return tiers.some(t => t?.id === serviceTier);
150
+ }
151
+
152
+ // Codex returns dated ids (gpt-5.4-mini-2026-03-17). Strip the trailing
153
+ // -YYYY-MM-DD to get the version alias (gpt-5.4-mini). Unknown shapes pass
154
+ // through unchanged.
155
+ function _displayCodexModel(id) {
156
+ if (!id || typeof id !== 'string') return id;
157
+ return id.replace(/-\d{4}-\d{2}-\d{2}$/, '');
158
+ }
159
+
160
+ function _normalizeCodexModel(m) {
161
+ const id = m?.slug || m?.id;
162
+ const family = _codexFamily(id);
163
+ const serviceTiers = Array.isArray(m?.service_tiers)
164
+ ? m.service_tiers
165
+ .map(t => ({
166
+ id: String(t?.id || '').trim(),
167
+ name: String(t?.name || '').trim(),
168
+ description: String(t?.description || '').trim(),
169
+ }))
170
+ .filter(t => t.id)
171
+ : [];
172
+ const additionalSpeedTiers = Array.isArray(m?.additional_speed_tiers)
173
+ ? m.additional_speed_tiers.map(t => String(t || '').trim()).filter(Boolean)
174
+ : [];
175
+ // Codex doesn't use dated ids — everything is effectively a version alias.
176
+ return {
177
+ id,
178
+ name: m?.display_name || id,
179
+ display: m?.display_name || id,
180
+ family,
181
+ provider: 'openai-oauth',
182
+ contextWindow: m?.context_window || m?.max_context_window || 1000000,
183
+ maxContextWindow: m?.max_context_window || null,
184
+ outputTokens: m?.max_output_tokens || m?.output_tokens || 32768,
185
+ autoCompactTokenLimit: m?.auto_compact_token_limit || null,
186
+ effectiveContextWindowPercent: m?.effective_context_window_percent || null,
187
+ tier: 'version',
188
+ latest: false,
189
+ description: m?.description || '',
190
+ reasoningLevels: (m?.supported_reasoning_levels || []).map(r => r.effort),
191
+ serviceTiers,
192
+ defaultServiceTier: m?.default_service_tier || null,
193
+ additionalSpeedTiers,
194
+ };
195
+ }
196
+
197
+ function _codexFamily(id) {
198
+ const s = String(id || '').toLowerCase();
199
+ if (s.includes('nano')) return 'gpt-nano';
200
+ if (s.includes('mini')) return 'gpt-mini';
201
+ if (s.includes('codex')) return 'gpt-codex';
202
+ if (s.startsWith('gpt-5.5')) return 'gpt-5.5';
203
+ if (s.startsWith('gpt-5.4')) return 'gpt-5.4';
204
+ if (s.startsWith('gpt-5.2')) return 'gpt-5.2';
205
+ if (s.startsWith('gpt-5')) return 'gpt-5';
206
+ return 'gpt';
207
+ }
208
+
209
+ // Compare two Codex ids by the X.Y version embedded in `gpt-X.Y`. Mirrors
210
+ // anthropic-oauth's _compareVersion, but Codex ids have no trailing date so
211
+ // the version lives in the dotted number, not a -YYYY-MM-DD suffix.
212
+ function _compareVersion(a, b) {
213
+ const na = (String(a).match(/gpt-(\d+)\.(\d+)/) || []).slice(1).map(Number);
214
+ const nb = (String(b).match(/gpt-(\d+)\.(\d+)/) || []).slice(1).map(Number);
215
+ for (let i = 0; i < Math.max(na.length, nb.length); i++) {
216
+ if ((na[i] || 0) !== (nb[i] || 0)) return (na[i] || 0) - (nb[i] || 0);
217
+ }
218
+ return String(a).localeCompare(String(b));
219
+ }
220
+
221
+ // Main gpt-5 chat family only: exclude the mini/nano/codex variants so "latest"
222
+ // resolves to the flagship, not a smaller sibling.
223
+ function _isMainCodexFamily(family) {
224
+ return typeof family === 'string' && family.startsWith('gpt-5');
225
+ }
226
+
227
+ // Mark the highest-version model per family as `latest: true`. VERSION-based
228
+ // (Codex ids carry no `created`), mirroring anthropic-oauth's per-family pass.
229
+ function _markLatestCodex(models) {
230
+ const byFamily = new Map();
231
+ for (const m of models) {
232
+ if (!m?.id) continue;
233
+ const cur = byFamily.get(m.family);
234
+ if (!cur || _compareVersion(m.id, cur.id) > 0) {
235
+ byFamily.set(m.family, m);
236
+ }
237
+ }
238
+ for (const m of byFamily.values()) m.latest = true;
239
+ }
240
+
241
+ // Newest MAIN gpt-5 chat model by version, read from the SYNC in-memory
242
+ // catalog mirror. Returns null until populated; callers warm via
243
+ // ensureLatestCodexModel when null.
244
+ export function resolveLatestCodexModel() {
245
+ if (!Array.isArray(_inMemoryCodexCatalog)) return null;
246
+ let best = null;
247
+ for (const m of _inMemoryCodexCatalog) {
248
+ if (!m?.id || !_isMainCodexFamily(m.family)) continue;
249
+ if (!best || _compareVersion(m.id, best.id) > 0) best = m;
250
+ }
251
+ return best?.id || null;
252
+ }
253
+
254
+ export async function ensureLatestCodexModel(provider) {
255
+ let m = resolveLatestCodexModel();
256
+ if (m) return m;
257
+ await provider._refreshModelCache();
258
+ m = resolveLatestCodexModel();
259
+ if (m) return m;
260
+ throw new Error('[openai-oauth] model catalog unavailable after warmup — cannot resolve default model');
261
+ }
262
+
263
+ function getOwnTokenPath() {
264
+ const dir = getPluginData();
265
+ if (!existsSync(dir))
266
+ mkdirSync(dir, { recursive: true });
267
+ return join(dir, 'openai-oauth.json');
268
+ }
269
+
270
+ // Public predicate used by config.buildDefaultConfig — provider is enabled
271
+ // when own tokens exist OR codex bootstrap auth is present. Single truth:
272
+ // same loader the runtime uses (loadTokens), no parallel hard-coded path probe.
273
+ export function hasOpenAIOAuthCredentials() {
274
+ try {
275
+ const tokens = loadTokens();
276
+ return !!(tokens?.access_token && tokens?.refresh_token);
277
+ } catch { return false; }
278
+ }
279
+
280
+ export function describeOpenAIOAuthCredentials() {
281
+ try {
282
+ const tokens = loadTokens();
283
+ if (!tokens?.access_token) {
284
+ return { authenticated: false, status: 'Not Set', detail: '~/.codex/auth.json or mixdog token store' };
285
+ }
286
+ const hasRefresh = Boolean(tokens.refresh_token);
287
+ const expiresAt = _normalizeExpiresAt(tokens.expires_at ?? tokens.expiresAt);
288
+ const expiring = expiresAt > 0 && expiresAt < Date.now() + TOKEN_REFRESH_SKEW_MS;
289
+ const expired = expiresAt > 0 && expiresAt <= Date.now();
290
+ const source = tokens.source || 'oauth';
291
+ if (!hasRefresh) {
292
+ return {
293
+ authenticated: expiresAt === 0 || !expired,
294
+ status: expired ? 'Reauth Required' : 'Access Only',
295
+ detail: `${source}; no refresh token`,
296
+ expiresAt,
297
+ };
298
+ }
299
+ if (expired) return { authenticated: true, status: 'Refresh Required', detail: source, expiresAt };
300
+ if (expiring) return { authenticated: true, status: 'Refresh Soon', detail: source, expiresAt };
301
+ return { authenticated: true, status: 'Valid', detail: source, expiresAt };
302
+ } catch (err) {
303
+ return { authenticated: false, status: 'Error', detail: String(err?.message || err).slice(0, 200) };
304
+ }
305
+ }
306
+ function _normalizeExpiresAt(value) {
307
+ const n = Number(value || 0);
308
+ if (!Number.isFinite(n) || n <= 0) return 0;
309
+ return n < 1e12 ? n * 1000 : n;
310
+ }
311
+ function _tokensMaxMtime() {
312
+ let max = 0;
313
+ const paths = [getOwnTokenPath(), join(homedir(), '.codex', 'auth.json')];
314
+ for (const p of paths) {
315
+ try {
316
+ const s = statSync(p);
317
+ if (s.mtimeMs > max) max = s.mtimeMs;
318
+ } catch { /* not present — skip */ }
319
+ }
320
+ return max;
321
+ }
322
+
323
+ function _codexCliAuthPath() {
324
+ return join(homedir(), '.codex', 'auth.json');
325
+ }
326
+ function _loadOwnCodexTokens() {
327
+ const ownPath = getOwnTokenPath();
328
+ if (!existsSync(ownPath)) return null;
329
+ try {
330
+ const stat = statSync(ownPath);
331
+ const own = JSON.parse(readFileSync(ownPath, 'utf-8'));
332
+ if (own.access_token && own.refresh_token) {
333
+ return {
334
+ ...own,
335
+ expires_at: _normalizeExpiresAt(own.expires_at ?? own.expiresAt) || _expiryFromAccessToken(own.access_token),
336
+ account_id: own.account_id || extractAccountId(own.access_token),
337
+ source: 'mixdog',
338
+ _mtimeMs: stat.mtimeMs,
339
+ };
340
+ }
341
+ }
342
+ catch { /* fall through */ }
343
+ return null;
344
+ }
345
+ function _loadCodexCliTokens() {
346
+ const codexPath = _codexCliAuthPath();
347
+ if (!existsSync(codexPath)) return null;
348
+ try {
349
+ const stat = statSync(codexPath);
350
+ const data = JSON.parse(readFileSync(codexPath, 'utf-8'));
351
+ const tokens = data.tokens || data;
352
+ if (tokens.access_token && tokens.refresh_token) {
353
+ const expiresAt = _normalizeExpiresAt(data.expires_at ?? tokens.expires_at ?? data.expiresAt ?? tokens.expiresAt) || _expiryFromAccessToken(tokens.access_token);
354
+ return {
355
+ access_token: tokens.access_token,
356
+ refresh_token: tokens.refresh_token,
357
+ expires_at: expiresAt,
358
+ account_id: tokens.account_id || extractAccountId(tokens.access_token),
359
+ source: 'codex-cli',
360
+ _mtimeMs: stat.mtimeMs,
361
+ };
362
+ }
363
+ }
364
+ catch { /* fall through */ }
365
+ return null;
366
+ }
367
+ // Own store is authoritative (accurate expires_at from refresh); the Codex CLI
368
+ // store seeds the initial bootstrap. But the refresh-token lineage is shared
369
+ // single-use with the Codex CLI, so when the CLI store is STRICTLY newer on
370
+ // disk (an independent `codex login`/CLI refresh) we must adopt it instead of
371
+ // replaying our consumed token. Freshest-wins, own preferred on a tie.
372
+ function loadTokens() {
373
+ const own = _loadOwnCodexTokens();
374
+ const cli = _loadCodexCliTokens();
375
+ if (own && cli) return (cli._mtimeMs > own._mtimeMs) ? cli : own;
376
+ return own || cli;
377
+ }
378
+ function saveTokens(tokens) {
379
+ const target = getOwnTokenPath();
380
+ writeJsonAtomicSync(target, tokens, { lock: true, fsyncDir: true, mode: 0o600, secret: true });
381
+ }
382
+
383
+ export function forgetOpenAIOAuthCredentials() {
384
+ let removed = false;
385
+ const ownPath = getOwnTokenPath();
386
+ if (existsSync(ownPath)) {
387
+ unlinkSync(ownPath);
388
+ removed = true;
389
+ }
390
+ const codexPath = _codexCliAuthPath();
391
+ if (existsSync(codexPath)) {
392
+ try {
393
+ const raw = JSON.parse(readFileSync(codexPath, 'utf-8'));
394
+ if (raw?.tokens && typeof raw.tokens === 'object') {
395
+ delete raw.tokens.access_token;
396
+ delete raw.tokens.refresh_token;
397
+ delete raw.tokens.id_token;
398
+ raw.last_refresh = new Date().toISOString();
399
+ writeJsonAtomicSync(codexPath, raw, { lock: true, fsyncDir: true });
400
+ removed = true;
401
+ } else if (raw?.access_token || raw?.refresh_token) {
402
+ delete raw.access_token;
403
+ delete raw.refresh_token;
404
+ delete raw.id_token;
405
+ raw.last_refresh = new Date().toISOString();
406
+ writeJsonAtomicSync(codexPath, raw, { lock: true, fsyncDir: true });
407
+ removed = true;
408
+ }
409
+ } catch (err) {
410
+ throw new Error(`OpenAI OAuth reset failed for ${codexPath}: ${err?.message || err}`);
411
+ }
412
+ }
413
+ return { removed };
414
+ }
415
+ // Write rotated tokens back to the Codex CLI store (~/.codex/auth.json) so the
416
+ // Codex CLI picks up the rotation instead of replaying a consumed refresh_token
417
+ // from the shared single-use lineage. Mirrors anthropic-oauth's write-back.
418
+ // Best-effort; the own store stays authoritative. Host-owned file: preserve all
419
+ // other fields and don't re-permission it (no secret/mode).
420
+ function _writeBackCodexCliTokens(tokens) {
421
+ const path = _codexCliAuthPath();
422
+ if (!existsSync(path)) return;
423
+ try {
424
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
425
+ if (!raw || typeof raw !== 'object') return;
426
+ const slot = (raw.tokens && typeof raw.tokens === 'object') ? raw.tokens : raw;
427
+ slot.access_token = tokens.access_token;
428
+ slot.refresh_token = tokens.refresh_token;
429
+ raw.last_refresh = new Date().toISOString();
430
+ // Preserve the Codex CLI file's existing POSIX mode (writeJsonAtomicSync
431
+ // otherwise defaults to 0o600, re-permissioning a host-owned file).
432
+ let mode;
433
+ try { mode = statSync(path).mode & 0o777; } catch { /* keep helper default */ }
434
+ writeJsonAtomicSync(path, raw, { lock: true, fsyncDir: true, mode });
435
+ } catch (err) {
436
+ process.stderr.write(`[openai-oauth] Codex CLI store write-back failed: ${String(err?.message || err).slice(0, 200)}\n`);
437
+ }
438
+ }
439
+ function extractAccountId(token) {
440
+ try {
441
+ const parts = token.split('.');
442
+ if (parts.length !== 3)
443
+ return undefined;
444
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));
445
+ return payload?.['https://api.openai.com/auth']?.chatgpt_account_id;
446
+ }
447
+ catch {
448
+ return undefined;
449
+ }
450
+ }
451
+ // Derive token expiry from the access_token's JWT `exp` claim (epoch ms), as a
452
+ // fallback when the source store carries no explicit expires_at — e.g. the Codex
453
+ // CLI's ~/.codex/auth.json records only last_refresh, so expires_at resolves to 0
454
+ // and ensureAuth reads that as "never expires", disabling proactive refresh; the
455
+ // token then only refreshes reactively after a request fails (and a WS handshake
456
+ // 401 can surface as an opaque transport error that the 401 path misses). Returns
457
+ // 0 for opaque (non-JWT) tokens. JWT `exp` is epoch SECONDS (RFC 7519).
458
+ function _expiryFromAccessToken(token) {
459
+ try {
460
+ const parts = String(token || '').split('.');
461
+ if (parts.length !== 3) return 0;
462
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));
463
+ const exp = Number(payload?.exp);
464
+ return Number.isFinite(exp) && exp > 0 ? exp * 1000 : 0;
465
+ }
466
+ catch { return 0; }
467
+ }
468
+ // --- Token refresh ---
469
+ async function refreshTokens(refreshToken) {
470
+ const controller = new AbortController();
471
+ const timeout = setTimeout(() => controller.abort(), 30_000);
472
+ try {
473
+ const res = await fetch(TOKEN_URL, {
474
+ method: 'POST',
475
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
476
+ body: new URLSearchParams({
477
+ grant_type: 'refresh_token',
478
+ refresh_token: refreshToken,
479
+ client_id: CLIENT_ID,
480
+ }),
481
+ // Never follow a redirect on a secret-bearing request: a token
482
+ // endpoint that 307/308-redirects would replay the refresh_token to
483
+ // the redirect target. Fail loud instead.
484
+ redirect: 'error',
485
+ signal: controller.signal,
486
+ dispatcher: getLlmDispatcher(),
487
+ });
488
+ if (!res.ok) {
489
+ const text = await res.text().catch(() => '');
490
+ // Distinguish a terminally-dead refresh token (consumed by the Codex
491
+ // CLI's single-use lineage) from transient failures, so the caller can
492
+ // re-read disk and retry once with a newer token instead of
493
+ // collapsing every failure to a generic null.
494
+ if (res.status === 400 || res.status === 401 || /invalid_grant|revoked|reused/i.test(text)) {
495
+ throw Object.assign(new Error(`OpenAI OAuth token refresh ${res.status} (invalid_grant)`), { isInvalidGrant: true });
496
+ }
497
+ return null;
498
+ }
499
+ const json = await res.json();
500
+ if (!json.access_token) return null;
501
+ const expiresAt = _normalizeExpiresAt(json.expires_at ?? json.expiresAt)
502
+ || (typeof json.expires_in === 'number' ? Date.now() + json.expires_in * 1000 : 0);
503
+ const tokens = {
504
+ access_token: json.access_token,
505
+ refresh_token: json.refresh_token || refreshToken,
506
+ expires_at: expiresAt,
507
+ account_id: extractAccountId(json.access_token),
508
+ };
509
+ // CLI store first, own store last: the own store keeps the newest mtime
510
+ // (and its accurate refresh expires_at), so freshest-wins loadTokens
511
+ // treats our refresh as authoritative while the CLI still picks up the
512
+ // rotated token.
513
+ _writeBackCodexCliTokens(tokens);
514
+ saveTokens(tokens);
515
+ return tokens;
516
+ } catch (err) {
517
+ if (err?.name === 'AbortError')
518
+ throw new Error('OpenAI OAuth token refresh timed out after 30000ms');
519
+ throw err;
520
+ } finally {
521
+ clearTimeout(timeout);
522
+ }
523
+ }
524
+ // --- Build Responses API request ---
525
+ function _cloneJson(value) {
526
+ try { return JSON.parse(JSON.stringify(value)); } catch { return value; }
527
+ }
528
+
529
+ function _nativeCompactState(providerState, model) {
530
+ const state = providerState?.openaiCodex?.remoteCompact;
531
+ if (!state || state.version !== OPENAI_CODEX_REMOTE_COMPACT_VERSION) return null;
532
+ if (!Array.isArray(state.nativePrefix) || state.nativePrefix.length === 0) return null;
533
+ if (state.model && model && state.model !== model) return null;
534
+ return state;
535
+ }
536
+
537
+ function _isRemoteCompactFallbackMessage(m) {
538
+ return m?._mixdogRemoteCompactFallback === OPENAI_CODEX_REMOTE_COMPACT_FALLBACK;
539
+ }
540
+
541
+ function _contentTextParts(content, type = 'input_text') {
542
+ if (typeof content === 'string') return content ? [{ type, text: content }] : [];
543
+ if (!Array.isArray(content)) {
544
+ const text = content == null ? '' : JSON.stringify(content);
545
+ return text ? [{ type, text }] : [];
546
+ }
547
+ const out = [];
548
+ for (const item of content) {
549
+ if (!item || typeof item !== 'object') continue;
550
+ if (typeof item.text === 'string') {
551
+ out.push({ type: item.type === 'output_text' ? 'output_text' : type, text: item.text });
552
+ } else if (typeof item.content === 'string') {
553
+ out.push({ type, text: item.content });
554
+ }
555
+ }
556
+ return out;
557
+ }
558
+
559
+ function _messageToNativeRetainedItem(m) {
560
+ const role = m?.role;
561
+ if (role !== 'user') return null;
562
+ if (_isRemoteCompactFallbackMessage(m)) return null;
563
+ const content = _contentTextParts(m.content, 'input_text');
564
+ if (!content.length) return null;
565
+ return { type: 'message', role, content };
566
+ }
567
+
568
+ function _nativeMessageTextTokenCount(item) {
569
+ if (!item || item.type !== 'message') return 0;
570
+ const content = Array.isArray(item.content) ? item.content : [];
571
+ let chars = 0;
572
+ for (const part of content) {
573
+ if (typeof part?.text === 'string') chars += part.text.length;
574
+ }
575
+ return Math.max(1, Math.ceil(chars / 4));
576
+ }
577
+
578
+ function _truncateTextForTokens(text, maxTokens) {
579
+ const value = String(text || '');
580
+ const maxChars = Math.max(0, Math.floor(maxTokens * 4));
581
+ if (value.length <= maxChars) return value;
582
+ if (maxChars <= 32) return value.slice(0, maxChars);
583
+ const marker = `...${Math.max(1, Math.ceil((value.length - maxChars) / 4))} tokens truncated...`;
584
+ const room = Math.max(0, maxChars - marker.length);
585
+ const head = Math.ceil(room / 2);
586
+ const tail = Math.floor(room / 2);
587
+ return `${value.slice(0, head)}${marker}${value.slice(value.length - tail)}`;
588
+ }
589
+
590
+ function _truncateNativeMessageToTokenBudget(item, maxTokens) {
591
+ if (!item || item.type !== 'message') return item;
592
+ const clone = _cloneJson(item);
593
+ let remaining = Math.max(0, Math.floor(maxTokens));
594
+ const content = [];
595
+ for (const part of Array.isArray(clone.content) ? clone.content : []) {
596
+ if (typeof part?.text !== 'string') {
597
+ content.push(part);
598
+ continue;
599
+ }
600
+ if (remaining <= 0) continue;
601
+ const tokenCount = Math.max(1, Math.ceil(part.text.length / 4));
602
+ if (tokenCount <= remaining) {
603
+ content.push(part);
604
+ remaining -= tokenCount;
605
+ } else {
606
+ const truncated = _truncateTextForTokens(part.text, remaining);
607
+ if (truncated) content.push({ ...part, text: truncated });
608
+ remaining = 0;
609
+ }
610
+ }
611
+ if (!content.length) return null;
612
+ clone.content = content;
613
+ return clone;
614
+ }
615
+
616
+ function _truncateRetainedNativeMessages(items, maxTokens = REMOTE_COMPACT_RETAINED_TOKEN_BUDGET) {
617
+ let remaining = Math.max(0, Math.floor(maxTokens));
618
+ const reversed = [];
619
+ for (let i = items.length - 1; i >= 0; i -= 1) {
620
+ if (remaining <= 0) break;
621
+ const item = items[i];
622
+ const tokenCount = _nativeMessageTextTokenCount(item);
623
+ if (tokenCount <= remaining) {
624
+ reversed.push(item);
625
+ remaining -= tokenCount;
626
+ continue;
627
+ }
628
+ const truncated = _truncateNativeMessageToTokenBudget(item, remaining);
629
+ if (truncated) reversed.push(truncated);
630
+ remaining = 0;
631
+ }
632
+ return reversed.reverse();
633
+ }
634
+
635
+ function _buildOpenAICodexNativeCompactPrefix(messages, compactionItem) {
636
+ const retained = [];
637
+ for (const m of messages || []) {
638
+ const item = _messageToNativeRetainedItem(m);
639
+ if (item) retained.push(item);
640
+ }
641
+ const compact = _cloneJson(compactionItem);
642
+ return [..._truncateRetainedNativeMessages(retained), compact];
643
+ }
644
+
645
+ function _withOpenAICodexRemoteCompactState(providerState, { model, nativePrefix, responseId } = {}) {
646
+ return {
647
+ ...(providerState || {}),
648
+ openaiCodex: {
649
+ ...(providerState?.openaiCodex || {}),
650
+ remoteCompact: {
651
+ version: OPENAI_CODEX_REMOTE_COMPACT_VERSION,
652
+ model: model || null,
653
+ nativePrefix,
654
+ responseId: responseId || null,
655
+ installedAt: Date.now(),
656
+ },
657
+ },
658
+ };
659
+ }
660
+
661
+ /**
662
+ * Convert a message slice to Responses API input items.
663
+ */
664
+ function convertMessagesToResponsesInput(messages, opts = {}) {
665
+ const out = [];
666
+ const nativeCompact = _nativeCompactState(opts.providerState, opts.model);
667
+ if (nativeCompact) {
668
+ for (const item of nativeCompact.nativePrefix) out.push(_cloneJson(item));
669
+ }
670
+ const pendingToolMedia = [];
671
+ const flushToolMedia = () => {
672
+ if (!pendingToolMedia.length) return;
673
+ out.push({ role: 'user', content: pendingToolMedia.splice(0) });
674
+ };
675
+ for (const m of messages) {
676
+ if (!m || m.role === 'system') continue;
677
+ if (nativeCompact && _isRemoteCompactFallbackMessage(m)) continue;
678
+ if (m.role === 'tool') {
679
+ const { output, mediaContent } = splitToolContentForOpenAIResponses(m.content);
680
+ out.push({
681
+ type: 'function_call_output',
682
+ call_id: m.toolCallId || '',
683
+ output,
684
+ });
685
+ if (mediaContent) pendingToolMedia.push(...mediaContent);
686
+ continue;
687
+ }
688
+ flushToolMedia();
689
+ if (m.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length) {
690
+ // Reasoning replay deliberately omitted: Codex rejects an
691
+ // `rs_*` reasoning item with the same id across the same
692
+ // handshake session_id (in-memory conversation state lives
693
+ // for the WS_IDLE_MS window even after a socket close).
694
+ // Server-side state already preserves the prefix; sending
695
+ // reasoning in `input` triggers "Duplicate item".
696
+ if (m.content) out.push({ role: 'assistant', content: normalizeContentForOpenAIResponses(m.content, { role: 'assistant' }) });
697
+ for (const tc of m.toolCalls) {
698
+ out.push({
699
+ type: 'function_call',
700
+ call_id: tc.id,
701
+ name: tc.name,
702
+ arguments: JSON.stringify(tc.arguments),
703
+ });
704
+ }
705
+ continue;
706
+ }
707
+ out.push({
708
+ role: m.role === 'assistant' ? 'assistant' : 'user',
709
+ content: normalizeContentForOpenAIResponses(m.content, { role: m.role }),
710
+ });
711
+ }
712
+ flushToolMedia();
713
+ return out;
714
+ }
715
+
716
+ function messagesHaveImageContent(messages) {
717
+ return (messages || []).some((m) => contentHasImage(m?.content));
718
+ }
719
+
720
+ export function buildRequestBody(messages, model, tools, sendOpts) {
721
+ // Extract system/instructions
722
+ const systemMsgs = messages.filter(m => m.role === 'system');
723
+ const instructions = systemMsgs.map(m => m.content).join('\n\n') || 'You are a helpful assistant.';
724
+ const opts = sendOpts || {};
725
+ const input = convertMessagesToResponsesInput(messages, {
726
+ providerState: opts.providerState,
727
+ model,
728
+ });
729
+ // Match the body shape pi-mono and the official Codex CLI ship so the
730
+ // server-side auto-cache routes correctly. text.verbosity / include /
731
+ // tool_choice / parallel_tool_calls are all inert without side effects
732
+ // for most callers but their presence affects how Codex classifies the
733
+ // request (and therefore whether the prompt cache is consulted).
734
+ const body = {
735
+ model,
736
+ instructions,
737
+ input,
738
+ store: process.env.MIXDOG_OAI_STORE === 'true' ? true : false,
739
+ stream: true,
740
+ reasoning: { effort: opts.effort || 'medium' },
741
+ text: { verbosity: 'medium' },
742
+ include: ['reasoning.encrypted_content'],
743
+ tool_choice: opts.toolChoice || 'auto',
744
+ parallel_tool_calls: true,
745
+ };
746
+ const maxOutputTokens = Number(opts.maxOutputTokens ?? opts.outputTokens ?? opts.max_output_tokens);
747
+ if (_envFlag('MIXDOG_OPENAI_OAUTH_SEND_MAX_OUTPUT_TOKENS', false)
748
+ && Number.isFinite(maxOutputTokens)
749
+ && maxOutputTokens > 0) {
750
+ body.max_output_tokens = Math.floor(maxOutputTokens);
751
+ }
752
+ if (opts.fast === true) {
753
+ // 'priority' is the only fast-class value the Codex OAuth backend
754
+ // accepts on the wire: 'fast' is hard-rejected ("Unsupported
755
+ // service_tier: fast", probed 2026-06-11). Match official Codex:
756
+ // only send the request value when the model catalog advertises it.
757
+ if (codexModelSupportsServiceTier(model, 'priority')) {
758
+ body.service_tier = 'priority';
759
+ }
760
+ }
761
+ // Add tools
762
+ if (tools?.length) {
763
+ body.tools = tools.map(t => ({
764
+ type: 'function',
765
+ name: t.name,
766
+ description: t.description,
767
+ parameters: t.inputSchema,
768
+ }));
769
+ }
770
+ const promptCacheProvider = opts.promptCacheProvider || 'openai-oauth';
771
+ const promptCacheLane = opts.promptCacheLane || resolveProviderPromptCacheLane(promptCacheProvider, opts);
772
+ body.prompt_cache_key = buildStableProviderPromptCacheKey(promptCacheProvider, opts, {
773
+ model,
774
+ instructions,
775
+ tools: body.tools || [],
776
+ effort: body.reasoning?.effort,
777
+ fast: opts.fast === true,
778
+ serviceTier: body.service_tier || '',
779
+ toolChoice: body.tool_choice,
780
+ parallelToolCalls: body.parallel_tool_calls,
781
+ cacheLaneSlot: promptCacheLane.slot,
782
+ cacheLaneShards: promptCacheLane.shards,
783
+ });
784
+ // NOTE: prompt_cache_retention is a public OpenAI Responses API parameter,
785
+ // but the Codex OAuth endpoint still rejects it ("Unsupported parameter:
786
+ // prompt_cache_retention", re-probed 2026-06-22). Leave retention on the
787
+ // Codex server default; public OpenAI direct injects 24h separately.
788
+ return body;
789
+ }
790
+
791
+ function buildRemoteCompactionRequestBody(messages, model, tools, sendOpts) {
792
+ const body = buildRequestBody(messages, model, tools, {
793
+ ...(sendOpts || {}),
794
+ expectCompaction: true,
795
+ });
796
+ body.input = [
797
+ ...(Array.isArray(body.input) ? body.input : []),
798
+ { type: 'compaction_trigger' },
799
+ ];
800
+ return body;
801
+ }
802
+
803
+ function _envFlag(name, fallback = true) {
804
+ const raw = process.env[name];
805
+ if (raw == null || raw === '') return fallback;
806
+ return !['0', 'false', 'off', 'no'].includes(String(raw).toLowerCase());
807
+ }
808
+
809
+ function _parseJsonObject(value) {
810
+ try {
811
+ const parsed = JSON.parse(value || '{}');
812
+ return parsed && typeof parsed === 'object' ? parsed : {};
813
+ } catch {
814
+ return {};
815
+ }
816
+ }
817
+
818
+ function _extractCachedTokens(usage) {
819
+ const details = usage?.input_tokens_details || usage?.prompt_tokens_details || {};
820
+ return Number(details.cached_tokens ?? details.cached ?? usage?.cached_tokens ?? 0) || 0;
821
+ }
822
+
823
+ function _sseEventsFromBuffer(buffer) {
824
+ const frames = [];
825
+ let rest = buffer.replace(/\r\n/g, '\n');
826
+ let idx;
827
+ while ((idx = rest.indexOf('\n\n')) >= 0) {
828
+ frames.push(rest.slice(0, idx));
829
+ rest = rest.slice(idx + 2);
830
+ }
831
+ return { frames, rest };
832
+ }
833
+
834
+ function _parseSseFrame(frame) {
835
+ const lines = String(frame || '').split('\n');
836
+ const data = [];
837
+ for (const line of lines) {
838
+ if (!line || line.startsWith(':')) continue;
839
+ if (line.startsWith('data:')) data.push(line.slice(5).trimStart());
840
+ }
841
+ if (!data.length) return null;
842
+ const raw = data.join('\n').trim();
843
+ if (!raw || raw === '[DONE]') return null;
844
+ try { return JSON.parse(raw); } catch { return null; }
845
+ }
846
+
847
+ function _incompleteReasonFromEvent(event) {
848
+ const reasonObj = event?.response?.incomplete_details
849
+ || event?.incomplete_details
850
+ || event?.response?.status_details
851
+ || null;
852
+ return String(reasonObj?.reason || event?.response?.status || 'incomplete');
853
+ }
854
+
855
+ function _isMaxOutputIncompleteReason(reason) {
856
+ return /^(?:max_output_tokens|max_tokens|length|output_token_limit)$/i.test(String(reason || '').trim());
857
+ }
858
+
859
+ function _pushOutputTextAnnotations(part, citations, citationKeys) {
860
+ const annotations = Array.isArray(part?.annotations) ? part.annotations : [];
861
+ for (const raw of annotations) {
862
+ const url = raw?.url || raw?.uri || raw?.href || '';
863
+ if (!url || citationKeys.has(url)) continue;
864
+ citationKeys.add(url);
865
+ citations.push({
866
+ title: raw?.title || '',
867
+ url,
868
+ snippet: raw?.snippet || raw?.text || raw?.description || '',
869
+ source: 'openai-oauth',
870
+ });
871
+ }
872
+ }
873
+
874
+ function _buildOpenAIHttpFallbackHeaders({ auth, cacheKey }) {
875
+ const headers = {
876
+ Authorization: `Bearer ${auth.access_token}`,
877
+ 'Content-Type': 'application/json',
878
+ Accept: 'text/event-stream',
879
+ 'OpenAI-Beta': 'responses=experimental',
880
+ originator: CODEX_OAUTH_ORIGINATOR,
881
+ 'chatgpt-account-id': auth.account_id || '',
882
+ 'x-client-request-id': randomBytes(16).toString('hex'),
883
+ };
884
+ if (cacheKey) headers.session_id = String(cacheKey);
885
+ return headers;
886
+ }
887
+
888
+ function _shouldUseOpenAIHttpFallback(err, externalSignal) {
889
+ if (!_envFlag('MIXDOG_OPENAI_OAUTH_HTTP_FALLBACK', true)) return false;
890
+ if (externalSignal?.aborted) return false;
891
+ // Live-text invariant: if the WS attempt already relayed a non-empty text
892
+ // chunk to the client, the HTTP fallback would re-run the request and
893
+ // concatenate a second attempt onto rendered output. Never fall back.
894
+ if (err?.liveTextEmitted === true) return false;
895
+ const status = Number(err?.httpStatus || err?.status || 0);
896
+ if (status === 401 || status === 403 || status === 404 || status === 429) return false;
897
+ if (status >= 500 && status < 600) return true;
898
+ const code = String(err?.code || '');
899
+ if (['EWSACQUIRETIMEOUT', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'ECONNRESET', 'EAI_AGAIN', 'ENOTFOUND', 'EAI_NODATA', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'EPIPE'].includes(code)) {
900
+ return true;
901
+ }
902
+ const classifier = String(err?.retryClassifier || err?.midstreamClassifier || '');
903
+ if (['timeout', 'reset', 'dns', 'refused', 'network', 'acquire_timeout', 'http_5xx', 'first_byte_timeout', 'first_meaningful_timeout'].includes(classifier)) {
904
+ return true;
905
+ }
906
+ if (/^http_5\d\d$/.test(classifier)) return true;
907
+ if (err?.firstByteTimeout) return true;
908
+ if (err?.firstMeaningfulTimeout) return true;
909
+ const msg = String(err?.message || '');
910
+ return /opening handshake has timed out|socket hang up|acquire timed out|no first server event|no meaningful output/i.test(msg);
911
+ }
912
+
913
+ // Exported for the single-emit regression smoke (scripts/openai-oauth-
914
+ // http-sse-toolcall-smoke.mjs): the SSE stream can surface the same
915
+ // function_call across response.function_call_arguments.done +
916
+ // response.output_item.done + response.completed, and onToolCall must fire
917
+ // exactly once per call id. No production caller imports this name; the
918
+ // provider invokes it internally.
919
+ export async function sendViaHttpSse({
920
+ auth,
921
+ body,
922
+ opts,
923
+ onStreamDelta,
924
+ onToolCall,
925
+ onTextDelta,
926
+ onStageChange,
927
+ externalSignal,
928
+ poolKey,
929
+ cacheKey,
930
+ iteration,
931
+ useModel,
932
+ fetchFn = fetch,
933
+ } = {}) {
934
+ const totalTimeout = createTimeoutSignal(
935
+ externalSignal,
936
+ PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
937
+ 'OpenAI OAuth HTTP fallback total',
938
+ );
939
+ const headerTimeout = createTimeoutSignal(
940
+ totalTimeout.signal,
941
+ PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
942
+ 'OpenAI OAuth HTTP fallback initial response',
943
+ );
944
+ const headers = _buildOpenAIHttpFallbackHeaders({ auth, cacheKey });
945
+ const fetchStartedAt = Date.now();
946
+ let response;
947
+ try {
948
+ try { onStageChange?.('requesting'); } catch {}
949
+ response = await fetchFn(CODEX_RESPONSES_URL, {
950
+ method: 'POST',
951
+ headers,
952
+ body: JSON.stringify(body),
953
+ signal: headerTimeout.signal,
954
+ dispatcher: getLlmDispatcher(),
955
+ });
956
+ } catch (err) {
957
+ if (headerTimeout.signal?.aborted && headerTimeout.signal.reason instanceof Error) throw headerTimeout.signal.reason;
958
+ throw err;
959
+ } finally {
960
+ headerTimeout.cleanup();
961
+ }
962
+
963
+ traceBridgeFetch({
964
+ sessionId: poolKey,
965
+ headersMs: Date.now() - fetchStartedAt,
966
+ httpStatus: response.status,
967
+ provider: 'openai-oauth',
968
+ model: useModel,
969
+ transport: 'http',
970
+ });
971
+
972
+ if (!response.ok) {
973
+ const text = await response.text().catch(() => '');
974
+ const err = new Error(`OpenAI OAuth HTTP fallback ${response.status}: ${text.slice(0, 200)}`);
975
+ err.httpStatus = response.status;
976
+ err.headers = response.headers;
977
+ populateHttpStatusFromMessage(err, text);
978
+ totalTimeout.cleanup();
979
+ throw err;
980
+ }
981
+ if (!response.body) {
982
+ totalTimeout.cleanup();
983
+ throw new Error('OpenAI OAuth HTTP fallback returned no response body');
984
+ }
985
+
986
+ try { onStageChange?.('streaming'); } catch {}
987
+ const sseStartedAt = Date.now();
988
+ const reader = response.body.getReader();
989
+ const decoder = new TextDecoder();
990
+ // After headerTimeout.cleanup() the in-flight fetch no longer carries a live
991
+ // signal, so a totalTimeout / external abort that fires during a pending
992
+ // reader.read() would otherwise leave the pooled request hanging. Keep the
993
+ // reader tied to totalTimeout for the whole stream: on abort, cancel the
994
+ // reader so the awaited read() unblocks and the socket is released back to
995
+ // the shared pool instead of leaking. reader.cancel() may resolve the
996
+ // pending read() as {done:true} rather than rejecting, which would let a
997
+ // partial response surface as success — so record the abort reason and
998
+ // re-throw it after the loop unblocks (see below).
999
+ let _streamAbortReason = null;
1000
+ let _onTotalAbort = null;
1001
+ if (totalTimeout.signal) {
1002
+ _onTotalAbort = () => {
1003
+ const reason = totalTimeout.signal.reason;
1004
+ _streamAbortReason = reason instanceof Error
1005
+ ? reason
1006
+ : new Error('OpenAI OAuth HTTP fallback aborted');
1007
+ try { reader.cancel(_streamAbortReason).catch(() => {}); } catch {}
1008
+ };
1009
+ if (totalTimeout.signal.aborted) _onTotalAbort();
1010
+ else totalTimeout.signal.addEventListener('abort', _onTotalAbort, { once: true });
1011
+ }
1012
+ let buffer = '';
1013
+ let content = '';
1014
+ let model = '';
1015
+ let responseId = '';
1016
+ let serviceTier = '';
1017
+ let usage = null;
1018
+ let ttftMs = null;
1019
+ const toolCalls = [];
1020
+ const pendingCalls = new Map();
1021
+ const reasoningItems = [];
1022
+ const compactionItems = [];
1023
+ const citations = [];
1024
+ const citationKeys = new Set();
1025
+ const webSearchCalls = [];
1026
+ const webSearchCallKeys = new Set();
1027
+ let completed = false;
1028
+ let stopReason = null;
1029
+ // Gateway live-text relay invariant: set once a non-empty text chunk has
1030
+ // been forwarded to the client. A failure afterwards is non-retryable —
1031
+ // the rendered text cannot be withdrawn and a re-request would concatenate
1032
+ // a second attempt.
1033
+ let emittedText = false;
1034
+
1035
+ // Single-emit guard for tool calls (matches the WS path's
1036
+ // emittedToolCall intent). The HTTP/SSE event stream can surface the
1037
+ // same function_call across multiple frames — response.function_call_arguments.done,
1038
+ // response.output_item.done, and the final response.completed.output
1039
+ // bundle. Each frame independently completes the call (id + name) and
1040
+ // would re-invoke onToolCall, double-executing a side-effecting tool.
1041
+ // Route every emit through emitToolCall: it fires the callback exactly
1042
+ // once per unique call id, the first time the call is complete. A call
1043
+ // whose id/name only arrives in a later frame is NOT dropped — its
1044
+ // first complete frame still emits; only redundant re-emits are
1045
+ // suppressed.
1046
+ const emittedToolCallIds = new Set();
1047
+ const emitToolCall = (call) => {
1048
+ if (!call || !call.id) return;
1049
+ if (emittedToolCallIds.has(call.id)) return;
1050
+ emittedToolCallIds.add(call.id);
1051
+ try { onToolCall?.(call); } catch {}
1052
+ };
1053
+
1054
+ const pushWebSearchCall = (item) => {
1055
+ if (!item || item.type !== 'web_search_call') return;
1056
+ const key = item.id || JSON.stringify(item.action || item);
1057
+ if (webSearchCallKeys.has(key)) return;
1058
+ webSearchCallKeys.add(key);
1059
+ webSearchCalls.push({ id: item.id || '', status: item.status || '', action: item.action || null });
1060
+ };
1061
+ const pushReasoningItem = (item) => {
1062
+ if (item?.type === 'reasoning' && item.encrypted_content && !reasoningItems.some(r => r.id === item.id)) {
1063
+ reasoningItems.push({
1064
+ id: item.id || '',
1065
+ encrypted_content: item.encrypted_content,
1066
+ summary: Array.isArray(item.summary) ? item.summary : [],
1067
+ });
1068
+ }
1069
+ };
1070
+ const pushCompactionItem = (item) => {
1071
+ if (!item || !['compaction', 'compaction_summary', 'context_compaction'].includes(item.type)) return;
1072
+ if (!item.encrypted_content) return;
1073
+ compactionItems.push(item);
1074
+ };
1075
+ const meaningful = () => {
1076
+ if (ttftMs == null) ttftMs = Date.now() - sseStartedAt;
1077
+ try { onStreamDelta?.(); } catch {}
1078
+ };
1079
+ const handleEvent = (event) => {
1080
+ if (!event || typeof event.type !== 'string') return;
1081
+ switch (event.type) {
1082
+ case 'response.created':
1083
+ if (event.response?.model) model = event.response.model;
1084
+ if (event.response?.id) responseId = event.response.id;
1085
+ break;
1086
+ case 'response.output_text.delta':
1087
+ content += event.delta || '';
1088
+ meaningful();
1089
+ if (event.delta && onTextDelta) {
1090
+ emittedText = true;
1091
+ try { onTextDelta(event.delta); } catch {}
1092
+ }
1093
+ break;
1094
+ case 'response.reasoning_text.delta':
1095
+ case 'response.reasoning_summary_text.delta':
1096
+ meaningful();
1097
+ break;
1098
+ case 'response.output_item.added':
1099
+ if (event.item?.type === 'function_call') {
1100
+ pendingCalls.set(event.item.id || '', {
1101
+ name: event.item.name || '',
1102
+ callId: event.item.call_id || '',
1103
+ });
1104
+ }
1105
+ break;
1106
+ case 'response.function_call_arguments.delta':
1107
+ meaningful();
1108
+ break;
1109
+ case 'response.function_call_arguments.done': {
1110
+ const itemId = event.item_id || '';
1111
+ const pending = pendingCalls.get(itemId);
1112
+ const call = {
1113
+ id: pending?.callId || event.call_id || '',
1114
+ name: pending?.name || event.name || '',
1115
+ arguments: _parseJsonObject(event.arguments),
1116
+ _pendingItemId: itemId,
1117
+ };
1118
+ toolCalls.push(call);
1119
+ if (call.id && call.name) {
1120
+ delete call._pendingItemId;
1121
+ emitToolCall(call);
1122
+ }
1123
+ meaningful();
1124
+ break;
1125
+ }
1126
+ case 'response.output_item.done': {
1127
+ const item = event.item || {};
1128
+ pushReasoningItem(item);
1129
+ pushWebSearchCall(item);
1130
+ pushCompactionItem(item);
1131
+ if (item.type === 'function_call') {
1132
+ const tc = toolCalls.find(t => t._pendingItemId === (item.id || ''));
1133
+ if (tc) {
1134
+ if (!tc.id && item.call_id) tc.id = item.call_id;
1135
+ if (!tc.name && item.name) tc.name = item.name;
1136
+ if (tc.id && tc.name) {
1137
+ delete tc._pendingItemId;
1138
+ emitToolCall(tc);
1139
+ }
1140
+ }
1141
+ }
1142
+ break;
1143
+ }
1144
+ case 'response.completed': {
1145
+ const resp = event.response || {};
1146
+ serviceTier = resp.service_tier || resp.serviceTier || serviceTier;
1147
+ if (!model && resp.model) model = resp.model;
1148
+ if (!responseId && resp.id) responseId = resp.id;
1149
+ if (resp.usage) {
1150
+ usage = {
1151
+ inputTokens: resp.usage.input_tokens || 0,
1152
+ outputTokens: resp.usage.output_tokens || 0,
1153
+ cachedTokens: _extractCachedTokens(resp.usage),
1154
+ promptTokens: resp.usage.input_tokens || 0,
1155
+ raw: serviceTier ? { ...resp.usage, service_tier: serviceTier } : resp.usage,
1156
+ };
1157
+ }
1158
+ for (const item of resp.output || []) {
1159
+ if (item.type === 'message') {
1160
+ for (const part of item.content || []) {
1161
+ if (!content && part.type === 'output_text') content += part.text || '';
1162
+ if (part.type === 'output_text') _pushOutputTextAnnotations(part, citations, citationKeys);
1163
+ }
1164
+ } else if (item.type === 'reasoning') {
1165
+ pushReasoningItem(item);
1166
+ } else if (item.type === 'web_search_call') {
1167
+ pushWebSearchCall(item);
1168
+ } else if (['compaction', 'compaction_summary', 'context_compaction'].includes(item.type)) {
1169
+ pushCompactionItem(item);
1170
+ } else if (item.type === 'function_call') {
1171
+ // Match the still-pending placeholder by item id, or
1172
+ // an already-recorded call by its canonical call_id —
1173
+ // so a call completed at args.done / output_item.done
1174
+ // is reused here rather than re-pushed as a duplicate.
1175
+ const tc = toolCalls.find(t =>
1176
+ t._pendingItemId === (item.id || '')
1177
+ || (item.call_id && t.id === item.call_id));
1178
+ if (tc) {
1179
+ if (!tc.id && item.call_id) tc.id = item.call_id;
1180
+ if (!tc.name && item.name) tc.name = item.name;
1181
+ if (tc.id && tc.name) {
1182
+ delete tc._pendingItemId;
1183
+ emitToolCall(tc);
1184
+ }
1185
+ } else if (item.call_id && item.name) {
1186
+ const call = {
1187
+ id: item.call_id,
1188
+ name: item.name,
1189
+ arguments: _parseJsonObject(item.arguments),
1190
+ };
1191
+ toolCalls.push(call);
1192
+ emitToolCall(call);
1193
+ }
1194
+ }
1195
+ }
1196
+ completed = true;
1197
+ break;
1198
+ }
1199
+ case 'response.done':
1200
+ if (!event.response || event.response.status === 'completed') completed = true;
1201
+ else if (event.response.status === 'failed') {
1202
+ const msg = event.response?.error?.message || 'response.done failed';
1203
+ const err = new Error(`OpenAI OAuth HTTP fallback response.done failed: ${msg}`);
1204
+ populateHttpStatusFromMessage(err, msg);
1205
+ throw err;
1206
+ } else if (event.response.status === 'incomplete') {
1207
+ const reason = _incompleteReasonFromEvent(event);
1208
+ if (_isMaxOutputIncompleteReason(reason)) {
1209
+ completed = true;
1210
+ stopReason = 'length';
1211
+ break;
1212
+ }
1213
+ throw new Error(`OpenAI OAuth HTTP fallback response.done incomplete: ${reason}`);
1214
+ }
1215
+ break;
1216
+ case 'response.failed': {
1217
+ const msg = event.response?.error?.message || event.error?.message || event.message || 'response.failed';
1218
+ const err = new Error(`OpenAI OAuth HTTP fallback response.failed: ${msg}`);
1219
+ populateHttpStatusFromMessage(err, msg);
1220
+ throw err;
1221
+ }
1222
+ case 'response.incomplete': {
1223
+ const reason = _incompleteReasonFromEvent(event);
1224
+ if (_isMaxOutputIncompleteReason(reason)) {
1225
+ completed = true;
1226
+ stopReason = 'length';
1227
+ break;
1228
+ }
1229
+ throw new Error(`OpenAI OAuth HTTP fallback response.incomplete: ${reason}`);
1230
+ }
1231
+ case 'error': {
1232
+ const msg = event.message || event.error?.message || 'unknown';
1233
+ const err = new Error(`OpenAI OAuth HTTP fallback error: ${msg}`);
1234
+ populateHttpStatusFromMessage(err, msg);
1235
+ throw err;
1236
+ }
1237
+ default:
1238
+ break;
1239
+ }
1240
+ };
1241
+
1242
+ try {
1243
+ while (true) {
1244
+ if (totalTimeout.signal.aborted) {
1245
+ const reason = totalTimeout.signal.reason;
1246
+ throw reason instanceof Error ? reason : new Error('OpenAI OAuth HTTP fallback aborted');
1247
+ }
1248
+ const { value, done } = await reader.read();
1249
+ if (done) break;
1250
+ buffer += decoder.decode(value, { stream: true });
1251
+ const parsed = _sseEventsFromBuffer(buffer);
1252
+ buffer = parsed.rest;
1253
+ for (const frame of parsed.frames) {
1254
+ const event = _parseSseFrame(frame);
1255
+ if (event) handleEvent(event);
1256
+ }
1257
+ }
1258
+ // The read() above can unblock via reader.cancel() as {done:true} on an
1259
+ // external/total-timeout abort. Surface that as the abort/timeout error
1260
+ // instead of treating the partial stream as a successful response.
1261
+ if (_streamAbortReason) throw _streamAbortReason;
1262
+ buffer += decoder.decode();
1263
+ const parsed = _sseEventsFromBuffer(buffer + '\n\n');
1264
+ for (const frame of parsed.frames) {
1265
+ const event = _parseSseFrame(frame);
1266
+ if (event) handleEvent(event);
1267
+ }
1268
+ } catch (err) {
1269
+ // Live-text invariant: once a non-empty chunk has been relayed it
1270
+ // cannot be withdrawn — flag the error so no upstream layer retries.
1271
+ if (emittedText && err) { try { err.liveTextEmitted = true; err.unsafeToRetry = true; } catch {} }
1272
+ throw err;
1273
+ } finally {
1274
+ try { reader.releaseLock?.(); } catch {}
1275
+ if (_onTotalAbort && totalTimeout.signal) {
1276
+ try { totalTimeout.signal.removeEventListener('abort', _onTotalAbort); } catch {}
1277
+ }
1278
+ totalTimeout.cleanup();
1279
+ }
1280
+
1281
+ const unresolved = toolCalls.find(t => t._pendingItemId);
1282
+ if (unresolved) {
1283
+ throw new Error(`OpenAI OAuth HTTP fallback function_call salvage failed: missing call_id/name for item_id=${unresolved._pendingItemId || '?'}`);
1284
+ }
1285
+ if (opts?.expectCompaction === true && compactionItems.length !== 1) {
1286
+ throw new Error(`OpenAI OAuth HTTP fallback remote compaction expected exactly one compaction output item, got ${compactionItems.length}`);
1287
+ }
1288
+ if (!completed && !content && !toolCalls.length) {
1289
+ throw new Error('OpenAI OAuth HTTP fallback ended before response.completed');
1290
+ }
1291
+
1292
+ const liveModel = model || useModel;
1293
+ traceBridgeSse({
1294
+ sessionId: poolKey,
1295
+ sseParseMs: Date.now() - sseStartedAt,
1296
+ ttftMs,
1297
+ provider: 'openai-oauth',
1298
+ model: liveModel,
1299
+ transport: 'sse',
1300
+ });
1301
+ if (usage) {
1302
+ traceBridgeUsage({
1303
+ sessionId: poolKey,
1304
+ iteration,
1305
+ inputTokens: usage.inputTokens || 0,
1306
+ outputTokens: usage.outputTokens || 0,
1307
+ cachedTokens: usage.cachedTokens || 0,
1308
+ promptTokens: usage.promptTokens || 0,
1309
+ model: liveModel,
1310
+ modelDisplay: _displayCodexModel(liveModel),
1311
+ responseId: responseId || null,
1312
+ rawUsage: usage.raw || null,
1313
+ provider: 'openai-oauth',
1314
+ serviceTier,
1315
+ });
1316
+ }
1317
+ return {
1318
+ content,
1319
+ model: liveModel,
1320
+ reasoningItems: reasoningItems.length ? reasoningItems : undefined,
1321
+ compactionItem: compactionItems.length === 1 ? compactionItems[0] : undefined,
1322
+ compactionItems: compactionItems.length ? compactionItems : undefined,
1323
+ toolCalls: toolCalls.length ? toolCalls.map(({ _pendingItemId, ...t }) => t) : undefined,
1324
+ citations: citations.length ? citations : undefined,
1325
+ webSearchCalls: webSearchCalls.length ? webSearchCalls : undefined,
1326
+ usage: usage || undefined,
1327
+ stopReason: stopReason || undefined,
1328
+ responseId: responseId || undefined,
1329
+ serviceTier: serviceTier || undefined,
1330
+ };
1331
+ }
1332
+
1333
+ // --- Provider ---
1334
+ export class OpenAIOAuthProvider {
1335
+ // OpenAI input_tokens already INCLUDES cached_tokens (cached is a subset),
1336
+ // so input alone is the context footprint. See registry.mjs.
1337
+ static inputExcludesCache = false;
1338
+ name = 'openai-oauth';
1339
+ tokens = null;
1340
+ _refreshFallbackUntil = 0;
1341
+ _forceHttpFallback = false;
1342
+ config;
1343
+ constructor(config) {
1344
+ this.config = config || {};
1345
+ this.tokens = loadTokens();
1346
+ // Warm a kept-alive socket to the Codex responses API so the first
1347
+ // request skips the cold TLS handshake. Best-effort; never throws.
1348
+ preconnect('https://chatgpt.com');
1349
+ }
1350
+ getCachedModelInfo(model) {
1351
+ return _findCachedCodexModel(model);
1352
+ }
1353
+ async remoteCompactMessages(messages, model, tools, sendOpts = {}) {
1354
+ const opts = {
1355
+ ...sendOpts,
1356
+ expectCompaction: true,
1357
+ nativeCompact: true,
1358
+ };
1359
+ const useModel = model || await ensureLatestCodexModel(this);
1360
+ const promptCacheLane = resolveProviderPromptCacheLane('openai-oauth', opts, this.config);
1361
+ const bodyOpts = {
1362
+ ...opts,
1363
+ promptCacheLane,
1364
+ };
1365
+ const body = buildRemoteCompactionRequestBody(messages, useModel, tools, bodyOpts);
1366
+ let auth = await this.ensureAuth();
1367
+ const poolKey = opts.sessionId || null;
1368
+ const cacheKey = body.prompt_cache_key || resolveProviderCacheKey(opts, 'openai-oauth');
1369
+ const iteration = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null;
1370
+ const onStageChange = typeof opts.onStageChange === 'function' ? opts.onStageChange : null;
1371
+ const sendWs = typeof opts._sendViaWebSocketFn === 'function' ? opts._sendViaWebSocketFn : sendViaWebSocket;
1372
+ const sendHttp = typeof opts._sendViaHttpSseFn === 'function' ? opts._sendViaHttpSseFn : sendViaHttpSse;
1373
+ const dispatchHttp = async (reason, originalErr = null) => {
1374
+ appendBridgeTrace({
1375
+ sessionId: poolKey,
1376
+ iteration,
1377
+ kind: 'transport_fallback',
1378
+ provider: 'openai-oauth',
1379
+ model: useModel,
1380
+ transport: 'http',
1381
+ payload: {
1382
+ from: 'websocket',
1383
+ to: 'http',
1384
+ reason,
1385
+ remote_compact: true,
1386
+ error_code: originalErr?.code || null,
1387
+ error_http_status: Number(originalErr?.httpStatus || 0) || null,
1388
+ },
1389
+ });
1390
+ return sendHttp({
1391
+ auth,
1392
+ body,
1393
+ opts,
1394
+ onStreamDelta: null,
1395
+ onToolCall: null,
1396
+ onStageChange,
1397
+ externalSignal: opts.signal || null,
1398
+ poolKey,
1399
+ cacheKey,
1400
+ iteration,
1401
+ useModel,
1402
+ fetchFn: opts._fetchFn,
1403
+ });
1404
+ };
1405
+ const dispatchWs = (forceFresh = false) => sendWs({
1406
+ auth,
1407
+ body,
1408
+ sendOpts: opts,
1409
+ onStreamDelta: null,
1410
+ onToolCall: null,
1411
+ onStageChange,
1412
+ externalSignal: opts.signal || null,
1413
+ poolKey,
1414
+ cacheKey,
1415
+ iteration,
1416
+ useModel,
1417
+ displayModel: _displayCodexModel,
1418
+ forceFresh,
1419
+ includeResponseId: true,
1420
+ });
1421
+
1422
+ let result;
1423
+ if (opts.forceHttpFallback === true
1424
+ || this._forceHttpFallback
1425
+ || _envFlag('MIXDOG_OPENAI_OAUTH_FORCE_HTTP_FALLBACK', false)) {
1426
+ result = await dispatchHttp('forced');
1427
+ } else {
1428
+ try {
1429
+ result = await dispatchWs(false);
1430
+ } catch (err) {
1431
+ const status = err?.httpStatus;
1432
+ if (status === 401 || status === 403) {
1433
+ this._refreshFallbackUntil = 0;
1434
+ auth = await this.ensureAuth({ forceRefresh: true, reason: String(status) });
1435
+ result = await dispatchWs(true);
1436
+ } else if (_shouldUseOpenAIHttpFallback(err, opts.signal || null)) {
1437
+ result = await dispatchHttp(err?.retryClassifier || err?.midstreamClassifier || err?.code || err?.message || 'ws_failed', err);
1438
+ } else {
1439
+ throw err;
1440
+ }
1441
+ }
1442
+ }
1443
+
1444
+ if (!result?.compactionItem) {
1445
+ throw new Error('OpenAI OAuth remote compact completed without compaction item');
1446
+ }
1447
+ const liveModel = result.model || useModel;
1448
+ if (liveModel && !_codexCatalogHas(liveModel)) void this._refreshModelCache();
1449
+ const nativePrefix = _buildOpenAICodexNativeCompactPrefix(messages, result.compactionItem);
1450
+ return {
1451
+ model: liveModel,
1452
+ usage: result.usage,
1453
+ responseId: result.responseId || null,
1454
+ providerState: _withOpenAICodexRemoteCompactState(opts.providerState, {
1455
+ model: useModel,
1456
+ nativePrefix,
1457
+ responseId: result.responseId || null,
1458
+ }),
1459
+ };
1460
+ }
1461
+ async ensureAuth({ forceRefresh = false, reason = 'preemptive' } = {}) {
1462
+ if (!this.tokens) this.tokens = loadTokens();
1463
+ if (!this.tokens)
1464
+ throw new Error('OpenAI OAuth not authenticated. Run codex login first.');
1465
+ // Pick up disk-rotated tokens (codex login, host refresh) the moment
1466
+ // the auth file is rewritten — without this, a fresh login is ignored
1467
+ // until the in-memory token hits its expiry skew.
1468
+ const diskMtime = _tokensMaxMtime();
1469
+ // Watermark guards termination: if the newest file on disk isn't loadable
1470
+ // (e.g. a logged-out host auth.json beside a valid own store), loadTokens
1471
+ // falls back to the older valid store; record the scanned mtime so this
1472
+ // check can't re-fire on every ensureAuth().
1473
+ if (diskMtime > 0 && diskMtime > (this._lastDiskScan || 0) && diskMtime > (this.tokens._mtimeMs || 0)) {
1474
+ const fresh = loadTokens();
1475
+ if (fresh?.access_token) {
1476
+ this.tokens = fresh;
1477
+ this._refreshFallbackUntil = 0;
1478
+ process.stderr.write(`[openai-oauth] Reloaded tokens from disk (mtime change)\n`);
1479
+ }
1480
+ this._lastDiskScan = diskMtime;
1481
+ }
1482
+ if (!forceRefresh && this._refreshFallbackUntil > Date.now() && this.tokens?.access_token) {
1483
+ return this.tokens;
1484
+ }
1485
+ const expiring = this.tokens.expires_at
1486
+ ? this.tokens.expires_at < Date.now() + TOKEN_REFRESH_SKEW_MS
1487
+ : false;
1488
+ if (forceRefresh || expiring) {
1489
+ this._refreshFallbackUntil = 0;
1490
+ this.tokens = await this._refreshTokens({ force: forceRefresh, reason });
1491
+ }
1492
+ return this.tokens;
1493
+ }
1494
+
1495
+ async _refreshTokens({ force = false, reason = 'preemptive' } = {}) {
1496
+ const currentToken = this.tokens?.access_token || null;
1497
+ const disk = loadTokens();
1498
+ const validAfter = Date.now() + (force ? 0 : TOKEN_REFRESH_SKEW_MS);
1499
+ if (disk?.access_token && disk.access_token !== currentToken
1500
+ && (!disk.expires_at || disk.expires_at >= validAfter)) {
1501
+ this.tokens = disk;
1502
+ process.stderr.write(`[openai-oauth] Reloaded tokens from disk\n`);
1503
+ return disk;
1504
+ }
1505
+ if (!this.tokens && disk) this.tokens = disk;
1506
+
1507
+ if (_oauthRefreshInFlight) {
1508
+ const shared = await _oauthRefreshInFlight;
1509
+ this.tokens = shared;
1510
+ if (!force || shared?.access_token !== currentToken) return this.tokens;
1511
+ }
1512
+
1513
+ const startingTokens = this.tokens || disk;
1514
+ _oauthRefreshInFlight = (async () => {
1515
+ const latest = loadTokens() || startingTokens;
1516
+ const latestValidAfter = Date.now() + (force ? 0 : TOKEN_REFRESH_SKEW_MS);
1517
+ if (latest?.access_token && latest.access_token !== currentToken
1518
+ && (!latest.expires_at || latest.expires_at >= latestValidAfter)) {
1519
+ process.stderr.write(`[openai-oauth] Reloaded tokens from disk\n`);
1520
+ return latest;
1521
+ }
1522
+
1523
+ if (!latest?.refresh_token) {
1524
+ if (!force && latest?.access_token && (!latest.expires_at || latest.expires_at > Date.now())) {
1525
+ process.stderr.write(`[openai-oauth] WARNING: token expiring but no refresh token; using current token until expiry\n`);
1526
+ this._refreshFallbackUntil = Date.now() + TOKEN_REFRESH_SKEW_MS;
1527
+ return latest;
1528
+ }
1529
+ throw new Error('OpenAI OAuth refresh token not available. Run codex login to re-authenticate.');
1530
+ }
1531
+
1532
+ try {
1533
+ const _refreshT0 = Date.now();
1534
+ const _expiringInMs = (latest?.expires_at ?? 0) - Date.now();
1535
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] auth-refresh-needed expiringInMs=${_expiringInMs}\n`); }
1536
+ process.stderr.write(`[openai-oauth] Token ${reason}, refreshing...\n`);
1537
+ let refreshed;
1538
+ try {
1539
+ refreshed = await refreshTokens(latest.refresh_token);
1540
+ } catch (refreshErr) {
1541
+ // invalid_grant: the Codex CLI rotated this single-use refresh
1542
+ // token between our disk read and this refresh. Re-read both
1543
+ // stores and retry ONCE with the freshest different token.
1544
+ if (!refreshErr?.isInvalidGrant) throw refreshErr;
1545
+ process.stderr.write('[openai-oauth] invalid_grant — re-reading disk, retrying refresh\n');
1546
+ const candidates = [_loadOwnCodexTokens(), _loadCodexCliTokens()].filter(Boolean)
1547
+ .sort((a, b) => (b._mtimeMs || 0) - (a._mtimeMs || 0));
1548
+ const freshTok = candidates.find(c => c.refresh_token && c.refresh_token !== latest.refresh_token);
1549
+ if (!freshTok) throw refreshErr;
1550
+ refreshed = await refreshTokens(freshTok.refresh_token);
1551
+ }
1552
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] auth-refresh-done elapsed=${Date.now() - _refreshT0}ms ok=${!!refreshed}\n`); }
1553
+ if (!refreshed) throw new Error('refresh returned null');
1554
+ process.stderr.write(`[openai-oauth] Token refreshed, expires in ${Math.round(((refreshed.expires_at || Date.now()) - Date.now()) / 1000)}s\n`);
1555
+ return refreshed;
1556
+ }
1557
+ catch (err) {
1558
+ const msg = err instanceof Error ? err.message : String(err);
1559
+ if (!force && latest?.access_token && (!latest.expires_at || latest.expires_at > Date.now())) {
1560
+ this._refreshFallbackUntil = Date.now() + TOKEN_REFRESH_SKEW_MS;
1561
+ process.stderr.write(`[openai-oauth] Refresh failed (${msg}); using still-valid current token\n`);
1562
+ return latest;
1563
+ }
1564
+ throw new Error(`OpenAI OAuth token refresh failed (${msg}). Run codex login to re-authenticate.`);
1565
+ }
1566
+ })().finally(() => { _oauthRefreshInFlight = null; });
1567
+
1568
+ this.tokens = await _oauthRefreshInFlight;
1569
+ return this.tokens;
1570
+ }
1571
+ async send(messages, model, tools, sendOpts) {
1572
+ const opts = sendOpts || {};
1573
+ const onStageChange = typeof opts.onStageChange === 'function' ? opts.onStageChange : null;
1574
+ const onStreamDelta = typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null;
1575
+ const onToolCall = typeof opts.onToolCall === 'function' ? opts.onToolCall : null;
1576
+ const onTextDelta = typeof opts.onTextDelta === 'function' ? opts.onTextDelta : null;
1577
+ const externalSignal = opts.signal || null;
1578
+ const _sendSessionId = opts.sessionId || '(none)';
1579
+ const _sendRole = opts.role || '(none)';
1580
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] auth-start sessionHash=${createHash('sha256').update(String(_sendSessionId)).digest('hex').slice(0, 8)} role=${_sendRole} expiringInMs=${this.tokens?.expires_at ? this.tokens.expires_at - Date.now() : 'unknown'}\n`); }
1581
+ // Build request body in parallel with auth resolution. ensureAuth is
1582
+ // a no-op fast-path on cached tokens, but a refresh round-trip can
1583
+ // take 300ms+; the body build (message serialisation) overlaps cleanly.
1584
+ const useModel = model || await ensureLatestCodexModel(this);
1585
+ // Escape hatch for callers (e.g. the web-search backend) that ship a
1586
+ // fully-formed request body with a server-side tool shape buildRequestBody
1587
+ // can't express. Routing through send() still gives them the 401/403
1588
+ // force-refresh retry + HTTP/SSE fallback instead of a hard fail.
1589
+ const promptCacheLane = resolveProviderPromptCacheLane('openai-oauth', opts, this.config);
1590
+ const bodyOpts = {
1591
+ ...sendOpts,
1592
+ promptCacheLane,
1593
+ };
1594
+ const _bodyP = opts._prebuiltBody
1595
+ ? Promise.resolve(opts._prebuiltBody)
1596
+ : Promise.resolve().then(() => buildRequestBody(messages, useModel, tools, bodyOpts));
1597
+ const _authP = this.ensureAuth();
1598
+ let auth = await _authP;
1599
+ const body = await _bodyP;
1600
+ const hasImageContent = messagesHaveImageContent(messages);
1601
+ // poolKey ≠ cacheKey by design (see openai-oauth-ws.mjs header note).
1602
+ // poolKey is per-session so parallel reviewer/worker callers each
1603
+ // get their own socket bucket — a sibling cannot grab a mid-turn
1604
+ // entry and trip Codex's "No tool call found for function call
1605
+ // output with call_id …" rejection. cacheKey is prefix-scoped
1606
+ // (base namespace + model/system/tools hash) and feeds both
1607
+ // `body.prompt_cache_key` and the handshake `session_id` header, so
1608
+ // compatible prefixes share cache without main/worker lanes evicting
1609
+ // each other.
1610
+ // poolKey defaults to sessionId (per-session socket isolation); cacheKey
1611
+ // never falls back to sessionId, so a fresh session still reuses the
1612
+ // warm prefix cache for the same route/prefix.
1613
+ const poolKey = opts.sessionId || null;
1614
+ const cacheKey = body.prompt_cache_key || resolveProviderCacheKey(opts, 'openai-oauth');
1615
+ const iteration = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null;
1616
+ const sendWs = typeof opts._sendViaWebSocketFn === 'function' ? opts._sendViaWebSocketFn : sendViaWebSocket;
1617
+ const sendHttp = typeof opts._sendViaHttpSseFn === 'function' ? opts._sendViaHttpSseFn : sendViaHttpSse;
1618
+ const _t1 = Date.now();
1619
+ const recordLiveModel = (result) => {
1620
+ if (result?.model && !_codexCatalogHas(result.model)) {
1621
+ void this._refreshModelCache();
1622
+ }
1623
+ if (result && opts.providerState !== undefined && result.providerState === undefined) {
1624
+ result.providerState = opts.providerState;
1625
+ }
1626
+ return result;
1627
+ };
1628
+ const dispatchHttp = async (reason, originalErr = null) => {
1629
+ appendBridgeTrace({
1630
+ sessionId: poolKey,
1631
+ iteration,
1632
+ kind: 'transport_fallback',
1633
+ provider: 'openai-oauth',
1634
+ model: useModel,
1635
+ transport: 'http',
1636
+ payload: {
1637
+ from: 'websocket',
1638
+ to: 'http',
1639
+ reason,
1640
+ error_code: originalErr?.code || null,
1641
+ error_http_status: Number(originalErr?.httpStatus || 0) || null,
1642
+ error_classifier: originalErr?.retryClassifier || originalErr?.midstreamClassifier || null,
1643
+ },
1644
+ });
1645
+ process.stderr.write(`[openai-oauth] WebSocket unhealthy (${reason}); falling back to HTTP/SSE\n`);
1646
+ const result = await sendHttp({
1647
+ auth,
1648
+ body,
1649
+ opts,
1650
+ onStreamDelta,
1651
+ onToolCall,
1652
+ onTextDelta,
1653
+ onStageChange,
1654
+ externalSignal,
1655
+ poolKey,
1656
+ cacheKey,
1657
+ iteration,
1658
+ useModel,
1659
+ fetchFn: opts._fetchFn,
1660
+ });
1661
+ this._forceHttpFallback = true;
1662
+ if (process.env.MIXDOG_DEBUG_BRIDGE) {
1663
+ process.stderr.write(`[bridge-trace] provider-send-end elapsed=${Date.now() - _t1}ms result=ok transport=http-fallback\n`);
1664
+ }
1665
+ return recordLiveModel(result);
1666
+ };
1667
+ const dispatchWs = (forceFresh = false) => sendWs({
1668
+ auth,
1669
+ body,
1670
+ sendOpts: opts,
1671
+ onStreamDelta,
1672
+ onToolCall,
1673
+ onTextDelta,
1674
+ onStageChange,
1675
+ externalSignal,
1676
+ poolKey,
1677
+ cacheKey,
1678
+ iteration,
1679
+ useModel,
1680
+ displayModel: _displayCodexModel,
1681
+ forceFresh,
1682
+ });
1683
+ if (opts.forceHttpFallback === true
1684
+ || this._forceHttpFallback
1685
+ || hasImageContent
1686
+ || _envFlag('MIXDOG_OPENAI_OAUTH_FORCE_HTTP_FALLBACK', false)) {
1687
+ return dispatchHttp(hasImageContent ? 'image_content' : 'forced');
1688
+ }
1689
+
1690
+ // Prefer WebSocket for hot cache/delta transport; fall back to HTTP/SSE
1691
+ // after retry-exhausted handshake/acquire/no-first-event failures.
1692
+ try {
1693
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] provider-send-start model=${useModel} role=${_sendRole} sessionHash=${createHash('sha256').update(String(_sendSessionId)).digest('hex').slice(0, 8)} iteration=${iteration ?? '(none)'}\n`); }
1694
+ const result = await dispatchWs(false);
1695
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] provider-send-end elapsed=${Date.now() - _t1}ms result=ok\n`); }
1696
+ return recordLiveModel(result);
1697
+ } catch (err) {
1698
+ const status = err?.httpStatus;
1699
+ // Live-text invariant: if the WS attempt already relayed a
1700
+ // non-empty text chunk to the client, NO recovery path may reissue
1701
+ // the turn — an auth-refresh + dispatchWs(true) retry would
1702
+ // concatenate a second attempt onto already-rendered output. Refuse
1703
+ // the retry (and the HTTP fallback below already refuses) and
1704
+ // surface the original error.
1705
+ const liveTextEmitted = err?.liveTextEmitted === true || err?.unsafeToRetry === true;
1706
+ if ((status === 401 || status === 403) && !liveTextEmitted) {
1707
+ process.stderr.write(`[openai-oauth-ws] ${status} — forcing refresh and retrying once over WS\n`);
1708
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] provider-${status}-retry attempt=1\n`); }
1709
+ this._refreshFallbackUntil = 0;
1710
+ auth = await this.ensureAuth({ forceRefresh: true, reason: String(status) });
1711
+ try {
1712
+ const result = await dispatchWs(true);
1713
+ if (process.env.MIXDOG_DEBUG_BRIDGE) { process.stderr.write(`[bridge-trace] provider-send-end elapsed=${Date.now() - _t1}ms result=ok\n`); }
1714
+ return recordLiveModel(result);
1715
+ } catch (retryErr) {
1716
+ if (_shouldUseOpenAIHttpFallback(retryErr, externalSignal)) {
1717
+ try {
1718
+ return await dispatchHttp(retryErr?.retryClassifier || retryErr?.code || retryErr?.message || 'ws_auth_retry_failed', retryErr);
1719
+ } catch (fallbackErr) {
1720
+ try { retryErr.fallbackError = fallbackErr; } catch {}
1721
+ throw retryErr;
1722
+ }
1723
+ }
1724
+ throw retryErr;
1725
+ }
1726
+ }
1727
+ // Auth failure after live text already emitted: never reissue.
1728
+ if ((status === 401 || status === 403) && liveTextEmitted) {
1729
+ throw err;
1730
+ }
1731
+ const msg = err?.message || '';
1732
+ const isUnknownModel = status === 404
1733
+ || /unknown[_\s-]?model|model[_\s-]?not[_\s-]?found/i.test(msg);
1734
+ if (isUnknownModel && !opts._modelRetry) {
1735
+ process.stderr.write(`[openai-oauth-ws] unknown model — refreshing catalog + 1 retry\n`);
1736
+ await this._refreshModelCache();
1737
+ return this.send(messages, model, tools, { ...opts, _modelRetry: true });
1738
+ }
1739
+ if (_shouldUseOpenAIHttpFallback(err, externalSignal)) {
1740
+ try {
1741
+ return await dispatchHttp(err?.retryClassifier || err?.midstreamClassifier || err?.code || err?.message || 'ws_failed', err);
1742
+ } catch (fallbackErr) {
1743
+ try { err.fallbackError = fallbackErr; } catch {}
1744
+ throw err;
1745
+ }
1746
+ }
1747
+ throw err;
1748
+ }
1749
+ }
1750
+ async listModels() {
1751
+ // Dynamic lookup via Codex /backend-api/codex/models. Cached 24h.
1752
+ // Endpoint returns rich metadata (context_window, reasoning levels,
1753
+ // visibility) that is more detailed than /v1/models.
1754
+ const cached = await _loadCodexModelCache();
1755
+ if (cached) {
1756
+ _lastCodexListModelsError = '';
1757
+ _inMemoryCodexCatalog = cached.slice();
1758
+ return cached;
1759
+ }
1760
+ try {
1761
+ const auth = await this.ensureAuth();
1762
+ const clientVersion = await _resolveCodexClientVersion();
1763
+ const url = `https://chatgpt.com/backend-api/codex/models?client_version=${clientVersion}`;
1764
+ const res = await fetch(url, {
1765
+ signal: AbortSignal.timeout(10_000),
1766
+ method: 'GET',
1767
+ headers: {
1768
+ 'Authorization': `Bearer ${auth.access_token}`,
1769
+ 'OpenAI-Beta': 'responses=experimental',
1770
+ 'originator': 'codex_cli_rs',
1771
+ 'chatgpt-account-id': auth.account_id || '',
1772
+ },
1773
+ dispatcher: getLlmDispatcher(),
1774
+ });
1775
+ if (!res.ok) throw new Error(`codex list_models ${res.status}`);
1776
+ const data = await res.json();
1777
+ const items = Array.isArray(data?.models) ? data.models : [];
1778
+ const normalized = items.map(m => _normalizeCodexModel(m));
1779
+ _markLatestCodex(normalized);
1780
+ const enriched = await enrichModels(normalized);
1781
+ await _saveCodexModelCache(enriched);
1782
+ _lastCodexListModelsError = '';
1783
+ return enriched;
1784
+ } catch (err) {
1785
+ _lastCodexListModelsError = err?.message || String(err);
1786
+ process.stderr.write(`[openai-oauth] listModels fetch failed (${_lastCodexListModelsError})\n`);
1787
+ // No fallback catalog — empty list signals the UI to show a
1788
+ // "catalog unavailable, retry" state. Codex has no equivalent to
1789
+ // Anthropic's family tokens so there's no meaningful minimal list.
1790
+ return [];
1791
+ }
1792
+ }
1793
+ // Force a catalog refresh (ignores 24h TTL). De-duped via
1794
+ // _codexRefreshInFlight so concurrent callers share one HTTP round-trip.
1795
+ async _refreshModelCache() {
1796
+ if (_codexRefreshInFlight) return _codexRefreshInFlight;
1797
+ _codexRefreshInFlight = (async () => {
1798
+ try {
1799
+ const auth = await this.ensureAuth();
1800
+ const clientVersion = await _resolveCodexClientVersion();
1801
+ const url = `https://chatgpt.com/backend-api/codex/models?client_version=${clientVersion}`;
1802
+ const res = await fetch(url, {
1803
+ signal: AbortSignal.timeout(10_000),
1804
+ method: 'GET',
1805
+ headers: {
1806
+ 'Authorization': `Bearer ${auth.access_token}`,
1807
+ 'OpenAI-Beta': 'responses=experimental',
1808
+ 'originator': 'codex_cli_rs',
1809
+ 'chatgpt-account-id': auth.account_id || '',
1810
+ },
1811
+ dispatcher: getLlmDispatcher(),
1812
+ });
1813
+ if (!res.ok) throw new Error(`codex list_models ${res.status}`);
1814
+ const data = await res.json();
1815
+ const items = Array.isArray(data?.models) ? data.models : [];
1816
+ const normalized = items.map(m => _normalizeCodexModel(m));
1817
+ _markLatestCodex(normalized);
1818
+ const enriched = await enrichModels(normalized);
1819
+ await _saveCodexModelCache(enriched);
1820
+ if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(`[openai-oauth] catalog refreshed (${enriched.length} models)\n`);
1821
+ return enriched;
1822
+ } catch (err) {
1823
+ if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) process.stderr.write(`[openai-oauth] catalog refresh failed (${err.message})\n`);
1824
+ return null;
1825
+ } finally {
1826
+ _codexRefreshInFlight = null;
1827
+ }
1828
+ })();
1829
+ return _codexRefreshInFlight;
1830
+ }
1831
+
1832
+ async isAvailable() {
1833
+ return this.tokens !== null;
1834
+ }
1835
+ }
1836
+
1837
+ const AUTHORIZE_URL = 'https://auth.openai.com/oauth/authorize';
1838
+ const CODEX_OAUTH_SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke';
1839
+ const CALLBACK_HOST = '127.0.0.1';
1840
+ const CALLBACK_PORT = 1455;
1841
+ const CALLBACK_PATH = '/auth/callback';
1842
+ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1843
+ const LOGIN_TIMEOUT_MS = 5 * 60_000;
1844
+ const TOKEN_TIMEOUT_MS = 30_000;
1845
+
1846
+ function generatePKCE() {
1847
+ const verifier = randomBytes(64).toString('base64url');
1848
+ const challenge = createHash('sha256').update(verifier).digest('base64url');
1849
+ return { verifier, challenge };
1850
+ }
1851
+
1852
+ function _scrubOAuthLoginBody(text) {
1853
+ return String(text || '')
1854
+ .replace(/"access_token"\s*:\s*"[^"]+"/g, '"access_token":"[REDACTED]"')
1855
+ .replace(/"refresh_token"\s*:\s*"[^"]+"/g, '"refresh_token":"[REDACTED]"')
1856
+ .replace(/"id_token"\s*:\s*"[^"]+"/g, '"id_token":"[REDACTED]"')
1857
+ .replace(/[A-Za-z0-9_-]{32,}\.[A-Za-z0-9._-]+/g, '[REDACTED]');
1858
+ }
1859
+
1860
+ export async function loginOAuth() {
1861
+ const pkce = generatePKCE();
1862
+ const state = randomBytes(16).toString('hex');
1863
+ const url = new URL(AUTHORIZE_URL);
1864
+ url.searchParams.set('response_type', 'code');
1865
+ url.searchParams.set('client_id', CLIENT_ID);
1866
+ url.searchParams.set('redirect_uri', REDIRECT_URI);
1867
+ url.searchParams.set('scope', CODEX_OAUTH_SCOPE);
1868
+ url.searchParams.set('code_challenge', pkce.challenge);
1869
+ url.searchParams.set('code_challenge_method', 'S256');
1870
+ url.searchParams.set('id_token_add_organizations', 'true');
1871
+ url.searchParams.set('codex_cli_simplified_flow', 'true');
1872
+ url.searchParams.set('state', state);
1873
+ url.searchParams.set('originator', CODEX_OAUTH_ORIGINATOR);
1874
+
1875
+ return new Promise((resolve, reject) => {
1876
+ let settled = false;
1877
+ let timeout = null;
1878
+ const settle = (value, error = null) => {
1879
+ if (settled) return;
1880
+ settled = true;
1881
+ if (timeout) clearTimeout(timeout);
1882
+ try { server.close(); } catch { /* already closed */ }
1883
+ if (error) reject(error);
1884
+ else resolve(value);
1885
+ };
1886
+ const server = createServer(async (req, res) => {
1887
+ const u = new URL(req.url || '/', `http://${CALLBACK_HOST}:${CALLBACK_PORT}`);
1888
+ if (u.pathname !== CALLBACK_PATH) {
1889
+ res.writeHead(404);
1890
+ res.end();
1891
+ return;
1892
+ }
1893
+ const code = u.searchParams.get('code');
1894
+ if (!code || u.searchParams.get('state') !== state) {
1895
+ res.writeHead(400);
1896
+ res.end('Invalid');
1897
+ settle(null);
1898
+ return;
1899
+ }
1900
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1901
+ res.end('<html><body><h2>Codex login successful! You can close this tab.</h2></body></html>');
1902
+ try {
1903
+ const tokenRes = await fetch(TOKEN_URL, {
1904
+ method: 'POST',
1905
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1906
+ body: new URLSearchParams({
1907
+ grant_type: 'authorization_code',
1908
+ code,
1909
+ redirect_uri: REDIRECT_URI,
1910
+ client_id: CLIENT_ID,
1911
+ code_verifier: pkce.verifier,
1912
+ }),
1913
+ redirect: 'error',
1914
+ signal: AbortSignal.timeout(TOKEN_TIMEOUT_MS),
1915
+ });
1916
+ if (!tokenRes.ok) {
1917
+ const text = await tokenRes.text().catch(() => '');
1918
+ settle(null, new Error(`[openai-oauth] token exchange ${tokenRes.status}: ${_scrubOAuthLoginBody(text).slice(0, 500)}`));
1919
+ return;
1920
+ }
1921
+ const json = await tokenRes.json();
1922
+ if (!json.access_token || !json.refresh_token) {
1923
+ settle(null, new Error('[openai-oauth] token exchange response missing access_token or refresh_token'));
1924
+ return;
1925
+ }
1926
+ const expiresAt = (typeof json.expires_in === 'number'
1927
+ ? Date.now() + json.expires_in * 1000
1928
+ : 0) || _expiryFromAccessToken(json.access_token);
1929
+ const tokens = {
1930
+ access_token: json.access_token,
1931
+ refresh_token: json.refresh_token,
1932
+ expires_at: expiresAt,
1933
+ account_id: extractAccountId(json.access_token),
1934
+ };
1935
+ saveTokens(tokens);
1936
+ settle(tokens);
1937
+ } catch (err) {
1938
+ settle(null, err instanceof Error ? err : new Error(String(err)));
1939
+ }
1940
+ });
1941
+ timeout = setTimeout(() => settle(null), LOGIN_TIMEOUT_MS);
1942
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, async () => {
1943
+ process.stderr.write(`\n[openai-oauth] Open this URL to log in to ChatGPT (Codex):\n${url.toString()}\n\n`);
1944
+ try {
1945
+ const { openInBrowser } = await import('../../../shared/open-url.mjs');
1946
+ openInBrowser(url.toString());
1947
+ } catch (err) {
1948
+ process.stderr.write(`[openai-oauth] browser open failed: ${String(err?.message || err).slice(0, 200)}\n`);
1949
+ }
1950
+ });
1951
+ server.on('error', (err) => settle(null, new Error(`[openai-oauth] callback server failed on ${CALLBACK_HOST}:${CALLBACK_PORT}: ${err?.message || err}`)));
1952
+ });
1953
+ }