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
@@ -1,1881 +0,0 @@
1
- /**
2
- * Anthropic OAuth provider — uses Claude Code's OAuth credentials
3
- * (~/.claude/.credentials.json) for Claude Max subscription access.
4
- *
5
- * Raw HTTP + SSE streaming, reuses message/tool conversion patterns
6
- * from anthropic.mjs. Bridge-trace instrumented.
7
- */
8
- import { readFileSync, existsSync, statSync } from 'fs';
9
- import { join } from 'path';
10
- import { homedir } from 'os';
11
- import { createServer } from 'http';
12
- import { randomBytes, createHash } from 'crypto';
13
- import {
14
- traceBridgeFetch,
15
- traceBridgeSse,
16
- traceBridgeUsage,
17
- } from '../bridge-trace.mjs';
18
- import { createAbortController } from '../../../shared/abort-controller.mjs';
19
- import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
20
- import { getPluginData } from '../config.mjs';
21
- import { enrichModels } from './model-catalog.mjs';
22
- import { sanitizeToolPairs, sanitizeAnthropicContentPairs } from '../session/trim.mjs';
23
- import {
24
- PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
25
- PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
26
- PROVIDER_RETRY_BACKOFF_MS,
27
- PROVIDER_RETRY_MAX_ATTEMPTS,
28
- PROVIDER_SSE_IDLE_TIMEOUT_MS,
29
- PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
30
- createTimeoutSignal,
31
- } from '../stall-policy.mjs';
32
- import {
33
- classifyError,
34
- retryAfterMsFromError,
35
- withRetry,
36
- } from './retry-classifier.mjs';
37
- import { buildAnthropicBetaHeaders, supportsAnthropicFastMode } from './anthropic-betas.mjs';
38
- import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
39
-
40
- // --- Model catalog cache helpers ---
41
- // Disk-backed cache so repeated process starts (cron, tool calls) don't
42
- // hammer /v1/models. 24h TTL is the same cadence Claude Code itself uses
43
- // for its internal model discovery.
44
- const MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
45
- // SSE progress emits (per-request "Response …" and "Done:" lines). Off by default.
46
- const SSE_VERBOSE = process.env.MIXDOG_SSE_VERBOSE === '1';
47
-
48
- function _modelCachePath() {
49
- return join(getPluginData(), 'anthropic-oauth-models.json');
50
- }
51
-
52
- async function _loadModelCache() {
53
- const path = _modelCachePath();
54
- if (!existsSync(path)) return null;
55
- try {
56
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
57
- if (!raw?.fetchedAt || !Array.isArray(raw.models)) return null;
58
- if (Date.now() - raw.fetchedAt > MODEL_CACHE_TTL_MS) return null;
59
- return raw.models;
60
- } catch { return null; }
61
- }
62
-
63
- async function _saveModelCache(models) {
64
- try {
65
- writeJsonAtomicSync(_modelCachePath(), {
66
- fetchedAt: Date.now(),
67
- models,
68
- }, { lock: true, fsyncDir: true });
69
- _inMemoryCatalog = Array.isArray(models) ? models.slice() : null;
70
- } catch { /* cache is best-effort */ }
71
- }
72
-
73
- // In-memory mirror of the disk catalog — populated on first listModels() and
74
- // refreshed after every _saveModelCache. Used by _catalogHas and _displayModel
75
- // so hot paths don't hit disk on every response.
76
- let _inMemoryCatalog = null;
77
- let _modelRefreshInFlight = null;
78
- let _oauthRefreshInFlight = null;
79
- // No in-memory credential cache: the canonical credentials file is the
80
- // single source of truth. Cross-process refresh_token rotation by host
81
- // Claude Code (or another concurrent reader) would invalidate any cached
82
- // copy here and produce invalid_grant on the next refresh. Reading from
83
- // disk on demand is cheap (one stat + one small JSON parse) and removes
84
- // the cache-vs-disk skew entirely.
85
-
86
-
87
- function _catalogHas(id) {
88
- if (!id || !Array.isArray(_inMemoryCatalog)) return false;
89
- return _inMemoryCatalog.some(m => m.id === id);
90
- }
91
-
92
- // Display-name normalization for trace / usage. Turns dated or version-alias
93
- // ids into the version alias form: claude-opus-4-7 → claude-opus-4.7,
94
- // claude-haiku-4-5-20251001 → claude-haiku-4.5. Falls back to the raw id.
95
- function _displayModel(id) {
96
- if (!id || typeof id !== 'string') return id;
97
- const m = id.match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)(?:-\d{8})?$/i);
98
- if (!m) return id;
99
- return `claude-${m[1].toLowerCase()}-${m[2]}.${m[3]}`;
100
- }
101
-
102
- // Classify a model id into our common tier/family shape. Anthropic's catalog
103
- // mixes dated ids (claude-opus-4-5-20251101), versioned aliases
104
- // (claude-opus-4-6), and the raw family tokens resolved via env vars.
105
- function _normalizeAnthropicModel(raw) {
106
- const id = raw?.id || raw?.name;
107
- if (!id) return null;
108
- const familyMatch = id.match(/^claude-(opus|sonnet|haiku)/i);
109
- const family = familyMatch ? familyMatch[1].toLowerCase() : 'other';
110
- // Dated: trailing -YYYYMMDD (8 digits).
111
- const dated = /-\d{8}$/.test(id);
112
- // Versioned alias: claude-<family>-<major>-<minor>[-...] with no dated suffix.
113
- const versioned = !dated && /-\d+-\d+/.test(id);
114
- const tier = dated ? 'dated' : versioned ? 'version' : 'family';
115
- const releaseDate = dated
116
- ? id.match(/-(\d{4})(\d{2})(\d{2})$/)
117
- : null;
118
- return {
119
- id,
120
- display: raw?.display_name || _prettyName(id, family),
121
- family,
122
- provider: 'anthropic-oauth',
123
- contextWindow: raw?.context_window || raw?.max_context_window || _defaultContextForModel(id, family),
124
- tier,
125
- latest: false, // assigned in a second pass once full list is known
126
- releaseDate: releaseDate ? `${releaseDate[1]}-${releaseDate[2]}-${releaseDate[3]}` : null,
127
- };
128
- }
129
-
130
- function _prettyName(id, family) {
131
- const v = id.match(/-(\d+)-(\d+)/);
132
- const base = family[0].toUpperCase() + family.slice(1);
133
- return v ? `${base} ${v[1]}.${v[2]}` : base;
134
- }
135
-
136
- function _defaultContextForModel(id, family) {
137
- if (/^claude-(opus|sonnet)-4-(6|7|8)(?:$|-)/i.test(String(id || ''))) return 1000000;
138
- if (family === 'opus') return 200000;
139
- if (family === 'sonnet') return 200000;
140
- if (family === 'haiku') return 200000;
141
- return 200000;
142
- }
143
-
144
- // Mark the highest-numbered version per family as `latest: true`. Uses a simple
145
- // lexicographic comparison on the numeric parts embedded in the id.
146
- function _markLatestByFamily(models) {
147
- const byFamily = new Map();
148
- for (const m of models) {
149
- if (m.tier !== 'version') continue;
150
- const cur = byFamily.get(m.family);
151
- if (!cur || _compareVersion(m.id, cur.id) > 0) {
152
- byFamily.set(m.family, m);
153
- }
154
- }
155
- for (const m of byFamily.values()) m.latest = true;
156
- }
157
-
158
- function _compareVersion(a, b) {
159
- const na = (a.match(/-(\d+)-(\d+)/) || []).slice(1).map(Number);
160
- const nb = (b.match(/-(\d+)-(\d+)/) || []).slice(1).map(Number);
161
- for (let i = 0; i < Math.max(na.length, nb.length); i++) {
162
- if ((na[i] || 0) !== (nb[i] || 0)) return (na[i] || 0) - (nb[i] || 0);
163
- }
164
- return a.localeCompare(b);
165
- }
166
-
167
- // Newest HIGH-TIER chat model by version, read from the SYNC in-memory catalog
168
- // mirror. Symmetric with resolveLatestGrokModel / resolveLatestCodexModel.
169
- // Anthropic ships three families: opus / sonnet / haiku. "Latest" is the
170
- // highest version across opus + sonnet only — haiku is the cheap tier and is
171
- // never the flagship default. Returns null until listModels() populates the
172
- // mirror; callers must warm the catalog (ensureLatestAnthropicModel) when null.
173
- export function resolveLatestAnthropicModel() {
174
- if (!Array.isArray(_inMemoryCatalog)) return null;
175
- let best = null;
176
- for (const m of _inMemoryCatalog) {
177
- if (!m?.id || (m.family !== 'opus' && m.family !== 'sonnet')) continue;
178
- if (!best || _compareVersion(m.id, best.id) > 0) best = m;
179
- }
180
- return best?.id || null;
181
- }
182
-
183
- export async function ensureLatestAnthropicModel(provider) {
184
- let m = resolveLatestAnthropicModel();
185
- if (m) return m;
186
- await provider._refreshModelCache();
187
- m = resolveLatestAnthropicModel();
188
- if (m) return m;
189
- throw new Error('[anthropic-oauth] model catalog unavailable after warmup — cannot resolve default model');
190
- }
191
-
192
- const API_URL = 'https://api.anthropic.com/v1/messages';
193
- // SSRF guard for the OAuth token endpoint override. Env-supplied URLs must be
194
- // https with a valid http(s) URL shape; reject file:/data:/ftp:/etc. and any
195
- // http override so a hostile env cannot redirect refresh-token requests.
196
- function assertSafeTokenURL(rawURL) {
197
- let parsed;
198
- try {
199
- parsed = new URL(String(rawURL));
200
- } catch {
201
- throw new Error(`[anthropic-oauth] invalid ANTHROPIC_OAUTH_TOKEN_URL: ${rawURL}`);
202
- }
203
- if (parsed.protocol.toLowerCase() !== 'https:') {
204
- throw new Error(`[anthropic-oauth] ANTHROPIC_OAUTH_TOKEN_URL must use https (got ${parsed.protocol})`);
205
- }
206
- return rawURL;
207
- }
208
- const TOKEN_URL = assertSafeTokenURL(process.env.ANTHROPIC_OAUTH_TOKEN_URL || 'https://console.anthropic.com/v1/oauth/token');
209
- const ANTHROPIC_VERSION = '2023-06-01';
210
- const DEFAULT_CREDENTIALS_PATH = join(homedir(), '.claude', '.credentials.json');
211
- const CLAUDE_CODE_CLIENT_ID = process.env.ANTHROPIC_OAUTH_CLIENT_ID || '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
212
- const TOKEN_REFRESH_SKEW_MS = 5 * 60_000;
213
- const CLAUDE_AI_AUTHORIZE_URL = 'https://claude.com/cai/oauth/authorize';
214
- const ALL_OAUTH_SCOPES = [
215
- 'org:create_api_key',
216
- 'user:profile',
217
- 'user:inference',
218
- 'user:sessions:claude_code',
219
- 'user:mcp_servers',
220
- 'user:file_upload',
221
- ];
222
- const OAUTH_LOGIN_SCOPE = ALL_OAUTH_SCOPES.join(' ');
223
- const OAUTH_CALLBACK_HOST = 'localhost';
224
- const OAUTH_CALLBACK_PORT = 54545;
225
- const OAUTH_CALLBACK_PATH = '/callback';
226
- const OAUTH_REDIRECT_URI = `http://${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`;
227
- const OAUTH_LOGIN_TIMEOUT_MS = 5 * 60_000;
228
- const OAUTH_TOKEN_TIMEOUT_MS = 30_000;
229
-
230
- // Anthropic OAuth contract for first-party Claude Code clients.
231
- // Opus/Sonnet requests are gated on a specific system-prompt prefix.
232
- // Our plugin ONLY runs inside Claude Code (marketplace-distributed),
233
- // so declaring ourselves as Claude Code is literally accurate — not
234
- // impersonation. Haiku is not gated and ignores this prefix.
235
- const CLAUDE_CODE_SYSTEM_PREFIX = "You are Claude Code, Anthropic's official CLI for Claude.";
236
- const OAUTH_BETA_HEADERS = 'oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,extended-cache-ttl-2025-04-11';
237
- const DEFAULT_CLI_VERSION = '2.1.77';
238
-
239
- function resolveCliVersion() {
240
- // Claude Code sets CLAUDE_CODE_VERSION in the plugin subprocess env.
241
- // Fallback exists so unit tests and older Claude Code versions still work.
242
- return process.env.CLAUDE_CODE_VERSION
243
- || process.env.CLAUDE_CODE_EXECPATH_VERSION
244
- || DEFAULT_CLI_VERSION;
245
- }
246
-
247
- function requiresSystemPrefix(model) {
248
- // Opus / Sonnet require the Claude Code system prefix when authenticated
249
- // via OAuth. Haiku does not.
250
- return /^claude-(opus|sonnet)/i.test(String(model || ''));
251
- }
252
-
253
- // OAuth rate-limit pool routing is gated by the server inspecting the first
254
- // system block. When it reads exactly "You are Claude Code, Anthropic's
255
- // official CLI for Claude." it routes into the Claude Code pool; any other
256
- // content (even the prefix concatenated with extra text in the same block)
257
- // falls into the standard pool and Opus/Sonnet return 429. Splitting into
258
- // two blocks — [prefix, rest] — keeps both routing and user instructions.
259
- function buildSystemBlocks(systemText, model, cacheControl) {
260
- // systemText is an array of strings — each element becomes its own Anthropic
261
- // content block with its own cache_control breakpoint (BP1 + BP2).
262
- // Invariant: callers must pass an array; scalar strings are not accepted.
263
- const texts = Array.isArray(systemText)
264
- ? systemText.map(s => typeof s === 'string' ? s.trim() : '').filter(Boolean)
265
- : [];
266
- const gated = requiresSystemPrefix(model);
267
-
268
- const blocks = [];
269
- if (gated) {
270
- blocks.push({ type: 'text', text: CLAUDE_CODE_SYSTEM_PREFIX });
271
- }
272
- for (let i = 0; i < texts.length; i++) {
273
- let body = texts[i];
274
- // Strip a duplicated Claude Code prefix from the first block if present.
275
- if (gated && i === 0 && body.startsWith(CLAUDE_CODE_SYSTEM_PREFIX)) {
276
- body = body.slice(CLAUDE_CODE_SYSTEM_PREFIX.length).trim();
277
- if (!body) continue;
278
- }
279
- const block = { type: 'text', text: body };
280
- if (cacheControl) block.cache_control = cacheControl;
281
- blocks.push(block);
282
- }
283
- return blocks;
284
- }
285
-
286
- const MODELS = [
287
- { id: 'claude-opus-4-8', name: 'Claude Opus 4.8', provider: 'anthropic-oauth', contextWindow: 1000000 },
288
- { id: 'claude-opus-4-7', name: 'Claude Opus 4.7', provider: 'anthropic-oauth', contextWindow: 1000000 },
289
- { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'anthropic-oauth', contextWindow: 1000000 },
290
- { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'anthropic-oauth', contextWindow: 1000000 },
291
- { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', provider: 'anthropic-oauth', contextWindow: 200000 },
292
- ];
293
-
294
- // Per-model max_tokens when the model id is explicitly listed. New models
295
- // (e.g., Sonnet 4.7) won't match a specific entry and fall through to the
296
- // family-based heuristic below. Conservative defaults — model may support
297
- // more but we'd rather stay within safe bounds.
298
- const MAX_TOKENS = {
299
- 'claude-opus-4-8': 65536,
300
- 'claude-opus-4-7': 65536,
301
- 'claude-opus-4-6': 65536,
302
- 'claude-sonnet-4-6': 16384,
303
- 'claude-haiku-4-5-20251001': 8192,
304
- };
305
-
306
- function resolveMaxTokens(model) {
307
- if (MAX_TOKENS[model]) return MAX_TOKENS[model];
308
- const id = String(model || '').toLowerCase();
309
- if (id.includes('opus')) return 65536;
310
- if (id.includes('sonnet')) return 16384;
311
- if (id.includes('haiku')) return 8192;
312
- return 8192;
313
- }
314
-
315
- const EFFORT_BUDGET = {
316
- low: 1024,
317
- medium: 4096,
318
- high: 16384,
319
- xhigh: 32768,
320
- max: 32768,
321
- };
322
-
323
- // Tracks which unknown effort labels we've already logged so a repeated
324
- // session-level misconfig doesn't flood stderr with the same warning.
325
- const _LOGGED_UNKNOWN_EFFORT = new Set();
326
-
327
- // Layered cache TTLs — stable layers get 1h, volatile layers get 5m.
328
- // Anthropic requires 1h entries to appear before 5m entries in the request.
329
- const CACHE_TTL_STABLE = { type: 'ephemeral', ttl: '1h' }; // tools, system
330
- const CACHE_TTL_VOLATILE = { type: 'ephemeral' }; // messages (5m default)
331
-
332
- // --- Credential helpers ---
333
-
334
- function _pushUnique(list, value) {
335
- if (!value || typeof value !== 'string') return;
336
- if (!list.includes(value)) list.push(value);
337
- }
338
-
339
- function _claudeCredentialsFromPluginRoot(root) {
340
- const clean = String(root || '').replace(/\\/g, '/');
341
- const marker = '/.claude/plugins/';
342
- const idx = clean.indexOf(marker);
343
- if (idx < 0) return null;
344
- return `${clean.slice(0, idx)}/.claude/.credentials.json`;
345
- }
346
-
347
- function credentialCandidates() {
348
- const paths = [];
349
- _pushUnique(paths, process.env.CLAUDE_CODE_CREDENTIALS_PATH);
350
- _pushUnique(paths, process.env.CLAUDE_CREDENTIALS_PATH);
351
- _pushUnique(paths, _claudeCredentialsFromPluginRoot(process.env.CLAUDE_PLUGIN_ROOT));
352
- _pushUnique(paths, DEFAULT_CREDENTIALS_PATH);
353
- return paths;
354
- }
355
-
356
- // Fallback expiry from the access_token's JWT `exp` claim (epoch ms) when the
357
- // credentials file carries no explicit expiresAt — without it expiresAt stays 0,
358
- // which ensureAuth reads as "never expires", disabling proactive refresh. Claude
359
- // OAuth tokens are opaque so this returns 0 and the file's expiresAt governs; kept
360
- // for parity with the other OAuth providers. JWT `exp` is epoch SECONDS (RFC 7519).
361
- function _expiryFromAccessToken(token) {
362
- try {
363
- const parts = String(token || '').split('.');
364
- if (parts.length !== 3) return 0;
365
- const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));
366
- const exp = Number(payload?.exp);
367
- return Number.isFinite(exp) && exp > 0 ? exp * 1000 : 0;
368
- } catch { return 0; }
369
- }
370
-
371
- function _loadCredentialsFile(path) {
372
- if (!existsSync(path)) return null;
373
- try {
374
- const stat = statSync(path);
375
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
376
- const oauth = raw?.claudeAiOauth;
377
- if (!oauth?.accessToken) return null;
378
- return {
379
- path,
380
- mtimeMs: stat.mtimeMs,
381
- accessToken: oauth.accessToken,
382
- refreshToken: oauth.refreshToken || null,
383
- expiresAt: _normalizeExpiresAt(oauth.expiresAt ?? oauth.expires_at) || _expiryFromAccessToken(oauth.accessToken),
384
- scopes: Array.isArray(oauth.scopes) ? oauth.scopes : [],
385
- subscriptionType: oauth.subscriptionType || null,
386
- };
387
- } catch {
388
- return null;
389
- }
390
- }
391
-
392
- // Cross-process safe write-back. Lockfile (O_EXCL) prevents two refreshers
393
- // from clobbering each other; atomic rename guarantees readers see either
394
- // the old or new file, never a half-written one. Used so refresh_token
395
- // rotation propagates to host Claude Code (and any other reader of the
396
- // same credentials file) instead of leaving them stuck on the previous
397
- // refresh_token. Mirrors openai-oauth.mjs:saveTokens.
398
- function _saveCredentialsFile(path, raw) {
399
- // No `secret: true`: this is the HOST-owned credentials file (~/.claude/
400
- // .credentials.json) — mixdog only writes back the rotated refresh_token,
401
- // it must not re-permission a file Claude Code owns. (Forcing an owner-
402
- // only ACL here also used to clamp the parent ~/.claude and wipe the
403
- // whole tree's DACLs — see atomic-file.mjs secret-write note.)
404
- writeJsonAtomicSync(path, raw, { lock: true, fsyncDir: true, mode: 0o600 });
405
- }
406
-
407
- // Cheap stat-only probe so ensureAuth can detect host-rotated credentials
408
- // (claude login, logout/relogin) without paying a full JSON read every call.
409
- function _credentialsMaxMtime() {
410
- let max = 0;
411
- for (const p of credentialCandidates()) {
412
- try {
413
- const s = statSync(p);
414
- if (s.mtimeMs > max) max = s.mtimeMs;
415
- } catch { /* not present — skip */ }
416
- }
417
- return max;
418
- }
419
-
420
- function loadCredentials() {
421
- const loaded = credentialCandidates()
422
- .map(_loadCredentialsFile)
423
- .filter(Boolean);
424
- if (!loaded.length) return null;
425
- loaded.sort((a, b) => (Number(b.expiresAt) || 0) - (Number(a.expiresAt) || 0));
426
- return loaded[0];
427
- }
428
-
429
- // Public predicate used by config.buildDefaultConfig — provider is enabled
430
- // when on-disk credentials exist AND carry the inference scope. Single
431
- // truth: same loader the runtime uses, no parallel hard-coded path probe.
432
- export function hasAnthropicOAuthCredentials() {
433
- const creds = loadCredentials();
434
- if (!creds?.accessToken) return false;
435
- return Array.isArray(creds.scopes) && creds.scopes.includes('user:inference');
436
- }
437
-
438
- function _normalizeExpiresAt(value) {
439
- if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return 0;
440
- return value < 1e12 ? value * 1000 : value;
441
- }
442
-
443
- function _scrubTokens(text) {
444
- return String(text || '')
445
- .replace(/Bearer [A-Za-z0-9._\-]+/g, 'Bearer [REDACTED]')
446
- .replace(/sk-ant-[A-Za-z0-9._\-]+/g, '[REDACTED]')
447
- .replace(/"access[Tt]oken"\s*:\s*"[^"]+"/g, '"accessToken":"[REDACTED]"')
448
- .replace(/"refresh[Tt]oken"\s*:\s*"[^"]+"/g, '"refreshToken":"[REDACTED]"')
449
- .replace(/"access_token"\s*:\s*"[^"]+"/g, '"access_token":"[REDACTED]"')
450
- .replace(/"refresh_token"\s*:\s*"[^"]+"/g, '"refresh_token":"[REDACTED]"');
451
- }
452
-
453
- async function refreshOAuthCredentials(creds) {
454
- if (!creds?.refreshToken) {
455
- throw new Error('Anthropic OAuth refresh token not available. Run "claude login" to re-authenticate.');
456
- }
457
-
458
- const controller = new AbortController();
459
- const timeout = setTimeout(() => controller.abort(), 30_000);
460
- try {
461
- const res = await fetch(TOKEN_URL, {
462
- method: 'POST',
463
- headers: {
464
- 'Content-Type': 'application/json',
465
- 'anthropic-dangerous-direct-browser-access': 'true',
466
- 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
467
- },
468
- body: JSON.stringify({
469
- grant_type: 'refresh_token',
470
- refresh_token: creds.refreshToken,
471
- client_id: CLAUDE_CODE_CLIENT_ID,
472
- }),
473
- // Never follow a redirect on a secret-bearing request: a token
474
- // endpoint that 307/308-redirects would replay the refresh_token to
475
- // the redirect target. Fail loud instead.
476
- redirect: 'error',
477
- signal: controller.signal,
478
- dispatcher: getLlmDispatcher(),
479
- });
480
-
481
- const text = await res.text();
482
- let json = null;
483
- try { json = text ? JSON.parse(text) : null; } catch { /* handled below */ }
484
- if (!res.ok) {
485
- const isInvalidGrant = text.includes('invalid_grant') || json?.error === 'invalid_grant';
486
- throw Object.assign(new Error(`token refresh ${res.status}: ${_scrubTokens(text).slice(0, 200)}`), { isInvalidGrant });
487
- }
488
-
489
- const accessToken = json?.access_token || json?.accessToken;
490
- if (!accessToken) throw new Error('token refresh returned no access token');
491
- const expiresAt = _normalizeExpiresAt(json?.expires_at ?? json?.expiresAt)
492
- || (typeof json?.expires_in === 'number' ? Date.now() + json.expires_in * 1000 : 0);
493
- const refreshed = {
494
- path: creds.path,
495
- accessToken,
496
- refreshToken: json?.refresh_token || json?.refreshToken || creds.refreshToken,
497
- expiresAt,
498
- scopes: Array.isArray(json?.scope) ? json.scope : creds.scopes,
499
- subscriptionType: creds.subscriptionType,
500
- };
501
- // Persist rotated tokens back so host Claude Code and any other
502
- // reader of the same credentials file pick up the new refresh_token.
503
- // Without this, host's next refresh invalidates our copy and we
504
- // loop on invalid_grant.
505
- if (creds.path && existsSync(creds.path)) {
506
- try {
507
- const raw = JSON.parse(readFileSync(creds.path, 'utf-8'));
508
- raw.claudeAiOauth = {
509
- ...(raw.claudeAiOauth || {}),
510
- accessToken: refreshed.accessToken,
511
- refreshToken: refreshed.refreshToken,
512
- expiresAt: refreshed.expiresAt,
513
- scopes: refreshed.scopes,
514
- };
515
- _saveCredentialsFile(creds.path, raw);
516
- } catch (err) {
517
- process.stderr.write(`[anthropic-oauth] credential write-back failed: ${_scrubTokens(err?.message || String(err)).slice(0, 200)}\n`);
518
- throw new Error(`[oauth] credentials write-back failed: ${err?.message ?? String(err)}`);
519
- }
520
- }
521
- return refreshed;
522
- } catch (err) {
523
- if (err?.name === 'AbortError') {
524
- throw new Error('Anthropic OAuth token refresh timed out after 30000ms');
525
- }
526
- throw err;
527
- } finally {
528
- clearTimeout(timeout);
529
- }
530
- }
531
-
532
- async function refreshOAuthCredentialsWithFallback(creds) {
533
- try {
534
- return await refreshOAuthCredentials(creds);
535
- } catch (firstErr) {
536
- if (!firstErr.isInvalidGrant) throw firstErr;
537
- // invalid_grant: another writer rotated the refresh_token between
538
- // our read and refresh. Re-read disk to pick up the rotation and
539
- // retry once. If the on-disk creds still match what we just failed
540
- // with, the user must re-auth via host Claude Code.
541
- process.stderr.write(`[anthropic-oauth] invalid_grant — re-reading disk, retrying refresh\n`);
542
- const fresh = loadCredentials();
543
- if (!fresh?.refreshToken || fresh.refreshToken === creds.refreshToken) throw new ReauthRequired(firstErr.message);
544
- try {
545
- return await refreshOAuthCredentials(fresh);
546
- } catch (secondErr) {
547
- if (secondErr.isInvalidGrant) throw new ReauthRequired(secondErr.message);
548
- throw secondErr;
549
- }
550
- }
551
- }
552
-
553
- // Exported so callers can detect re-auth-required scenarios and prompt the user.
554
- export class ReauthRequired extends Error {
555
- constructor(message) {
556
- super(message);
557
- this.name = 'ReauthRequired';
558
- }
559
- }
560
-
561
- // --- Message conversion (mirrors anthropic.mjs) ---
562
-
563
- function withCacheControl(block, ttl = CACHE_TTL_VOLATILE) {
564
- if (!block || typeof block !== 'object' || block.cache_control) return block;
565
- return { ...block, cache_control: ttl };
566
- }
567
-
568
- function appendCacheControl(content, ttl = CACHE_TTL_VOLATILE) {
569
- if (Array.isArray(content)) {
570
- if (content.length === 0) return content;
571
- const next = [...content];
572
- next[next.length - 1] = withCacheControl(next[next.length - 1], ttl);
573
- return next;
574
- }
575
- if (typeof content === 'string') {
576
- return [withCacheControl({ type: 'text', text: content }, ttl)];
577
- }
578
- return content;
579
- }
580
-
581
- function collectRecentCacheableIndexes(messages, availableSlots = 2) {
582
- // Anthropic enforces a 4-breakpoint max per request. Callers reserve slots
583
- // for tools[-1] and system breakpoints (typically 2); whatever remains is
584
- // spread across messages as 5m breakpoints.
585
- //
586
- // Anchor strategy when only ONE message slot is available — pin the
587
- // single marker to the FIRST chat message (typically the locked task
588
- // brief) instead of the sliding tail. Reason: a tail marker shifts
589
- // position every iter (messages.length grows as tool turns accumulate),
590
- // and Anthropic caches by prefix-bytes-up-to-marker, so a moving tail
591
- // creates a NEW prefix every iter — which means cache_creation fires
592
- // every loop on first-time-seen prefixes (no prior 1h slot warmed up,
593
- // 1h indexing latency blocks intra-call read). Pinning the marker to
594
- // a stable position keeps the prefix bytes identical across iters so
595
- // 5m cache can read on the second iter onward, dramatically cutting
596
- // first-call cost when the loop runs N>1 turns.
597
- //
598
- // Multi-slot path (slots>=2) still uses the sliding tail for the
599
- // remaining slots so the most-recent message also gets cached for the
600
- // benefit of cross-call hits within the 5m window.
601
- const slots = Math.max(0, Math.min(4, availableSlots));
602
- if (slots === 0) return new Set();
603
- const marked = new Set();
604
- let firstChat = -1;
605
- for (let i = 0; i < messages.length; i++) {
606
- if (messages[i]?.role !== 'system') { firstChat = i; break; }
607
- }
608
- if (firstChat < 0) return marked;
609
- marked.add(firstChat);
610
- if (slots === 1) return marked;
611
- for (let i = messages.length - 1; i >= 0 && marked.size < slots; i--) {
612
- if (messages[i]?.role !== 'system') marked.add(i);
613
- }
614
- return marked;
615
- }
616
-
617
- // Anthropic's tool spec forbids oneOf / allOf / anyOf at the TOP level of
618
- // input_schema (nested usage inside properties is allowed). External MCP
619
- // servers (e.g. Claude Code's built-in tools) sometimes emit such schemas.
620
- // Convert them to a flat object schema so the API never sees a 400.
621
- function _sanitizeInputSchema(schema, toolName) {
622
- if (!schema || typeof schema !== 'object') {
623
- return { type: 'object', properties: {} };
624
- }
625
- const compound = schema.oneOf || schema.anyOf || schema.allOf;
626
- if (!compound) return structuredClone(schema);
627
- // Merge all branch properties into one permissive object schema.
628
- // None of the branches' required lists are hoisted — callers that relied
629
- // on discriminated-union semantics will still function; the model simply
630
- // receives a union of the property surface with no hard-required constraint.
631
- const mergedProps = {};
632
- const branchDescs = [];
633
- for (const branch of Array.isArray(compound) ? compound : []) {
634
- if (branch && typeof branch === 'object' && branch.properties) {
635
- Object.assign(mergedProps, branch.properties);
636
- }
637
- if (branch && typeof branch === 'object') {
638
- const parts = [];
639
- if (branch.description) parts.push(branch.description);
640
- else if (branch.type) parts.push(`type:${branch.type}`);
641
- if (parts.length) branchDescs.push(parts.join(' '));
642
- }
643
- }
644
- const compoundKey = schema.oneOf ? 'oneOf' : schema.anyOf ? 'anyOf' : 'allOf';
645
- let description = schema.description || '';
646
- if (branchDescs.length) {
647
- const parts = [];
648
- let used = 0;
649
- for (let i = 0; i < branchDescs.length; i++) {
650
- const v = `(variant ${i + 1}: ${branchDescs[i]})`;
651
- if (used + v.length + (parts.length ? 1 : 0) > 500) break;
652
- parts.push(v);
653
- used += v.length + (parts.length > 1 ? 1 : 0);
654
- }
655
- const addition = parts.join(' ');
656
- if (addition) description = description ? `${description} ${addition}` : addition;
657
- }
658
- const mergedPropsCount = Object.keys(mergedProps).length;
659
- process.stderr.write(
660
- `[anthropic-oauth-sanitizer] tool="${toolName ?? ''}" compound="${compoundKey}" branches=${Array.isArray(compound) ? compound.length : 0} mergedProps=${mergedPropsCount}\n`
661
- );
662
- return {
663
- type: 'object',
664
- ...(description ? { description } : {}),
665
- properties: mergedProps,
666
- };
667
- }
668
-
669
- function toAnthropicTools(tools) {
670
- return tools.map(t => ({
671
- name: t.name,
672
- description: t.description,
673
- input_schema: _sanitizeInputSchema(t.inputSchema, t.name),
674
- }));
675
- }
676
-
677
- function toAnthropicMessages(
678
- messages,
679
- cacheableIndexes = new Set(),
680
- messageTtl = CACHE_TTL_VOLATILE,
681
- tier3Idx = -1,
682
- tier3Ttl = null,
683
- ) {
684
- // messageTtl === null disables message-tail caching.
685
- // tier3Ttl === null disables the dedicated Tier 3 breakpoint.
686
- const applyMsgTtl = messageTtl || CACHE_TTL_VOLATILE;
687
- const shouldCacheMsg = (idx) => messageTtl !== null && cacheableIndexes.has(idx);
688
- const shouldCacheTier3 = (idx) => tier3Ttl !== null && idx === tier3Idx;
689
- const pickTtl = (idx) => shouldCacheTier3(idx) ? tier3Ttl : applyMsgTtl;
690
- const anyCache = (idx) => shouldCacheMsg(idx) || shouldCacheTier3(idx);
691
-
692
- const result = [];
693
- for (let idx = 0; idx < messages.length; idx++) {
694
- const m = messages[idx];
695
- if (m.role === 'system') continue;
696
-
697
- if (m.role === 'assistant' && m.toolCalls?.length) {
698
- let content = [];
699
- if (m.content) content.push({ type: 'text', text: m.content });
700
- for (const tc of m.toolCalls) {
701
- content.push({
702
- type: 'tool_use',
703
- id: tc.id,
704
- name: tc.name,
705
- input: tc.arguments,
706
- });
707
- }
708
- if (anyCache(idx)) content = appendCacheControl(content, pickTtl(idx));
709
- result.push({ role: 'assistant', content });
710
- continue;
711
- }
712
-
713
- if (m.role === 'tool') {
714
- const last = result[result.length - 1];
715
- const block = {
716
- type: 'tool_result',
717
- tool_use_id: m.toolCallId || '',
718
- content: m.content,
719
- };
720
- if (last?.role === 'user' && Array.isArray(last.content)) {
721
- last.content.push(block);
722
- if (anyCache(idx)) {
723
- last.content = appendCacheControl(last.content, pickTtl(idx));
724
- }
725
- } else {
726
- let content = [block];
727
- if (anyCache(idx)) content = appendCacheControl(content, pickTtl(idx));
728
- result.push({ role: 'user', content });
729
- }
730
- continue;
731
- }
732
-
733
- const content = anyCache(idx)
734
- ? appendCacheControl(m.content, pickTtl(idx))
735
- : m.content;
736
- result.push({ role: m.role, content });
737
- }
738
- return sanitizeAnthropicContentPairs(result);
739
- }
740
-
741
- // --- SSE parser ---
742
-
743
- function _captureMidstreamAbort(state, reason) {
744
- if (!state) return;
745
- const reasonName = reason?.name || '';
746
- if (reasonName === 'BridgeStallAbortError' || reasonName === 'StreamStalledAbortError') {
747
- state.watchdogAbort = reasonName;
748
- } else {
749
- state.userAbort = true;
750
- }
751
- }
752
-
753
- async function parseSSEStream(response, signal, abortStream, onStreamDelta, onToolCall, state) {
754
- const reader = response.body.getReader();
755
- const decoder = new TextDecoder();
756
- const SSE_IDLE_TIMEOUT_MS = PROVIDER_SSE_IDLE_TIMEOUT_MS;
757
- let content = '';
758
- let hasThinkingContent = false;
759
- const contentBlockTypes = new Set();
760
- let model = '';
761
- let toolCalls = [];
762
- let usage = { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, raw: null };
763
- let stopReason = null;
764
- let buffer = '';
765
- let idleTimedOut = false;
766
- let idleTimer = null;
767
- let currentEvent = '';
768
-
769
- const pendingToolInputs = new Map();
770
-
771
- // Holds the in-flight reader.read() race rejector so the idle timer can
772
- // force-unblock the loop even when reader.cancel() fails to settle the
773
- // pending read (undici half-open socket). See resetIdleTimer below.
774
- let idleReject = null;
775
-
776
- const resetIdleTimer = () => {
777
- // OFF by default (matches Claude Code native gate). When disabled the
778
- // idle timer never arms, so the stream is never killed on inactivity;
779
- // the bridge stall watchdog (600s) remains the dead-stream backstop.
780
- if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
781
- if (idleTimer) clearTimeout(idleTimer);
782
- idleTimer = setTimeout(() => {
783
- idleTimedOut = true;
784
- try { abortStream?.(); } catch (err) {
785
- try { process.stderr.write(`[anthropic-oauth] sse idle abortStream failed: ${err?.message ?? String(err)}\n`); } catch {}
786
- }
787
- try {
788
- const _c = reader.cancel('SSE idle timeout');
789
- if (_c && typeof _c.catch === 'function') _c.catch(() => {});
790
- } catch (err) {
791
- try { process.stderr.write(`[anthropic-oauth] sse idle cancel failed: ${err?.message ?? String(err)}\n`); } catch {}
792
- }
793
- // Force-reject the in-flight reader.read() race even when reader.cancel()
794
- // fails to settle the pending read: without this the await below stays
795
- // pending forever and the SSE idle timeout never unblocks the loop —
796
- // the 391s-hang root cause.
797
- if (idleReject) {
798
- const e = new Error(`Anthropic OAuth SSE stream timed out after ${SSE_IDLE_TIMEOUT_MS}ms of inactivity`);
799
- e.code = 'ETIMEDOUT';
800
- const r = idleReject; idleReject = null; r(e);
801
- }
802
- // Shared provider policy: short inter-chunk inactivity catches the
803
- // sess_9cfd11-class stuck pattern where SSE starts but then goes silent.
804
- }, SSE_IDLE_TIMEOUT_MS);
805
- };
806
-
807
- const onAbort = () => {
808
- try {
809
- const _c = reader.cancel('SSE aborted');
810
- if (_c && typeof _c.catch === 'function') _c.catch(() => {});
811
- } catch {}
812
- };
813
- if (signal) {
814
- if (signal.aborted) {
815
- _captureMidstreamAbort(state, signal.reason);
816
- throw signal.reason instanceof Error
817
- ? signal.reason
818
- : new Error('Anthropic OAuth SSE stream aborted');
819
- }
820
- signal.addEventListener('abort', onAbort, { once: true });
821
- }
822
-
823
- try {
824
- resetIdleTimer();
825
- streamLoop: while (true) {
826
- let chunk;
827
- try {
828
- // Race the read against the idle timer's rejector so a stuck
829
- // reader.read() (cancel did not settle it) still unblocks here.
830
- chunk = await new Promise((resolve, reject) => {
831
- idleReject = reject;
832
- reader.read().then(resolve, reject);
833
- });
834
- } catch (err) {
835
- if (idleTimedOut) {
836
- const idleErr = new Error(`Anthropic OAuth SSE stream timed out after ${SSE_IDLE_TIMEOUT_MS}ms of inactivity`);
837
- idleErr.code = 'ETIMEDOUT';
838
- throw idleErr;
839
- }
840
- if (signal?.aborted) {
841
- _captureMidstreamAbort(state, signal.reason);
842
- throw signal.reason instanceof Error
843
- ? signal.reason
844
- : new Error('Anthropic OAuth SSE stream aborted');
845
- }
846
- throw err;
847
- }
848
- const { done, value } = chunk;
849
- if (done) break;
850
-
851
- resetIdleTimer();
852
- buffer += decoder.decode(value, { stream: true });
853
- const lines = buffer.split('\n');
854
- buffer = lines.pop() || '';
855
-
856
- for (const line of lines) {
857
- if (line.startsWith(':')) {
858
- // SSE comment frame (Anthropic `:ping` keepalive). The HTML Standard SSE
859
- // spec says comments are silently ignored, but we surface them here so
860
- // the bridge-stall-watchdog sees the stream is still alive during Opus
861
- // extended-thinking pauses. No content is emitted — this only refreshes
862
- // the runtime's lastStreamDeltaAt timestamp.
863
- try { onStreamDelta?.(); } catch {}
864
- continue;
865
- }
866
- if (line.startsWith('event: ')) {
867
- currentEvent = line.slice(7).trim();
868
- continue;
869
- }
870
- if (!line.startsWith('data: ')) continue;
871
- const data = line.slice(6).trim();
872
- if (!data) continue;
873
-
874
- try {
875
- const event = JSON.parse(data);
876
-
877
- if (event.type === 'message_start' && event.message) {
878
- if (state) state.sawMessageStart = true;
879
- if (event.message.model) model = event.message.model;
880
- if (event.message.usage) {
881
- usage.inputTokens = event.message.usage.input_tokens || 0;
882
- usage.cachedTokens = event.message.usage.cache_read_input_tokens || 0;
883
- usage.cacheWriteTokens = event.message.usage.cache_creation_input_tokens || 0;
884
- usage.raw = { ...event.message.usage };
885
- }
886
- }
887
-
888
- if (event.type === 'content_block_start') {
889
- const block = event.content_block;
890
- if (block?.type === 'tool_use') {
891
- pendingToolInputs.set(event.index, {
892
- id: block.id || '',
893
- name: block.name || '',
894
- inputJson: '',
895
- });
896
- }
897
- }
898
-
899
- if (event.type === 'content_block_delta') {
900
- const delta = event.delta;
901
- if (delta?.type) contentBlockTypes.add(delta.type);
902
- if (delta?.type === 'text_delta') {
903
- content += delta.text || '';
904
- try { onStreamDelta?.(); } catch {}
905
- }
906
- if (delta?.type === 'thinking_delta' || delta?.type === 'signature_delta') {
907
- // Extended-thinking block: provider reasoning without
908
- // user-visible text. Track presence so a final turn
909
- // that emitted ONLY thinking (no text_delta, no
910
- // tool_use) can be classified by the loop as
911
- // synthesis-stalled rather than silent empty.
912
- hasThinkingContent = true;
913
- try { onStreamDelta?.(); } catch {}
914
- }
915
- if (delta?.type === 'input_json_delta') {
916
- const pending = pendingToolInputs.get(event.index);
917
- if (pending) {
918
- pending.inputJson += delta.partial_json || '';
919
- }
920
- try { onStreamDelta?.(); } catch {}
921
- }
922
- }
923
-
924
- if (event.type === 'content_block_stop') {
925
- const pending = pendingToolInputs.get(event.index);
926
- if (pending) {
927
- // Bare JSON.parse threw straight up into the
928
- // surrounding broad catch, which swallowed the
929
- // whole tool_call — the loop never saw it and
930
- // the assistant turn ended with an unmatched
931
- // tool_use id. Wrap the parse so a malformed
932
- // input still produces a tool_call (with empty
933
- // arguments and a logged error) instead of a
934
- // silent drop.
935
- let parsedArgs = {};
936
- if (pending.inputJson) {
937
- try { parsedArgs = JSON.parse(pending.inputJson); }
938
- catch (parseErr) {
939
- process.stderr.write(`[anthropic-oauth] tool args JSON.parse failed (id=${pending.id}, name=${pending.name}): ${parseErr?.message || parseErr}\n`);
940
- parsedArgs = {};
941
- }
942
- }
943
- const call = {
944
- id: pending.id,
945
- name: pending.name,
946
- arguments: parsedArgs,
947
- };
948
- toolCalls.push(call);
949
- pendingToolInputs.delete(event.index);
950
- if (state) state.emittedToolCall = true;
951
- // Eager dispatch: let the loop start this tool
952
- // before message_stop arrives. The loop keys
953
- // pending promises by call.id so order is safe.
954
- try { onToolCall?.(call); } catch {}
955
- try { onStreamDelta?.(); } catch {}
956
- }
957
- }
958
-
959
- if (event.type === 'message_delta') {
960
- if (event.delta?.stop_reason) {
961
- stopReason = event.delta.stop_reason;
962
- }
963
- if (event.usage) {
964
- usage.outputTokens = event.usage.output_tokens || 0;
965
- usage.raw = { ...(usage.raw || {}), ...event.usage };
966
- }
967
- if (stopReason === 'tool_use' && toolCalls.length > 0 && pendingToolInputs.size === 0) {
968
- if (state) state.sawCompleted = true;
969
- break streamLoop;
970
- }
971
- }
972
- if (event.type === 'message_stop') {
973
- if (state) state.sawCompleted = true;
974
- // Anthropic streams can keep emitting `:ping` keepalive
975
- // frames after `message_stop`; if we wait for EOF the
976
- // outer reader.read() loop hangs indefinitely. Break
977
- // out of streamLoop the moment the message ends.
978
- break streamLoop;
979
- }
980
- // Unified prompt volume — what the model actually ingested.
981
- // Anthropic splits input into three billable slots (uncached
982
- // input + cache_read + cache_create); keep them separate for
983
- // cost math but also expose the sum so cross-provider logs
984
- // have a consistent `promptTokens` meaning.
985
- usage.promptTokens = (usage.inputTokens || 0)
986
- + (usage.cachedTokens || 0)
987
- + (usage.cacheWriteTokens || 0);
988
- } catch { /* skip malformed events */ }
989
- }
990
- }
991
-
992
- // Truncated-stream guard: if the reader loop exited (EOF or break)
993
- // after message_start but without seeing message_stop / a tool_use
994
- // stop_reason, the assistant turn was cut off mid-flight. Returning
995
- // success here would silently surface partial content (or a partially
996
- // streamed tool_use whose input_json never completed) as final.
997
- // Throw a typed truncated-stream error so the loop can decide whether
998
- // to retry, surface, or escalate instead of accepting the partial.
999
- if (state?.sawMessageStart && !state?.sawCompleted) {
1000
- const pendingToolUse = pendingToolInputs.size > 0;
1001
- const err = Object.assign(
1002
- new Error(
1003
- `Anthropic OAuth SSE stream truncated: message_start without message_stop`
1004
- + (pendingToolUse ? ` (pending tool_use input)` : ''),
1005
- ),
1006
- {
1007
- name: 'TruncatedStreamError',
1008
- code: 'TRUNCATED_STREAM',
1009
- truncatedStream: true,
1010
- pendingToolUse,
1011
- stopReason,
1012
- },
1013
- );
1014
- throw err;
1015
- }
1016
-
1017
- return {
1018
- content,
1019
- model,
1020
- toolCalls: toolCalls.length ? toolCalls : undefined,
1021
- usage,
1022
- stopReason,
1023
- hasThinkingContent,
1024
- contentBlockTypes: Array.from(contentBlockTypes),
1025
- };
1026
- } finally {
1027
- if (idleTimer) clearTimeout(idleTimer);
1028
- if (signal) signal.removeEventListener('abort', onAbort);
1029
- try { reader.releaseLock(); } catch (err) {
1030
- try { process.stderr.write(`[anthropic-oauth] reader releaseLock failed: ${err?.message ?? String(err)}\n`); } catch {}
1031
- }
1032
- }
1033
- }
1034
-
1035
- /**
1036
- * Classify an Anthropic SSE failure for single-shot mid-stream retry.
1037
- *
1038
- * Retry is allowed only after `message_start` and before `message_stop`,
1039
- * and only when no tool call has already been surfaced to the loop.
1040
- * That keeps recovery limited to transport/stream stalls without risking
1041
- * duplicate eager tool execution.
1042
- */
1043
- export function _classifyMidstreamError(err, state) {
1044
- if (!state) return null;
1045
- if ((state.attemptIndex | 0) >= 1) return null;
1046
- if (state.sawCompleted) return null;
1047
- if (!state.sawMessageStart) return null;
1048
- if (state.userAbort) return null;
1049
- if (state.emittedToolCall) return null;
1050
-
1051
- if (!err) return null;
1052
- const status = Number(err?.httpStatus || 0);
1053
- if (status === 401 || status === 403 || status === 429) return null;
1054
-
1055
- const name = err?.name || '';
1056
- if (name === 'BridgeStallAbortError') return 'bridge_stall';
1057
- if (name === 'StreamStalledAbortError') return 'stream_stalled';
1058
- if (state.watchdogAbort === 'BridgeStallAbortError') return 'bridge_stall';
1059
- if (state.watchdogAbort === 'StreamStalledAbortError') return 'stream_stalled';
1060
-
1061
- const code = err?.code || err?.cause?.code || '';
1062
- if (code === 'ECONNRESET') return 'reset';
1063
- if (code === 'ETIMEDOUT' || code === 'ESOCKETTIMEDOUT') return 'timeout';
1064
- if (code === 'ENOTFOUND' || code === 'EAI_AGAIN' || code === 'EAI_NODATA') return 'dns';
1065
-
1066
- const msg = String(err?.message || '').toLowerCase();
1067
- if (msg.includes('stream timed out after') && msg.includes('of inactivity')) return 'sse_idle_timeout';
1068
- if (msg.includes('body stream') && msg.includes('terminated')) return 'stream_terminated';
1069
- if (msg.includes('fetch failed')) return 'fetch_failed';
1070
-
1071
- return null;
1072
- }
1073
-
1074
- // --- Build request body ---
1075
-
1076
- function resolveCacheTtls(opts) {
1077
- // Layered cache strategy — caller may override per-layer via opts.cacheStrategy.
1078
- // Anthropic enforces: 1h entries must appear before 5m entries in the request.
1079
- const strategy = opts.cacheStrategy || {};
1080
- const pick = (layer, fallback) => {
1081
- const v = strategy[layer];
1082
- if (v === '1h') return CACHE_TTL_STABLE;
1083
- if (v === '5m') return CACHE_TTL_VOLATILE;
1084
- if (v === 'none') return null;
1085
- return fallback;
1086
- };
1087
- // BP budget (4 total):
1088
- // BP1 baseRules — 1h (shared across ALL roles)
1089
- // BP2 roleCatalog — 1h (shared across ALL roles)
1090
- // BP3 tier3 — 1h (sessionMarker: role + permission + project)
1091
- // BP4 messages — 5m sliding tail (tool_result cache across iter)
1092
- // tools BP is dropped — system BP covers the tools prefix via
1093
- // Anthropic's prompt cache prefix semantics (order: tools → system
1094
- // → messages).
1095
- // tier3 defaults to 1h (stable) — sessionMarker content is stable per
1096
- // (role, permission, project) tuple and Anthropic only spends the BP
1097
- // slot when findTier3Index() actually finds a <system-reminder> block,
1098
- // so this default is free for sessions that don't carry one. Previously
1099
- // null here meant any caller that skipped smart bridge resolve (CLI,
1100
- // raw bridge spawn) silently lost the tier3 cache layer even
1101
- // though their message layout supported it.
1102
- return {
1103
- tools: pick('tools', CACHE_TTL_STABLE),
1104
- system: pick('system', CACHE_TTL_STABLE),
1105
- tier3: pick('tier3', CACHE_TTL_STABLE),
1106
- messages: pick('messages', CACHE_TTL_VOLATILE),
1107
- };
1108
- }
1109
-
1110
- // Tier 3 is injected by session/manager as a user message wrapped in
1111
- // `<system-reminder>` whose body starts with the explicit sentinel
1112
- // `<!-- bp3-sentinel -->` (emitted by collect.mjs:composeSystemPrompt only
1113
- // when a stable projectContext is present). The sentinel is mandatory:
1114
- // volatileTail (role/permission/taskBrief/memoryRecap) is also wrapped in
1115
- // `<system-reminder>` but varies per-call, so a plain prefix match would
1116
- // pin per-call data to the 1h BP3 slot and explode the cache.
1117
- const BP3_SENTINEL = '<!-- bp3-sentinel -->';
1118
- function findTier3Index(chatMsgs) {
1119
- for (let i = 0; i < chatMsgs.length; i++) {
1120
- const m = chatMsgs[i];
1121
- if (m?.role === 'user' && typeof m.content === 'string'
1122
- && m.content.startsWith('<system-reminder>')
1123
- && m.content.includes(BP3_SENTINEL)) {
1124
- return i;
1125
- }
1126
- }
1127
- return -1;
1128
- }
1129
-
1130
- function buildRequestBody(messages, model, tools, sendOpts) {
1131
- const systemMsgs = messages.filter(m => m.role === 'system');
1132
- const chatMsgs = messages.filter(m => m.role !== 'system');
1133
- // Pass each system message text as its own entry so the Anthropic body
1134
- // gets N separate content blocks — each can have its own BP
1135
- // independent of the others.
1136
- const systemTexts = systemMsgs.map(m => m.content);
1137
- const maxTokens = resolveMaxTokens(model);
1138
- const opts = sendOpts || {};
1139
- const ttls = resolveCacheTtls(opts);
1140
- const systemBlocks = buildSystemBlocks(systemTexts, model, ttls?.system);
1141
-
1142
- // 4-BP budget layout. tools BP is dropped — system BP covers the
1143
- // tools prefix via Anthropic's prompt cache prefix semantics
1144
- // (order: tools → system → messages). That frees slots for
1145
- // tier3 + messages-tail.
1146
- const systemBpUsed = ttls.system ? systemBlocks.filter(b => b.cache_control).length : 0;
1147
- const toolsBpUsed = 0;
1148
- const tier3Idx = ttls.tier3 ? findTier3Index(chatMsgs) : -1;
1149
- const tier3BpUsed = tier3Idx >= 0 ? 1 : 0;
1150
- const usedSlots = toolsBpUsed + systemBpUsed + tier3BpUsed;
1151
- // Env override for smoke-testing BP-count strategies. ANTHROPIC_MSG_SLOTS
1152
- // caps how many sliding message-tail breakpoints we burn per request
1153
- // (default: fill whatever's left of the 4-BP budget). Set to 1 to reduce
1154
- // BP-position churn across iterations; set to 0 to disable messages-tail
1155
- // caching entirely and rely on the tools+system+tier3 prefix.
1156
- const msgSlotsCap = Number.parseInt(process.env.ANTHROPIC_MSG_SLOTS, 10);
1157
- const defaultMsgSlots = Math.max(0, 4 - usedSlots);
1158
- const msgSlots = ttls.messages
1159
- ? (Number.isFinite(msgSlotsCap) && msgSlotsCap >= 0 ? Math.min(msgSlotsCap, defaultMsgSlots) : defaultMsgSlots)
1160
- : 0;
1161
- const cacheableIndexes = collectRecentCacheableIndexes(chatMsgs, msgSlots);
1162
- // If the tail slot landed on the Tier 3 index, drop it from the sliding
1163
- // set — Tier 3 already owns its own BP and we don't want to double-mark.
1164
- if (tier3Idx >= 0) cacheableIndexes.delete(tier3Idx);
1165
- const anthropicMessages = toAnthropicMessages(
1166
- chatMsgs,
1167
- cacheableIndexes,
1168
- ttls.messages,
1169
- tier3Idx,
1170
- ttls.tier3,
1171
- );
1172
-
1173
- const body = {
1174
- model,
1175
- max_tokens: maxTokens,
1176
- messages: anthropicMessages,
1177
- stream: true,
1178
- };
1179
-
1180
- if (systemBlocks.length) body.system = systemBlocks;
1181
-
1182
- if (tools?.length) {
1183
- // No cache_control on tools — the systemBase BP already covers the
1184
- // tools prefix via Anthropic's prompt cache prefix semantics (order:
1185
- // tools → system → messages). Placing a separate BP here would waste
1186
- // a slot that's better spent on messages tail.
1187
- body.tools = toAnthropicTools(tools);
1188
- }
1189
-
1190
- if (opts.effort) {
1191
- if (EFFORT_BUDGET[opts.effort]) {
1192
- body.thinking = { type: 'enabled', budget_tokens: EFFORT_BUDGET[opts.effort] };
1193
- } else if (!_LOGGED_UNKNOWN_EFFORT.has(opts.effort)) {
1194
- _LOGGED_UNKNOWN_EFFORT.add(opts.effort);
1195
- try {
1196
- process.stderr.write(`[anthropic-oauth] unknown effort=${opts.effort} ignored (known: ${Object.keys(EFFORT_BUDGET).join(',')})\n`);
1197
- } catch {}
1198
- }
1199
- }
1200
-
1201
- if (opts.fast === true && supportsAnthropicFastMode(model)) {
1202
- body.speed = 'fast';
1203
- }
1204
-
1205
- return body;
1206
- }
1207
-
1208
- // --- Provider ---
1209
-
1210
- export class AnthropicOAuthProvider {
1211
- // input_tokens EXCLUDES cache_read_input_tokens (separate field) — add the
1212
- // cache back for the real context footprint. See registry.mjs.
1213
- static inputExcludesCache = true;
1214
- name = 'anthropic-oauth';
1215
- credentials = null;
1216
- config;
1217
- fastModeBetaHeaderLatched = false;
1218
-
1219
- constructor(config) {
1220
- this.config = config || {};
1221
- this.credentials = loadCredentials();
1222
- // Warm a kept-alive socket to the messages API so the first request
1223
- // skips the cold TLS handshake. Best-effort; never throws.
1224
- preconnect('https://api.anthropic.com');
1225
- }
1226
-
1227
- async ensureAuth({ forceRefresh = false, reason = 'preemptive' } = {}) {
1228
- if (!this.credentials) {
1229
- this.credentials = loadCredentials();
1230
- }
1231
- if (!this.credentials) {
1232
- throw new Error('Anthropic OAuth credentials not found. Run "claude login" to authenticate.');
1233
- }
1234
-
1235
- // Pick up host-rotated tokens the moment the credentials file is
1236
- // rewritten — without this, a fresh `claude login` is ignored until
1237
- // the in-memory token's expiry skew triggers a refresh.
1238
- const diskMtime = _credentialsMaxMtime();
1239
- if (diskMtime > 0 && diskMtime > (this.credentials.mtimeMs || 0)) {
1240
- const fresh = loadCredentials();
1241
- if (fresh?.accessToken) {
1242
- this.credentials = fresh;
1243
- process.stderr.write(`[anthropic-oauth] Credentials reloaded from disk (mtime change)\n`);
1244
- }
1245
- }
1246
-
1247
- const expiring = this.credentials.expiresAt
1248
- && this.credentials.expiresAt < Date.now() + TOKEN_REFRESH_SKEW_MS;
1249
- if (forceRefresh || expiring) {
1250
- this.credentials = await this._refreshCredentials({ force: forceRefresh, reason });
1251
- }
1252
-
1253
- return this.credentials;
1254
- }
1255
-
1256
- async _refreshCredentials({ force = false, reason = 'preemptive' } = {}) {
1257
- const currentToken = this.credentials?.accessToken || null;
1258
- const disk = loadCredentials();
1259
- const validAfter = Date.now() + (force ? 0 : TOKEN_REFRESH_SKEW_MS);
1260
- if (disk?.accessToken && disk.accessToken !== currentToken
1261
- && (!disk.expiresAt || disk.expiresAt >= validAfter)) {
1262
- this.credentials = disk;
1263
- process.stderr.write(`[anthropic-oauth] Credentials reloaded from disk\n`);
1264
- return disk;
1265
- }
1266
- if (!this.credentials && disk) this.credentials = disk;
1267
-
1268
- if (_oauthRefreshInFlight) {
1269
- const shared = await _oauthRefreshInFlight;
1270
- this.credentials = shared;
1271
- if (!force || shared?.accessToken !== currentToken) return this.credentials;
1272
- }
1273
-
1274
- const startingCreds = this.credentials || disk;
1275
- _oauthRefreshInFlight = (async () => {
1276
- const latest = loadCredentials() || startingCreds;
1277
- const latestValidAfter = Date.now() + (force ? 0 : TOKEN_REFRESH_SKEW_MS);
1278
- if (latest?.accessToken && latest.accessToken !== currentToken
1279
- && (!latest.expiresAt || latest.expiresAt >= latestValidAfter)) {
1280
- process.stderr.write(`[anthropic-oauth] Credentials reloaded from disk\n`);
1281
- return latest;
1282
- }
1283
-
1284
- if (!latest?.refreshToken) {
1285
- if (!force && latest?.accessToken && (!latest.expiresAt || latest.expiresAt > Date.now())) {
1286
- process.stderr.write(`[anthropic-oauth] WARNING: token expiring but no refresh token; using current token until expiry\n`);
1287
- return latest;
1288
- }
1289
- throw new Error('Anthropic OAuth refresh token not available. Run "claude login" to re-authenticate.');
1290
- }
1291
-
1292
- try {
1293
- process.stderr.write(`[anthropic-oauth] Token ${reason}, refreshing...\n`);
1294
- const refreshed = await refreshOAuthCredentials(latest);
1295
- process.stderr.write(`[anthropic-oauth] Token refreshed, expires in ${Math.round(((refreshed.expiresAt || Date.now()) - Date.now()) / 1000)}s\n`);
1296
- return refreshed;
1297
- } catch (err) {
1298
- if (!force && latest?.accessToken && (!latest.expiresAt || latest.expiresAt > Date.now())) {
1299
- const msg = err instanceof Error ? err.message : String(err);
1300
- process.stderr.write(`[anthropic-oauth] Refresh failed (${msg}); using still-valid current token\n`);
1301
- return latest;
1302
- }
1303
- throw err;
1304
- }
1305
- })().finally(() => { _oauthRefreshInFlight = null; });
1306
-
1307
- this.credentials = await _oauthRefreshInFlight;
1308
- return this.credentials;
1309
- }
1310
-
1311
- scrubTokens(text) {
1312
- return _scrubTokens(text);
1313
- }
1314
-
1315
- async send(messages, model, tools, sendOpts) {
1316
- // Defense-in-depth: enforce tool_use / tool_result pairing before
1317
- // the Anthropic API call. The trim.mjs sanitize pass is normally
1318
- // invoked by the budget trimmer in loop.mjs, but dispatches under
1319
- // budget skip it — a tool that aborted mid-flight then leaves an
1320
- // unmatched tool_use in messages, which the provider rejects with
1321
- // a hard 400. Pairing here closes the gap regardless of caller.
1322
- messages = sanitizeToolPairs(messages);
1323
- const opts = sendOpts || {};
1324
- const onStageChange = typeof opts.onStageChange === 'function' ? opts.onStageChange : null;
1325
- const onStreamDelta = typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null;
1326
- const onToolCall = typeof opts.onToolCall === 'function' ? opts.onToolCall : null;
1327
- const externalSignal = opts.signal || null;
1328
- // Test seam: lets the retry harness drive stream outcomes without a
1329
- // live OAuth session.
1330
- const parseSSEFn = typeof opts._parseSSEFn === 'function' ? opts._parseSSEFn : parseSSEStream;
1331
-
1332
- let creds = await this.ensureAuth();
1333
- // Default when the caller doesn't pin a model: newest high-tier chat
1334
- // model from the live catalog (one warmup round-trip if cache is cold).
1335
- const useModel = model || await ensureLatestAnthropicModel(this);
1336
- const body = buildRequestBody(messages, useModel, tools, sendOpts);
1337
- if (body.speed === 'fast') {
1338
- this.fastModeBetaHeaderLatched = true;
1339
- }
1340
- const sessionId = opts.sessionId || null;
1341
- const iteration = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null;
1342
- const totalTimeout = createTimeoutSignal(
1343
- externalSignal,
1344
- PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
1345
- 'Anthropic OAuth total request',
1346
- );
1347
- const totalSignal = totalTimeout.signal;
1348
-
1349
- const cleanupCancelHandler = (handler) => {
1350
- if (!handler) return;
1351
- try { totalSignal.removeEventListener('abort', handler); } catch {}
1352
- };
1353
-
1354
- const doRequest = async (accessToken, requestSignal = null) => {
1355
- const controller = createAbortController();
1356
- const fetchStartedAt = Date.now();
1357
-
1358
- let cancelHandler = null;
1359
- let attemptCancelHandler = null;
1360
- if (totalSignal) {
1361
- if (totalSignal.aborted) {
1362
- controller.abort(totalSignal.reason);
1363
- throw totalSignal.reason instanceof Error
1364
- ? totalSignal.reason
1365
- : new Error('Anthropic OAuth request aborted by session close');
1366
- }
1367
- cancelHandler = () => { try { controller.abort(totalSignal.reason); } catch {} };
1368
- totalSignal.addEventListener('abort', cancelHandler, { once: true });
1369
- }
1370
- if (requestSignal && requestSignal !== totalSignal) {
1371
- if (requestSignal.aborted) {
1372
- cleanupCancelHandler(cancelHandler);
1373
- controller.abort(requestSignal.reason);
1374
- throw requestSignal.reason instanceof Error
1375
- ? requestSignal.reason
1376
- : new Error('Anthropic OAuth request attempt aborted');
1377
- }
1378
- attemptCancelHandler = () => { try { controller.abort(requestSignal.reason); } catch {} };
1379
- requestSignal.addEventListener('abort', attemptCancelHandler, { once: true });
1380
- }
1381
-
1382
- try {
1383
- try { onStageChange?.('requesting'); } catch {}
1384
- body.messages = sanitizeAnthropicContentPairs(body.messages);
1385
-
1386
- const response = await fetch(API_URL, {
1387
- method: 'POST',
1388
- headers: {
1389
- 'Authorization': `Bearer ${accessToken}`,
1390
- 'anthropic-version': ANTHROPIC_VERSION,
1391
- 'anthropic-beta': buildAnthropicBetaHeaders({
1392
- base: OAUTH_BETA_HEADERS,
1393
- fastMode: this.fastModeBetaHeaderLatched,
1394
- }),
1395
- 'anthropic-dangerous-direct-browser-access': 'true',
1396
- 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
1397
- 'x-app': 'cli',
1398
- 'Content-Type': 'application/json',
1399
- },
1400
- body: JSON.stringify(body),
1401
- signal: controller.signal,
1402
- dispatcher: getLlmDispatcher(),
1403
- });
1404
-
1405
- traceBridgeFetch({
1406
- sessionId,
1407
- headersMs: Date.now() - fetchStartedAt,
1408
- httpStatus: response.status,
1409
- provider: 'anthropic-oauth',
1410
- model: useModel,
1411
- transport: 'sse',
1412
- });
1413
-
1414
- if (attemptCancelHandler) {
1415
- try { requestSignal.removeEventListener('abort', attemptCancelHandler); } catch {}
1416
- }
1417
- return { response, controller, cancelHandler };
1418
- } catch (err) {
1419
- if (attemptCancelHandler) {
1420
- try { requestSignal.removeEventListener('abort', attemptCancelHandler); } catch {}
1421
- }
1422
- cleanupCancelHandler(cancelHandler);
1423
- if (requestSignal?.aborted) {
1424
- const reason = requestSignal.reason;
1425
- throw reason instanceof Error ? reason : new Error('Anthropic OAuth request attempt aborted');
1426
- }
1427
- if (totalSignal?.aborted) {
1428
- const reason = totalSignal.reason;
1429
- throw reason instanceof Error ? reason : new Error('Anthropic OAuth request aborted by session close');
1430
- }
1431
- if (err?.name === 'AbortError') {
1432
- const timeoutErr = new Error(`Anthropic OAuth API initial response timed out after ${PROVIDER_HTTP_RESPONSE_TIMEOUT_MS}ms`);
1433
- timeoutErr.code = 'EPROVIDERTIMEOUT';
1434
- throw timeoutErr;
1435
- }
1436
- throw err;
1437
- }
1438
- };
1439
- // Test seam: injectable request factory for retry-path tests.
1440
- const doRequestImpl = typeof opts._doRequestFn === 'function' ? opts._doRequestFn : doRequest;
1441
-
1442
- const requestWithRetry = async (accessToken) => withRetry(async ({ signal: attemptSignal }) => {
1443
- const result = await doRequestImpl(accessToken, attemptSignal);
1444
- const status = Number(result?.response?.status || 0);
1445
- const transientStatus = classifyError({ httpStatus: status }) === 'transient';
1446
- if (transientStatus || status === 429) {
1447
- const err = new Error(`Anthropic OAuth API ${status}`);
1448
- err.httpStatus = status;
1449
- err.status = status;
1450
- err.headers = result?.response?.headers;
1451
- err.response = { status, headers: result?.response?.headers };
1452
- const retryAfterMs = retryAfterMsFromError(err);
1453
- if (transientStatus || retryAfterMs != null) {
1454
- try { await result.response.text(); } catch {}
1455
- cleanupCancelHandler(result.cancelHandler);
1456
- try { result.controller?.abort?.(); } catch {}
1457
- throw err;
1458
- }
1459
- }
1460
- return result;
1461
- }, {
1462
- signal: totalSignal,
1463
- maxAttempts: PROVIDER_RETRY_MAX_ATTEMPTS,
1464
- backoffMs: PROVIDER_RETRY_BACKOFF_MS,
1465
- perAttemptTimeoutMs: PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
1466
- perAttemptLabel: 'Anthropic OAuth initial response',
1467
- onRetry: ({ attempt, lastErr, delayMs, delayReason }) => {
1468
- const status = Number(lastErr?.httpStatus || lastErr?.status || lastErr?.response?.status || 0) || null;
1469
- const reason = status || lastErr?.code || lastErr?.message || 'network error';
1470
- const suffix = delayReason ? ` (${delayReason})` : '';
1471
- try {
1472
- process.stderr.write(
1473
- `[anthropic-oauth] retry attempt ${attempt + 1}/${PROVIDER_RETRY_MAX_ATTEMPTS} after ${reason}, backoff ${delayMs}ms${suffix}\n`,
1474
- );
1475
- } catch {}
1476
- },
1477
- });
1478
- // One retry only: enough to recover transient stream loss without
1479
- // quietly replaying long-running work multiple times.
1480
- const MAX_MIDSTREAM_RETRIES = 1;
1481
- let firstAttemptError = null;
1482
- let firstAttemptClassifier = null;
1483
-
1484
- try {
1485
- for (let attemptIndex = 0; attemptIndex <= MAX_MIDSTREAM_RETRIES; attemptIndex++) {
1486
- let response, controller, cancelHandler;
1487
- ({ response, controller, cancelHandler } = await requestWithRetry(creds.accessToken));
1488
-
1489
- // 401: token expired/revoked. 403: organization permission flipped
1490
- // (e.g. relogin into a different org). Both: force a shared refresh
1491
- // and retry once with the new token.
1492
- if (response.status === 401 || response.status === 403) {
1493
- process.stderr.write(`[anthropic-oauth] ${response.status} — forcing refresh and retrying once\n`);
1494
- cleanupCancelHandler(cancelHandler);
1495
- creds = await this.ensureAuth({ forceRefresh: true, reason: String(response.status) });
1496
- ({ response, controller, cancelHandler } = await requestWithRetry(creds.accessToken));
1497
- }
1498
-
1499
- if (!response.ok) {
1500
- cleanupCancelHandler(cancelHandler);
1501
- const text = await response.text().catch(() => '');
1502
- const safeText = this.scrubTokens(text).slice(0, 200);
1503
- process.stderr.write(`[anthropic-oauth] API error ${response.status}: ${safeText}\n`);
1504
-
1505
- // Phase I: on unknown/404 model errors, force a catalog refresh and
1506
- // retry once. Protects against a silently-rotated model id.
1507
- const isUnknownModel = response.status === 404
1508
- || /unknown[_\s-]?model|model[_\s-]?not[_\s-]?found/i.test(safeText);
1509
- if (isUnknownModel && !opts._modelRetry) {
1510
- process.stderr.write(`[anthropic-oauth] unknown model — refreshing catalog + 1 retry\n`);
1511
- await this._refreshModelCache();
1512
- return this.send(messages, model, tools, { ...opts, _modelRetry: true });
1513
- }
1514
- throw new Error(`Anthropic OAuth API ${response.status}: ${safeText}`);
1515
- }
1516
-
1517
- if (SSE_VERBOSE) process.stderr.write(`[anthropic-oauth] Response ${response.status}, parsing SSE...\n`);
1518
- try { onStageChange?.('streaming'); } catch {}
1519
-
1520
- const midState = {
1521
- attemptIndex,
1522
- sawMessageStart: false,
1523
- sawCompleted: false,
1524
- emittedToolCall: false,
1525
- userAbort: false,
1526
- watchdogAbort: null,
1527
- ttftAt: null,
1528
- };
1529
-
1530
- try {
1531
- const sseStartedAt = Date.now();
1532
- const result = await parseSSEFn(
1533
- response,
1534
- controller.signal,
1535
- () => controller.abort(),
1536
- onStreamDelta,
1537
- onToolCall,
1538
- midState,
1539
- );
1540
-
1541
- const ttftMs = midState.ttftAt ? midState.ttftAt - sseStartedAt : null;
1542
- const liveModel = result.model || useModel;
1543
- traceBridgeSse({
1544
- sessionId,
1545
- sseParseMs: Date.now() - sseStartedAt,
1546
- ttftMs,
1547
- provider: 'anthropic-oauth',
1548
- model: liveModel,
1549
- transport: 'sse',
1550
- });
1551
-
1552
- traceBridgeUsage({
1553
- sessionId,
1554
- iteration,
1555
- inputTokens: result.usage?.inputTokens || 0,
1556
- outputTokens: result.usage?.outputTokens || 0,
1557
- cachedTokens: result.usage?.cachedTokens || 0,
1558
- cacheWriteTokens: result.usage?.cacheWriteTokens || 0,
1559
- promptTokens: result.usage?.promptTokens || 0,
1560
- model: liveModel,
1561
- modelDisplay: _displayModel(liveModel),
1562
- rawUsage: result.usage?.raw || null,
1563
- provider: 'anthropic-oauth',
1564
- });
1565
-
1566
- // Phase I: if the live response surfaced a model id we don't know
1567
- // about yet, kick off a background catalog refresh. Fire-and-forget
1568
- // — do not await, do not surface errors.
1569
- if (result.model && !_catalogHas(result.model)) {
1570
- void this._refreshModelCache();
1571
- }
1572
-
1573
- if (SSE_VERBOSE) process.stderr.write(`[anthropic-oauth] Done: ${result.content.length} chars, ${result.toolCalls?.length || 0} tool calls\n`);
1574
- // Empty-stream guard. Invariant: a valid Anthropic SSE response
1575
- // ALWAYS opens with message_start (which carries usage.input_tokens).
1576
- // A 200 whose body produced no message_start delivered nothing —
1577
- // no usage, no content, no tool calls — i.e. a dropped/empty stream
1578
- // (transient, often rate-limit-adjacent under concurrent load), NOT
1579
- // a valid terminal turn. Returning it surfaces upstream as a silent
1580
- // empty turn (0 tokens, no content) that masks the cause. Throw a
1581
- // marked error: retry is provably safe here (no message_start ⇒
1582
- // nothing was emitted ⇒ no duplicate-tool risk), and once retries
1583
- // are exhausted the error is surfaced instead of swallowed.
1584
- if (!midState.sawMessageStart
1585
- && !midState.userAbort
1586
- && !midState.watchdogAbort
1587
- && !result.content
1588
- && !(result.toolCalls && result.toolCalls.length)
1589
- && !(result.usage && result.usage.inputTokens > 0)) {
1590
- const emptyErr = new Error('Anthropic OAuth SSE stream produced no message_start (empty/dropped stream — likely transient or rate-limited)');
1591
- emptyErr.code = 'EEMPTYSTREAM';
1592
- emptyErr.isEmptyStream = true;
1593
- throw emptyErr;
1594
- }
1595
- try {
1596
- Object.defineProperty(result, '__midstreamRetries', { value: attemptIndex, enumerable: false });
1597
- } catch { /* ignore non-extensible result */ }
1598
- return result;
1599
- } catch (err) {
1600
- // Empty/dropped stream (no message_start): safe to retry once —
1601
- // nothing was emitted, so there is no duplicate-tool risk. This
1602
- // is intentionally NOT routed through _classifyMidstreamError,
1603
- // which requires sawMessageStart and would reject it.
1604
- if (err?.isEmptyStream && attemptIndex < MAX_MIDSTREAM_RETRIES) {
1605
- firstAttemptError = err;
1606
- firstAttemptClassifier = 'empty_stream';
1607
- try { controller?.abort?.(err); } catch { /* best-effort teardown */ }
1608
- try { process.stderr.write(`[anthropic-oauth] empty stream (no message_start) — retry ${attemptIndex + 1}/${MAX_MIDSTREAM_RETRIES}\n`); } catch {}
1609
- continue;
1610
- }
1611
- // Truncated stream (message_start without message_stop): the
1612
- // partial result is discarded and re-requesting is safe (a
1613
- // pendingToolUse means the tool_use input JSON never completed).
1614
- // _classifyMidstreamError does not cover this; route it through
1615
- // the shared classifier so it inherits the cross-provider
1616
- // transient policy instead of escaping and killing the worker.
1617
- // Guard: parseSSEStream eagerly fires onToolCall and sets
1618
- // emittedToolCall=true at content_block_stop, BEFORE message_stop.
1619
- // If the stream truncates after that, retrying would
1620
- // double-execute the tool. Only retry when nothing was emitted
1621
- // yet; otherwise let the error surface.
1622
- if ((err?.truncatedStream === true || err?.code === 'TRUNCATED_STREAM')
1623
- && classifyError(err) === 'transient'
1624
- && !midState.emittedToolCall
1625
- && attemptIndex < MAX_MIDSTREAM_RETRIES) {
1626
- firstAttemptError = err;
1627
- firstAttemptClassifier = 'truncated_stream';
1628
- try { controller?.abort?.(err); } catch { /* best-effort teardown */ }
1629
- try { process.stderr.write(`[anthropic-oauth] truncated stream — retry ${attemptIndex + 1}/${MAX_MIDSTREAM_RETRIES}\n`); } catch {}
1630
- continue;
1631
- }
1632
- const classifier = _classifyMidstreamError(err, midState);
1633
- if (classifier && attemptIndex < MAX_MIDSTREAM_RETRIES) {
1634
- firstAttemptError = err;
1635
- firstAttemptClassifier = classifier;
1636
- try { controller?.abort?.(err); } catch (abortErr) {
1637
- /* best-effort stream teardown */
1638
- try { process.stderr.write(`[anthropic-oauth] abort on stream error failed: ${abortErr?.message ?? String(abortErr)}\n`); } catch {}
1639
- }
1640
- try {
1641
- process.stderr.write(`[anthropic-oauth] mid-stream recovered: retry ${attemptIndex + 1}/${MAX_MIDSTREAM_RETRIES} (cause: ${classifier})\n`);
1642
- } catch {}
1643
- continue;
1644
- }
1645
- if (attemptIndex > 0 && firstAttemptError) {
1646
- try { firstAttemptError.midstreamRetries = attemptIndex; } catch {}
1647
- try { firstAttemptError.midstreamClassifier = firstAttemptClassifier; } catch {}
1648
- throw firstAttemptError;
1649
- }
1650
- throw err;
1651
- } finally {
1652
- cleanupCancelHandler(cancelHandler);
1653
- }
1654
- }
1655
- throw firstAttemptError || new Error('Anthropic OAuth mid-stream retry: unreachable');
1656
- } finally {
1657
- totalTimeout.cleanup();
1658
- }
1659
- }
1660
-
1661
- async listModels() {
1662
- // Dynamic lookup via /v1/models — returns whatever Anthropic currently
1663
- // exposes for this OAuth account. Cached on disk with 24h TTL; falls
1664
- // back to the static MODELS list on any failure so the plugin still
1665
- // works offline or when Anthropic's /v1/models is momentarily down.
1666
- const cached = await _loadModelCache();
1667
- if (cached) {
1668
- _inMemoryCatalog = cached.slice();
1669
- return cached;
1670
- }
1671
- try {
1672
- const creds = await this.ensureAuth();
1673
- const res = await fetch('https://api.anthropic.com/v1/models', {
1674
- signal: AbortSignal.timeout(10_000),
1675
- method: 'GET',
1676
- headers: {
1677
- 'Authorization': `Bearer ${creds.accessToken}`,
1678
- 'anthropic-version': ANTHROPIC_VERSION,
1679
- 'anthropic-beta': OAUTH_BETA_HEADERS,
1680
- 'anthropic-dangerous-direct-browser-access': 'true',
1681
- 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
1682
- 'x-app': 'cli',
1683
- },
1684
- dispatcher: getLlmDispatcher(),
1685
- });
1686
- if (!res.ok) throw new Error(`list_models ${res.status}`);
1687
- const data = await res.json();
1688
- const items = Array.isArray(data?.data) ? data.data : [];
1689
- const normalized = items
1690
- .map(m => _normalizeAnthropicModel(m))
1691
- .filter(Boolean);
1692
- _markLatestByFamily(normalized);
1693
- // Enrich with LiteLLM catalog metadata (context, pricing, capabilities)
1694
- const enriched = await enrichModels(normalized);
1695
- await _saveModelCache(enriched);
1696
- return enriched;
1697
- } catch (err) {
1698
- process.stderr.write(`[anthropic-oauth] listModels fetch failed (${err.message})\n`);
1699
- // Fallback with full API model IDs. Short family tokens leaked
1700
- // through here would be accepted by setup and reintroduce the
1701
- // legacy shape. Env var override keeps this tracking defaults.
1702
- const opusId = process.env.ANTHROPIC_DEFAULT_OPUS_MODEL || 'claude-opus-4-8';
1703
- const sonnetId = process.env.ANTHROPIC_DEFAULT_SONNET_MODEL || 'claude-sonnet-4-6';
1704
- const haikuId = process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL || 'claude-haiku-4-5-20251001';
1705
- return [
1706
- { id: opusId, display: 'Opus (auto)', family: 'opus', provider: 'anthropic-oauth', tier: 'family', latest: true, contextWindow: 1000000 },
1707
- { id: sonnetId, display: 'Sonnet (auto)', family: 'sonnet', provider: 'anthropic-oauth', tier: 'family', latest: true, contextWindow: 1000000 },
1708
- { id: haikuId, display: 'Haiku (auto)', family: 'haiku', provider: 'anthropic-oauth', tier: 'family', latest: true, contextWindow: 200000 },
1709
- ];
1710
- }
1711
- }
1712
-
1713
- // Force a catalog refresh (ignores the 24h TTL). De-duped via
1714
- // _modelRefreshInFlight so concurrent callers share one HTTP round-trip.
1715
- // Returns the new catalog on success, null on failure.
1716
- async _refreshModelCache() {
1717
- if (_modelRefreshInFlight) return _modelRefreshInFlight;
1718
- _modelRefreshInFlight = (async () => {
1719
- try {
1720
- const creds = await this.ensureAuth();
1721
- const res = await fetch('https://api.anthropic.com/v1/models', {
1722
- signal: AbortSignal.timeout(10_000),
1723
- method: 'GET',
1724
- headers: {
1725
- 'Authorization': `Bearer ${creds.accessToken}`,
1726
- 'anthropic-version': ANTHROPIC_VERSION,
1727
- 'anthropic-beta': OAUTH_BETA_HEADERS,
1728
- 'anthropic-dangerous-direct-browser-access': 'true',
1729
- 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
1730
- 'x-app': 'cli',
1731
- },
1732
- dispatcher: getLlmDispatcher(),
1733
- });
1734
- if (!res.ok) throw new Error(`list_models ${res.status}`);
1735
- const data = await res.json();
1736
- const items = Array.isArray(data?.data) ? data.data : [];
1737
- const normalized = items
1738
- .map(m => _normalizeAnthropicModel(m))
1739
- .filter(Boolean);
1740
- _markLatestByFamily(normalized);
1741
- const enriched = await enrichModels(normalized);
1742
- await _saveModelCache(enriched);
1743
- process.stderr.write(`[anthropic-oauth] catalog refreshed (${enriched.length} models)\n`);
1744
- return enriched;
1745
- } catch (err) {
1746
- process.stderr.write(`[anthropic-oauth] catalog refresh failed (${err.message})\n`);
1747
- return null;
1748
- } finally {
1749
- _modelRefreshInFlight = null;
1750
- }
1751
- })();
1752
- return _modelRefreshInFlight;
1753
- }
1754
-
1755
- async isAvailable() {
1756
- return this.credentials !== null || loadCredentials() !== null;
1757
- }
1758
- }
1759
-
1760
- // --- Login flow (PKCE loopback, export for setup UI / CLI) ---
1761
-
1762
- function _oauthGeneratePKCE() {
1763
- const verifier = randomBytes(32).toString('base64url');
1764
- const challenge = createHash('sha256').update(verifier).digest('base64url');
1765
- return { verifier, challenge };
1766
- }
1767
-
1768
- function _oauthCredentialsWritePath() {
1769
- for (const p of credentialCandidates()) {
1770
- if (existsSync(p)) return p;
1771
- }
1772
- return DEFAULT_CREDENTIALS_PATH;
1773
- }
1774
-
1775
- function _oauthParseScopeField(scope) {
1776
- if (Array.isArray(scope)) return scope;
1777
- return String(scope || '').split(' ').filter(Boolean);
1778
- }
1779
-
1780
- export async function loginOAuth() {
1781
- const pkce = _oauthGeneratePKCE();
1782
- const state = randomBytes(32).toString('base64url');
1783
- const url = new URL(CLAUDE_AI_AUTHORIZE_URL);
1784
- url.searchParams.set('code', 'true');
1785
- url.searchParams.set('client_id', CLAUDE_CODE_CLIENT_ID);
1786
- url.searchParams.set('response_type', 'code');
1787
- url.searchParams.set('redirect_uri', OAUTH_REDIRECT_URI);
1788
- url.searchParams.set('scope', OAUTH_LOGIN_SCOPE);
1789
- url.searchParams.set('code_challenge', pkce.challenge);
1790
- url.searchParams.set('code_challenge_method', 'S256');
1791
- url.searchParams.set('state', state);
1792
- process.stderr.write(`\n[anthropic-oauth] Open this URL to log in with Claude:\n${url.toString()}\n\n`);
1793
- const { openInBrowser } = await import('../../../shared/open-url.mjs');
1794
- openInBrowser(url.toString());
1795
-
1796
- return new Promise((resolve) => {
1797
- const timeout = setTimeout(() => { server.close(); resolve(null); }, OAUTH_LOGIN_TIMEOUT_MS);
1798
- const server = createServer(async (req, res) => {
1799
- const u = new URL(req.url || '/', `http://${OAUTH_CALLBACK_HOST}:${OAUTH_CALLBACK_PORT}`);
1800
- if (u.pathname !== OAUTH_CALLBACK_PATH) {
1801
- res.writeHead(404);
1802
- res.end();
1803
- return;
1804
- }
1805
- const code = u.searchParams.get('code');
1806
- if (!code || u.searchParams.get('state') !== state) {
1807
- res.writeHead(400);
1808
- res.end('Invalid');
1809
- clearTimeout(timeout);
1810
- server.close();
1811
- resolve(null);
1812
- return;
1813
- }
1814
- res.writeHead(200, { 'Content-Type': 'text/html' });
1815
- res.end('<html><body><h2>Claude login successful! You can close this tab.</h2></body></html>');
1816
- clearTimeout(timeout);
1817
- server.close();
1818
- try {
1819
- const tokenRes = await fetch(TOKEN_URL, {
1820
- method: 'POST',
1821
- headers: {
1822
- 'Content-Type': 'application/json',
1823
- 'anthropic-dangerous-direct-browser-access': 'true',
1824
- 'user-agent': `claude-cli/${resolveCliVersion()} (external, sdk-cli)`,
1825
- },
1826
- body: JSON.stringify({
1827
- grant_type: 'authorization_code',
1828
- code,
1829
- redirect_uri: OAUTH_REDIRECT_URI,
1830
- client_id: CLAUDE_CODE_CLIENT_ID,
1831
- code_verifier: pkce.verifier,
1832
- state,
1833
- }),
1834
- redirect: 'error',
1835
- signal: AbortSignal.timeout(OAUTH_TOKEN_TIMEOUT_MS),
1836
- dispatcher: getLlmDispatcher(),
1837
- });
1838
- if (!tokenRes.ok) { resolve(null); return; }
1839
- const json = await tokenRes.json();
1840
- const accessToken = json?.access_token || json?.accessToken;
1841
- const refreshToken = json?.refresh_token || json?.refreshToken;
1842
- if (!accessToken || !refreshToken) { resolve(null); return; }
1843
- const expiresAt = _normalizeExpiresAt(json?.expires_at ?? json?.expiresAt)
1844
- || (typeof json?.expires_in === 'number' ? Date.now() + json.expires_in * 1000 : 0);
1845
- const scopes = _oauthParseScopeField(json?.scope);
1846
- const credPath = _oauthCredentialsWritePath();
1847
- let raw = {};
1848
- if (existsSync(credPath)) {
1849
- raw = JSON.parse(readFileSync(credPath, 'utf-8'));
1850
- }
1851
- const existingOauth = raw.claudeAiOauth || {};
1852
- raw.claudeAiOauth = {
1853
- ...existingOauth,
1854
- accessToken,
1855
- refreshToken,
1856
- expiresAt,
1857
- scopes,
1858
- subscriptionType: existingOauth.subscriptionType ?? null,
1859
- };
1860
- _saveCredentialsFile(credPath, raw);
1861
- resolve({
1862
- path: credPath,
1863
- accessToken,
1864
- refreshToken,
1865
- expiresAt,
1866
- scopes,
1867
- subscriptionType: raw.claudeAiOauth.subscriptionType,
1868
- });
1869
- } catch {
1870
- resolve(null);
1871
- }
1872
- });
1873
- server.listen(OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_HOST);
1874
- server.on('error', () => { clearTimeout(timeout); resolve(null); });
1875
- });
1876
- }
1877
-
1878
- // Additive exports for test harnesses.
1879
- // Lets the SSE parser be exercised in isolation against a synthetic
1880
- // ReadableStream without needing a live OAuth session.
1881
- export { parseSSEStream };