@otto-assistant/bridge 0.4.92

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 (483) hide show
  1. package/bin.js +2 -0
  2. package/dist/agent-model.e2e.test.js +755 -0
  3. package/dist/ai-tool-to-genai.js +233 -0
  4. package/dist/ai-tool-to-genai.test.js +267 -0
  5. package/dist/ai-tool.js +6 -0
  6. package/dist/anthropic-auth-plugin.js +728 -0
  7. package/dist/anthropic-auth-plugin.test.js +125 -0
  8. package/dist/anthropic-auth-state.js +231 -0
  9. package/dist/bin.js +90 -0
  10. package/dist/channel-management.js +227 -0
  11. package/dist/cli-parsing.test.js +137 -0
  12. package/dist/cli-send-thread.e2e.test.js +356 -0
  13. package/dist/cli.js +3276 -0
  14. package/dist/commands/abort.js +65 -0
  15. package/dist/commands/action-buttons.js +245 -0
  16. package/dist/commands/add-project.js +113 -0
  17. package/dist/commands/agent.js +335 -0
  18. package/dist/commands/ask-question.js +274 -0
  19. package/dist/commands/btw.js +116 -0
  20. package/dist/commands/compact.js +120 -0
  21. package/dist/commands/context-usage.js +140 -0
  22. package/dist/commands/create-new-project.js +130 -0
  23. package/dist/commands/diff.js +63 -0
  24. package/dist/commands/file-upload.js +275 -0
  25. package/dist/commands/fork.js +220 -0
  26. package/dist/commands/gemini-apikey.js +70 -0
  27. package/dist/commands/login.js +885 -0
  28. package/dist/commands/mcp.js +239 -0
  29. package/dist/commands/memory-snapshot.js +24 -0
  30. package/dist/commands/mention-mode.js +44 -0
  31. package/dist/commands/merge-worktree.js +159 -0
  32. package/dist/commands/model-variant.js +364 -0
  33. package/dist/commands/model.js +776 -0
  34. package/dist/commands/new-worktree.js +366 -0
  35. package/dist/commands/paginated-select.js +57 -0
  36. package/dist/commands/permissions.js +274 -0
  37. package/dist/commands/queue.js +206 -0
  38. package/dist/commands/remove-project.js +115 -0
  39. package/dist/commands/restart-opencode-server.js +127 -0
  40. package/dist/commands/resume.js +149 -0
  41. package/dist/commands/run-command.js +79 -0
  42. package/dist/commands/screenshare.js +303 -0
  43. package/dist/commands/screenshare.test.js +20 -0
  44. package/dist/commands/session-id.js +78 -0
  45. package/dist/commands/session.js +176 -0
  46. package/dist/commands/share.js +80 -0
  47. package/dist/commands/tasks.js +205 -0
  48. package/dist/commands/types.js +2 -0
  49. package/dist/commands/undo-redo.js +305 -0
  50. package/dist/commands/unset-model.js +138 -0
  51. package/dist/commands/upgrade.js +42 -0
  52. package/dist/commands/user-command.js +155 -0
  53. package/dist/commands/verbosity.js +125 -0
  54. package/dist/commands/worktree-settings.js +43 -0
  55. package/dist/commands/worktrees.js +410 -0
  56. package/dist/condense-memory.js +33 -0
  57. package/dist/config.js +94 -0
  58. package/dist/context-awareness-plugin.js +363 -0
  59. package/dist/context-awareness-plugin.test.js +124 -0
  60. package/dist/critique-utils.js +95 -0
  61. package/dist/database.js +1310 -0
  62. package/dist/db.js +251 -0
  63. package/dist/db.test.js +138 -0
  64. package/dist/debounce-timeout.js +28 -0
  65. package/dist/debounced-process-flush.js +77 -0
  66. package/dist/discord-bot.js +1008 -0
  67. package/dist/discord-command-registration.js +524 -0
  68. package/dist/discord-urls.js +81 -0
  69. package/dist/discord-utils.js +591 -0
  70. package/dist/discord-utils.test.js +134 -0
  71. package/dist/errors.js +157 -0
  72. package/dist/escape-backticks.test.js +429 -0
  73. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  74. package/dist/eventsource-parser.test.js +327 -0
  75. package/dist/exec-async.js +26 -0
  76. package/dist/external-opencode-sync.js +480 -0
  77. package/dist/format-tables.js +302 -0
  78. package/dist/format-tables.test.js +308 -0
  79. package/dist/forum-sync/config.js +79 -0
  80. package/dist/forum-sync/discord-operations.js +154 -0
  81. package/dist/forum-sync/index.js +5 -0
  82. package/dist/forum-sync/markdown.js +113 -0
  83. package/dist/forum-sync/sync-to-discord.js +417 -0
  84. package/dist/forum-sync/sync-to-files.js +190 -0
  85. package/dist/forum-sync/types.js +53 -0
  86. package/dist/forum-sync/watchers.js +307 -0
  87. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  88. package/dist/gateway-proxy.e2e.test.js +483 -0
  89. package/dist/genai-worker-wrapper.js +111 -0
  90. package/dist/genai-worker.js +311 -0
  91. package/dist/genai.js +232 -0
  92. package/dist/generated/browser.js +17 -0
  93. package/dist/generated/client.js +37 -0
  94. package/dist/generated/commonInputTypes.js +10 -0
  95. package/dist/generated/enums.js +52 -0
  96. package/dist/generated/internal/class.js +49 -0
  97. package/dist/generated/internal/prismaNamespace.js +253 -0
  98. package/dist/generated/internal/prismaNamespaceBrowser.js +223 -0
  99. package/dist/generated/models/bot_api_keys.js +1 -0
  100. package/dist/generated/models/bot_tokens.js +1 -0
  101. package/dist/generated/models/channel_agents.js +1 -0
  102. package/dist/generated/models/channel_directories.js +1 -0
  103. package/dist/generated/models/channel_mention_mode.js +1 -0
  104. package/dist/generated/models/channel_models.js +1 -0
  105. package/dist/generated/models/channel_verbosity.js +1 -0
  106. package/dist/generated/models/channel_worktrees.js +1 -0
  107. package/dist/generated/models/forum_sync_configs.js +1 -0
  108. package/dist/generated/models/global_models.js +1 -0
  109. package/dist/generated/models/ipc_requests.js +1 -0
  110. package/dist/generated/models/part_messages.js +1 -0
  111. package/dist/generated/models/scheduled_tasks.js +1 -0
  112. package/dist/generated/models/session_agents.js +1 -0
  113. package/dist/generated/models/session_events.js +1 -0
  114. package/dist/generated/models/session_models.js +1 -0
  115. package/dist/generated/models/session_start_sources.js +1 -0
  116. package/dist/generated/models/thread_sessions.js +1 -0
  117. package/dist/generated/models/thread_worktrees.js +1 -0
  118. package/dist/generated/models.js +1 -0
  119. package/dist/heap-monitor.js +122 -0
  120. package/dist/hrana-server.js +263 -0
  121. package/dist/hrana-server.test.js +370 -0
  122. package/dist/html-actions.js +123 -0
  123. package/dist/html-actions.test.js +70 -0
  124. package/dist/html-components.js +117 -0
  125. package/dist/html-components.test.js +34 -0
  126. package/dist/image-optimizer-plugin.js +153 -0
  127. package/dist/image-utils.js +112 -0
  128. package/dist/interaction-handler.js +397 -0
  129. package/dist/ipc-polling.js +252 -0
  130. package/dist/ipc-tools-plugin.js +193 -0
  131. package/dist/kimaki-digital-twin.e2e.test.js +161 -0
  132. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
  133. package/dist/kimaki-opencode-plugin.js +17 -0
  134. package/dist/kimaki-opencode-plugin.test.js +98 -0
  135. package/dist/limit-heading-depth.js +25 -0
  136. package/dist/limit-heading-depth.test.js +105 -0
  137. package/dist/logger.js +165 -0
  138. package/dist/markdown.js +342 -0
  139. package/dist/markdown.test.js +257 -0
  140. package/dist/message-finish-field.e2e.test.js +165 -0
  141. package/dist/message-formatting.js +413 -0
  142. package/dist/message-formatting.test.js +73 -0
  143. package/dist/message-preprocessing.js +330 -0
  144. package/dist/onboarding-tutorial.js +172 -0
  145. package/dist/onboarding-welcome.js +37 -0
  146. package/dist/openai-realtime.js +224 -0
  147. package/dist/opencode-command-detection.js +65 -0
  148. package/dist/opencode-command-detection.test.js +240 -0
  149. package/dist/opencode-command.js +129 -0
  150. package/dist/opencode-command.test.js +48 -0
  151. package/dist/opencode-interrupt-plugin.js +361 -0
  152. package/dist/opencode-interrupt-plugin.test.js +458 -0
  153. package/dist/opencode.js +861 -0
  154. package/dist/otto/branding.js +22 -0
  155. package/dist/otto/index.js +21 -0
  156. package/dist/parse-permission-rules.test.js +117 -0
  157. package/dist/patch-text-parser.js +97 -0
  158. package/dist/plugin-logger.js +59 -0
  159. package/dist/privacy-sanitizer.js +105 -0
  160. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  161. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  162. package/dist/queue-advanced-e2e-setup.js +786 -0
  163. package/dist/queue-advanced-footer.e2e.test.js +472 -0
  164. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  165. package/dist/queue-advanced-permissions-typing.e2e.test.js +180 -0
  166. package/dist/queue-advanced-question.e2e.test.js +261 -0
  167. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  168. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  169. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  170. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  171. package/dist/queue-question-select-drain.e2e.test.js +120 -0
  172. package/dist/runtime-idle-sweeper.js +52 -0
  173. package/dist/runtime-lifecycle.e2e.test.js +508 -0
  174. package/dist/sentry.js +23 -0
  175. package/dist/session-handler/agent-utils.js +67 -0
  176. package/dist/session-handler/event-stream-state.js +420 -0
  177. package/dist/session-handler/event-stream-state.test.js +563 -0
  178. package/dist/session-handler/model-utils.js +124 -0
  179. package/dist/session-handler/opencode-session-event-log.js +94 -0
  180. package/dist/session-handler/thread-runtime-state.js +104 -0
  181. package/dist/session-handler/thread-session-runtime.js +3258 -0
  182. package/dist/session-handler.js +9 -0
  183. package/dist/session-search.js +100 -0
  184. package/dist/session-search.test.js +40 -0
  185. package/dist/session-title-rename.test.js +80 -0
  186. package/dist/startup-service.js +153 -0
  187. package/dist/startup-time.e2e.test.js +296 -0
  188. package/dist/store.js +17 -0
  189. package/dist/system-message.js +613 -0
  190. package/dist/system-message.test.js +602 -0
  191. package/dist/task-runner.js +295 -0
  192. package/dist/task-schedule.js +209 -0
  193. package/dist/task-schedule.test.js +71 -0
  194. package/dist/test-utils.js +299 -0
  195. package/dist/thinking-utils.js +35 -0
  196. package/dist/thread-message-queue.e2e.test.js +999 -0
  197. package/dist/tools.js +357 -0
  198. package/dist/undo-redo.e2e.test.js +161 -0
  199. package/dist/unnest-code-blocks.js +146 -0
  200. package/dist/unnest-code-blocks.test.js +673 -0
  201. package/dist/upgrade.js +114 -0
  202. package/dist/utils.js +144 -0
  203. package/dist/voice-attachment.js +34 -0
  204. package/dist/voice-handler.js +646 -0
  205. package/dist/voice-message.e2e.test.js +1021 -0
  206. package/dist/voice.js +447 -0
  207. package/dist/voice.test.js +235 -0
  208. package/dist/wait-session.js +94 -0
  209. package/dist/websockify.js +69 -0
  210. package/dist/worker-types.js +4 -0
  211. package/dist/worktree-lifecycle.e2e.test.js +308 -0
  212. package/dist/worktree-utils.js +3 -0
  213. package/dist/worktrees.js +929 -0
  214. package/dist/worktrees.test.js +189 -0
  215. package/dist/xml.js +92 -0
  216. package/dist/xml.test.js +32 -0
  217. package/package.json +98 -0
  218. package/schema.prisma +295 -0
  219. package/skills/batch/SKILL.md +87 -0
  220. package/skills/critique/SKILL.md +112 -0
  221. package/skills/egaki/SKILL.md +100 -0
  222. package/skills/errore/SKILL.md +647 -0
  223. package/skills/event-sourcing-state/SKILL.md +252 -0
  224. package/skills/gitchamber/SKILL.md +93 -0
  225. package/skills/goke/SKILL.md +644 -0
  226. package/skills/jitter/EDITOR.md +219 -0
  227. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  228. package/skills/jitter/SKILL.md +158 -0
  229. package/skills/jitter/jitter-clipboard.json +1042 -0
  230. package/skills/jitter/package.json +14 -0
  231. package/skills/jitter/tsconfig.json +15 -0
  232. package/skills/jitter/utils/actions.ts +212 -0
  233. package/skills/jitter/utils/export.ts +114 -0
  234. package/skills/jitter/utils/index.ts +141 -0
  235. package/skills/jitter/utils/snapshot.ts +154 -0
  236. package/skills/jitter/utils/traverse.ts +246 -0
  237. package/skills/jitter/utils/types.ts +279 -0
  238. package/skills/jitter/utils/wait.ts +133 -0
  239. package/skills/lintcn/SKILL.md +873 -0
  240. package/skills/new-skill/SKILL.md +211 -0
  241. package/skills/npm-package/SKILL.md +239 -0
  242. package/skills/playwriter/SKILL.md +35 -0
  243. package/skills/proxyman/SKILL.md +215 -0
  244. package/skills/security-review/SKILL.md +208 -0
  245. package/skills/simplify/SKILL.md +58 -0
  246. package/skills/spiceflow/SKILL.md +14 -0
  247. package/skills/termcast/SKILL.md +945 -0
  248. package/skills/tuistory/SKILL.md +250 -0
  249. package/skills/usecomputer/SKILL.md +264 -0
  250. package/skills/x-articles/SKILL.md +554 -0
  251. package/skills/zele/SKILL.md +112 -0
  252. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  253. package/src/agent-model.e2e.test.ts +976 -0
  254. package/src/ai-tool-to-genai.test.ts +296 -0
  255. package/src/ai-tool-to-genai.ts +283 -0
  256. package/src/ai-tool.ts +39 -0
  257. package/src/anthropic-auth-plugin.test.ts +159 -0
  258. package/src/anthropic-auth-plugin.ts +861 -0
  259. package/src/anthropic-auth-state.ts +282 -0
  260. package/src/bin.ts +111 -0
  261. package/src/channel-management.ts +334 -0
  262. package/src/cli-parsing.test.ts +195 -0
  263. package/src/cli-send-thread.e2e.test.ts +464 -0
  264. package/src/cli.ts +4581 -0
  265. package/src/commands/abort.ts +89 -0
  266. package/src/commands/action-buttons.ts +364 -0
  267. package/src/commands/add-project.ts +149 -0
  268. package/src/commands/agent.ts +473 -0
  269. package/src/commands/ask-question.ts +390 -0
  270. package/src/commands/btw.ts +164 -0
  271. package/src/commands/compact.ts +157 -0
  272. package/src/commands/context-usage.ts +199 -0
  273. package/src/commands/create-new-project.ts +190 -0
  274. package/src/commands/diff.ts +91 -0
  275. package/src/commands/file-upload.ts +389 -0
  276. package/src/commands/fork.ts +321 -0
  277. package/src/commands/gemini-apikey.ts +104 -0
  278. package/src/commands/login.ts +1173 -0
  279. package/src/commands/mcp.ts +307 -0
  280. package/src/commands/memory-snapshot.ts +30 -0
  281. package/src/commands/mention-mode.ts +68 -0
  282. package/src/commands/merge-worktree.ts +223 -0
  283. package/src/commands/model-variant.ts +483 -0
  284. package/src/commands/model.ts +1053 -0
  285. package/src/commands/new-worktree.ts +510 -0
  286. package/src/commands/paginated-select.ts +81 -0
  287. package/src/commands/permissions.ts +397 -0
  288. package/src/commands/queue.ts +271 -0
  289. package/src/commands/remove-project.ts +155 -0
  290. package/src/commands/restart-opencode-server.ts +162 -0
  291. package/src/commands/resume.ts +230 -0
  292. package/src/commands/run-command.ts +123 -0
  293. package/src/commands/screenshare.test.ts +30 -0
  294. package/src/commands/screenshare.ts +366 -0
  295. package/src/commands/session-id.ts +109 -0
  296. package/src/commands/session.ts +227 -0
  297. package/src/commands/share.ts +106 -0
  298. package/src/commands/tasks.ts +293 -0
  299. package/src/commands/types.ts +25 -0
  300. package/src/commands/undo-redo.ts +386 -0
  301. package/src/commands/unset-model.ts +173 -0
  302. package/src/commands/upgrade.ts +52 -0
  303. package/src/commands/user-command.ts +198 -0
  304. package/src/commands/verbosity.ts +173 -0
  305. package/src/commands/worktree-settings.ts +70 -0
  306. package/src/commands/worktrees.ts +552 -0
  307. package/src/condense-memory.ts +36 -0
  308. package/src/config.ts +111 -0
  309. package/src/context-awareness-plugin.test.ts +142 -0
  310. package/src/context-awareness-plugin.ts +510 -0
  311. package/src/critique-utils.ts +139 -0
  312. package/src/database.ts +1876 -0
  313. package/src/db.test.ts +162 -0
  314. package/src/db.ts +286 -0
  315. package/src/debounce-timeout.ts +43 -0
  316. package/src/debounced-process-flush.ts +104 -0
  317. package/src/discord-bot.ts +1330 -0
  318. package/src/discord-command-registration.ts +693 -0
  319. package/src/discord-urls.ts +88 -0
  320. package/src/discord-utils.test.ts +153 -0
  321. package/src/discord-utils.ts +800 -0
  322. package/src/errors.ts +201 -0
  323. package/src/escape-backticks.test.ts +469 -0
  324. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  325. package/src/eventsource-parser.test.ts +351 -0
  326. package/src/exec-async.ts +35 -0
  327. package/src/external-opencode-sync.ts +685 -0
  328. package/src/format-tables.test.ts +335 -0
  329. package/src/format-tables.ts +445 -0
  330. package/src/forum-sync/config.ts +92 -0
  331. package/src/forum-sync/discord-operations.ts +241 -0
  332. package/src/forum-sync/index.ts +9 -0
  333. package/src/forum-sync/markdown.ts +172 -0
  334. package/src/forum-sync/sync-to-discord.ts +595 -0
  335. package/src/forum-sync/sync-to-files.ts +294 -0
  336. package/src/forum-sync/types.ts +175 -0
  337. package/src/forum-sync/watchers.ts +454 -0
  338. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  339. package/src/gateway-proxy.e2e.test.ts +640 -0
  340. package/src/genai-worker-wrapper.ts +164 -0
  341. package/src/genai-worker.ts +386 -0
  342. package/src/genai.ts +321 -0
  343. package/src/generated/browser.ts +114 -0
  344. package/src/generated/client.ts +138 -0
  345. package/src/generated/commonInputTypes.ts +736 -0
  346. package/src/generated/enums.ts +88 -0
  347. package/src/generated/internal/class.ts +384 -0
  348. package/src/generated/internal/prismaNamespace.ts +2386 -0
  349. package/src/generated/internal/prismaNamespaceBrowser.ts +326 -0
  350. package/src/generated/models/bot_api_keys.ts +1288 -0
  351. package/src/generated/models/bot_tokens.ts +1656 -0
  352. package/src/generated/models/channel_agents.ts +1256 -0
  353. package/src/generated/models/channel_directories.ts +1859 -0
  354. package/src/generated/models/channel_mention_mode.ts +1300 -0
  355. package/src/generated/models/channel_models.ts +1288 -0
  356. package/src/generated/models/channel_verbosity.ts +1228 -0
  357. package/src/generated/models/channel_worktrees.ts +1300 -0
  358. package/src/generated/models/forum_sync_configs.ts +1452 -0
  359. package/src/generated/models/global_models.ts +1288 -0
  360. package/src/generated/models/ipc_requests.ts +1485 -0
  361. package/src/generated/models/part_messages.ts +1302 -0
  362. package/src/generated/models/scheduled_tasks.ts +2320 -0
  363. package/src/generated/models/session_agents.ts +1086 -0
  364. package/src/generated/models/session_events.ts +1439 -0
  365. package/src/generated/models/session_models.ts +1114 -0
  366. package/src/generated/models/session_start_sources.ts +1408 -0
  367. package/src/generated/models/thread_sessions.ts +1781 -0
  368. package/src/generated/models/thread_worktrees.ts +1356 -0
  369. package/src/generated/models.ts +30 -0
  370. package/src/heap-monitor.ts +152 -0
  371. package/src/hrana-server.test.ts +434 -0
  372. package/src/hrana-server.ts +314 -0
  373. package/src/html-actions.test.ts +87 -0
  374. package/src/html-actions.ts +174 -0
  375. package/src/html-components.test.ts +38 -0
  376. package/src/html-components.ts +181 -0
  377. package/src/image-optimizer-plugin.ts +194 -0
  378. package/src/image-utils.ts +149 -0
  379. package/src/interaction-handler.ts +576 -0
  380. package/src/ipc-polling.ts +326 -0
  381. package/src/ipc-tools-plugin.ts +236 -0
  382. package/src/kimaki-digital-twin.e2e.test.ts +199 -0
  383. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +109 -0
  384. package/src/kimaki-opencode-plugin.test.ts +108 -0
  385. package/src/kimaki-opencode-plugin.ts +18 -0
  386. package/src/limit-heading-depth.test.ts +116 -0
  387. package/src/limit-heading-depth.ts +26 -0
  388. package/src/logger.ts +208 -0
  389. package/src/markdown.test.ts +308 -0
  390. package/src/markdown.ts +410 -0
  391. package/src/message-finish-field.e2e.test.ts +192 -0
  392. package/src/message-formatting.test.ts +81 -0
  393. package/src/message-formatting.ts +533 -0
  394. package/src/message-preprocessing.ts +455 -0
  395. package/src/onboarding-tutorial.ts +176 -0
  396. package/src/onboarding-welcome.ts +49 -0
  397. package/src/openai-realtime.ts +358 -0
  398. package/src/opencode-command-detection.test.ts +307 -0
  399. package/src/opencode-command-detection.ts +76 -0
  400. package/src/opencode-command.test.ts +70 -0
  401. package/src/opencode-command.ts +188 -0
  402. package/src/opencode-interrupt-plugin.test.ts +677 -0
  403. package/src/opencode-interrupt-plugin.ts +477 -0
  404. package/src/opencode.ts +1110 -0
  405. package/src/otto/branding.ts +23 -0
  406. package/src/otto/index.ts +22 -0
  407. package/src/parse-permission-rules.test.ts +127 -0
  408. package/src/patch-text-parser.ts +107 -0
  409. package/src/plugin-logger.ts +68 -0
  410. package/src/privacy-sanitizer.ts +142 -0
  411. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  412. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  413. package/src/queue-advanced-e2e-setup.ts +873 -0
  414. package/src/queue-advanced-footer.e2e.test.ts +576 -0
  415. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  416. package/src/queue-advanced-permissions-typing.e2e.test.ts +245 -0
  417. package/src/queue-advanced-question.e2e.test.ts +316 -0
  418. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  419. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  420. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  421. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  422. package/src/queue-question-select-drain.e2e.test.ts +152 -0
  423. package/src/runtime-idle-sweeper.ts +76 -0
  424. package/src/runtime-lifecycle.e2e.test.ts +641 -0
  425. package/src/schema.sql +173 -0
  426. package/src/sentry.ts +26 -0
  427. package/src/session-handler/agent-utils.ts +97 -0
  428. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  429. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  430. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  431. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  432. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  433. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  434. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  435. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  436. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  437. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  438. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  439. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  440. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  441. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  442. package/src/session-handler/event-stream-state.test.ts +645 -0
  443. package/src/session-handler/event-stream-state.ts +608 -0
  444. package/src/session-handler/model-utils.ts +183 -0
  445. package/src/session-handler/opencode-session-event-log.ts +130 -0
  446. package/src/session-handler/thread-runtime-state.ts +212 -0
  447. package/src/session-handler/thread-session-runtime.ts +4281 -0
  448. package/src/session-handler.ts +15 -0
  449. package/src/session-search.test.ts +50 -0
  450. package/src/session-search.ts +148 -0
  451. package/src/session-title-rename.test.ts +112 -0
  452. package/src/startup-service.ts +200 -0
  453. package/src/startup-time.e2e.test.ts +373 -0
  454. package/src/store.ts +122 -0
  455. package/src/system-message.test.ts +612 -0
  456. package/src/system-message.ts +723 -0
  457. package/src/task-runner.ts +421 -0
  458. package/src/task-schedule.test.ts +84 -0
  459. package/src/task-schedule.ts +311 -0
  460. package/src/test-utils.ts +435 -0
  461. package/src/thinking-utils.ts +61 -0
  462. package/src/thread-message-queue.e2e.test.ts +1219 -0
  463. package/src/tools.ts +430 -0
  464. package/src/undici.d.ts +12 -0
  465. package/src/undo-redo.e2e.test.ts +209 -0
  466. package/src/unnest-code-blocks.test.ts +713 -0
  467. package/src/unnest-code-blocks.ts +185 -0
  468. package/src/upgrade.ts +127 -0
  469. package/src/utils.ts +212 -0
  470. package/src/voice-attachment.ts +51 -0
  471. package/src/voice-handler.ts +908 -0
  472. package/src/voice-message.e2e.test.ts +1255 -0
  473. package/src/voice.test.ts +281 -0
  474. package/src/voice.ts +627 -0
  475. package/src/wait-session.ts +147 -0
  476. package/src/websockify.ts +101 -0
  477. package/src/worker-types.ts +64 -0
  478. package/src/worktree-lifecycle.e2e.test.ts +391 -0
  479. package/src/worktree-utils.ts +4 -0
  480. package/src/worktrees.test.ts +223 -0
  481. package/src/worktrees.ts +1294 -0
  482. package/src/xml.test.ts +38 -0
  483. package/src/xml.ts +121 -0
@@ -0,0 +1,728 @@
1
+ /**
2
+ * Anthropic OAuth authentication plugin for OpenCode.
3
+ *
4
+ * If you're copy-pasting this plugin into your OpenCode config folder,
5
+ * you need to install the runtime dependencies first:
6
+ *
7
+ * cd ~/.config/opencode
8
+ * bun init -y
9
+ * bun add proper-lockfile
10
+ *
11
+ * Handles three concerns:
12
+ * 1. OAuth login + token refresh (PKCE flow against claude.ai)
13
+ * 2. Request/response rewriting (tool names, system prompt, beta headers)
14
+ * so the Anthropic API treats requests as Claude Code CLI requests.
15
+ * 3. Multi-account OAuth rotation after Anthropic rate-limit/auth failures.
16
+ *
17
+ * Login mode is chosen from environment:
18
+ * - `KIMAKI` set: remote-first pasted callback URL/raw code flow
19
+ * - otherwise: standard localhost auto-complete flow
20
+ *
21
+ * Source references:
22
+ * - https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/anthropic.ts
23
+ * - https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts
24
+ */
25
+ import { loadAccountStore, rememberAnthropicOAuth, rotateAnthropicAccount, saveAccountStore, setAnthropicAuth, shouldRotateAuth, upsertAccount, withAuthStateLock, } from './anthropic-auth-state.js';
26
+ // PKCE (Proof Key for Code Exchange) using Web Crypto API.
27
+ // Reference: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/pkce.ts
28
+ function base64urlEncode(bytes) {
29
+ let binary = '';
30
+ for (const byte of bytes) {
31
+ binary += String.fromCharCode(byte);
32
+ }
33
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
34
+ }
35
+ async function generatePKCE() {
36
+ const verifierBytes = new Uint8Array(32);
37
+ crypto.getRandomValues(verifierBytes);
38
+ const verifier = base64urlEncode(verifierBytes);
39
+ const data = new TextEncoder().encode(verifier);
40
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
41
+ const challenge = base64urlEncode(new Uint8Array(hashBuffer));
42
+ return { verifier, challenge };
43
+ }
44
+ import { spawn } from 'node:child_process';
45
+ import { createServer } from 'node:http';
46
+ // --- Constants ---
47
+ const CLIENT_ID = (() => {
48
+ const encoded = 'OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl';
49
+ return typeof atob === 'function'
50
+ ? atob(encoded)
51
+ : Buffer.from(encoded, 'base64').toString('utf8');
52
+ })();
53
+ const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
54
+ const CREATE_API_KEY_URL = 'https://api.anthropic.com/api/oauth/claude_cli/create_api_key';
55
+ const CALLBACK_PORT = 53692;
56
+ const CALLBACK_PATH = '/callback';
57
+ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
58
+ const SCOPES = 'org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload';
59
+ const OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
60
+ const CLAUDE_CODE_VERSION = '2.1.75';
61
+ const CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
62
+ const OPENCODE_IDENTITY = 'You are OpenCode, the best coding agent on the planet.';
63
+ const CLAUDE_CODE_BETA = 'claude-code-20250219';
64
+ const OAUTH_BETA = 'oauth-2025-04-20';
65
+ const FINE_GRAINED_TOOL_STREAMING_BETA = 'fine-grained-tool-streaming-2025-05-14';
66
+ const INTERLEAVED_THINKING_BETA = 'interleaved-thinking-2025-05-14';
67
+ const ANTHROPIC_HOSTS = new Set([
68
+ 'api.anthropic.com',
69
+ 'claude.ai',
70
+ 'console.anthropic.com',
71
+ 'platform.claude.com',
72
+ ]);
73
+ const OPENCODE_TO_CLAUDE_CODE_TOOL_NAME = {
74
+ bash: 'Bash',
75
+ edit: 'Edit',
76
+ glob: 'Glob',
77
+ grep: 'Grep',
78
+ question: 'AskUserQuestion',
79
+ read: 'Read',
80
+ skill: 'Skill',
81
+ task: 'Task',
82
+ todowrite: 'TodoWrite',
83
+ webfetch: 'WebFetch',
84
+ websearch: 'WebSearch',
85
+ write: 'Write',
86
+ };
87
+ // --- HTTP helpers ---
88
+ // Claude OAuth token exchange can 429 when this runs inside the opencode auth
89
+ // process, even with the same payload that succeeds in a plain Node process.
90
+ // Run these OAuth-only HTTP calls in an isolated Node child to avoid whatever
91
+ // parent-process runtime state is affecting the in-process requests.
92
+ async function requestText(urlString, options) {
93
+ return new Promise((resolve, reject) => {
94
+ const payload = JSON.stringify({
95
+ body: options.body,
96
+ headers: options.headers,
97
+ method: options.method,
98
+ url: urlString,
99
+ });
100
+ const child = spawn('node', [
101
+ '-e',
102
+ `
103
+ const input = JSON.parse(process.argv[1]);
104
+ (async () => {
105
+ const response = await fetch(input.url, {
106
+ method: input.method,
107
+ headers: input.headers,
108
+ body: input.body,
109
+ });
110
+ const text = await response.text();
111
+ if (!response.ok) {
112
+ console.error(JSON.stringify({ status: response.status, body: text }));
113
+ process.exit(1);
114
+ }
115
+ process.stdout.write(text);
116
+ })().catch((error) => {
117
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
118
+ process.exit(1);
119
+ });
120
+ `.trim(),
121
+ payload,
122
+ ], {
123
+ stdio: ['ignore', 'pipe', 'pipe'],
124
+ });
125
+ let stdout = '';
126
+ let stderr = '';
127
+ const timeout = setTimeout(() => {
128
+ child.kill();
129
+ reject(new Error(`Request timed out. url=${urlString}`));
130
+ }, 30_000);
131
+ child.stdout.on('data', (chunk) => {
132
+ stdout += String(chunk);
133
+ });
134
+ child.stderr.on('data', (chunk) => {
135
+ stderr += String(chunk);
136
+ });
137
+ child.on('error', (error) => {
138
+ clearTimeout(timeout);
139
+ reject(error);
140
+ });
141
+ child.on('close', (code) => {
142
+ clearTimeout(timeout);
143
+ if (code !== 0) {
144
+ let details = stderr.trim();
145
+ try {
146
+ const parsed = JSON.parse(details);
147
+ if (typeof parsed.status === 'number') {
148
+ reject(new Error(`HTTP ${parsed.status} from ${urlString}: ${parsed.body ?? ''}`));
149
+ return;
150
+ }
151
+ }
152
+ catch {
153
+ // fall back to raw stderr
154
+ }
155
+ reject(new Error(details || `Node helper exited with code ${code}`));
156
+ return;
157
+ }
158
+ resolve(stdout);
159
+ });
160
+ });
161
+ }
162
+ async function postJson(url, body) {
163
+ const requestBody = JSON.stringify(body);
164
+ const responseText = await requestText(url, {
165
+ method: 'POST',
166
+ headers: {
167
+ Accept: 'application/json',
168
+ 'Content-Length': String(Buffer.byteLength(requestBody)),
169
+ 'Content-Type': 'application/json',
170
+ },
171
+ body: requestBody,
172
+ });
173
+ return JSON.parse(responseText);
174
+ }
175
+ const pendingRefresh = new Map();
176
+ // --- OAuth token exchange & refresh ---
177
+ function parseTokenResponse(json) {
178
+ const data = json;
179
+ if (!data.access_token || !data.refresh_token) {
180
+ throw new Error(`Invalid token response: ${JSON.stringify(json)}`);
181
+ }
182
+ return data;
183
+ }
184
+ function tokenExpiry(expiresIn) {
185
+ return Date.now() + expiresIn * 1000 - 5 * 60 * 1000;
186
+ }
187
+ async function exchangeAuthorizationCode(code, state, verifier, redirectUri) {
188
+ const json = await postJson(TOKEN_URL, {
189
+ grant_type: 'authorization_code',
190
+ client_id: CLIENT_ID,
191
+ code,
192
+ state,
193
+ redirect_uri: redirectUri,
194
+ code_verifier: verifier,
195
+ });
196
+ const data = parseTokenResponse(json);
197
+ return {
198
+ type: 'success',
199
+ refresh: data.refresh_token,
200
+ access: data.access_token,
201
+ expires: tokenExpiry(data.expires_in),
202
+ };
203
+ }
204
+ async function refreshAnthropicToken(refreshToken) {
205
+ const json = await postJson(TOKEN_URL, {
206
+ grant_type: 'refresh_token',
207
+ client_id: CLIENT_ID,
208
+ refresh_token: refreshToken,
209
+ });
210
+ const data = parseTokenResponse(json);
211
+ return {
212
+ type: 'oauth',
213
+ refresh: data.refresh_token,
214
+ access: data.access_token,
215
+ expires: tokenExpiry(data.expires_in),
216
+ };
217
+ }
218
+ async function createApiKey(accessToken) {
219
+ const responseText = await requestText(CREATE_API_KEY_URL, {
220
+ method: 'POST',
221
+ headers: {
222
+ Accept: 'application/json',
223
+ authorization: `Bearer ${accessToken}`,
224
+ 'Content-Type': 'application/json',
225
+ },
226
+ });
227
+ const json = JSON.parse(responseText);
228
+ return { type: 'success', key: json.raw_key };
229
+ }
230
+ async function startCallbackServer(expectedState) {
231
+ return new Promise((resolve, reject) => {
232
+ let settle;
233
+ let settled = false;
234
+ const waitPromise = new Promise((res) => {
235
+ settle = (v) => {
236
+ if (settled)
237
+ return;
238
+ settled = true;
239
+ res(v);
240
+ };
241
+ });
242
+ const server = createServer((req, res) => {
243
+ try {
244
+ const url = new URL(req.url || '', 'http://localhost');
245
+ if (url.pathname !== CALLBACK_PATH) {
246
+ res.writeHead(404).end('Not found');
247
+ return;
248
+ }
249
+ const code = url.searchParams.get('code');
250
+ const state = url.searchParams.get('state');
251
+ const error = url.searchParams.get('error');
252
+ if (error || !code || !state || state !== expectedState) {
253
+ res.writeHead(400).end('Authentication failed: ' + (error || 'missing code/state'));
254
+ return;
255
+ }
256
+ res
257
+ .writeHead(200, { 'Content-Type': 'text/plain' })
258
+ .end('Authentication successful. You can close this window.');
259
+ settle?.({ code, state });
260
+ }
261
+ catch {
262
+ res.writeHead(500).end('Internal error');
263
+ }
264
+ });
265
+ server.once('error', reject);
266
+ server.listen(CALLBACK_PORT, '127.0.0.1', () => {
267
+ resolve({
268
+ server,
269
+ cancelWait: () => {
270
+ settle?.(null);
271
+ },
272
+ waitForCode: () => waitPromise,
273
+ });
274
+ });
275
+ });
276
+ }
277
+ function closeServer(server) {
278
+ return new Promise((resolve) => {
279
+ server.close(() => {
280
+ resolve();
281
+ });
282
+ });
283
+ }
284
+ // --- Authorization flow ---
285
+ // Unified flow: beginAuthorizationFlow starts PKCE + callback server,
286
+ // then waitForCallback handles both auto (localhost) and manual (pasted code) paths.
287
+ async function beginAuthorizationFlow() {
288
+ const pkce = await generatePKCE();
289
+ const callbackServer = await startCallbackServer(pkce.verifier);
290
+ const authParams = new URLSearchParams({
291
+ code: 'true',
292
+ client_id: CLIENT_ID,
293
+ response_type: 'code',
294
+ redirect_uri: REDIRECT_URI,
295
+ scope: SCOPES,
296
+ code_challenge: pkce.challenge,
297
+ code_challenge_method: 'S256',
298
+ state: pkce.verifier,
299
+ });
300
+ return {
301
+ url: `https://claude.ai/oauth/authorize?${authParams.toString()}`,
302
+ verifier: pkce.verifier,
303
+ callbackServer,
304
+ };
305
+ }
306
+ async function waitForCallback(callbackServer, manualInput) {
307
+ try {
308
+ // Try localhost callback first (instant check)
309
+ const quick = await Promise.race([
310
+ callbackServer.waitForCode(),
311
+ new Promise((r) => {
312
+ setTimeout(() => {
313
+ r(null);
314
+ }, 50);
315
+ }),
316
+ ]);
317
+ if (quick?.code)
318
+ return quick;
319
+ // If manual input was provided, parse it
320
+ const trimmed = manualInput?.trim();
321
+ if (trimmed) {
322
+ return parseManualInput(trimmed);
323
+ }
324
+ // Wait for localhost callback with timeout
325
+ const result = await Promise.race([
326
+ callbackServer.waitForCode(),
327
+ new Promise((r) => {
328
+ setTimeout(() => {
329
+ r(null);
330
+ }, OAUTH_TIMEOUT_MS);
331
+ }),
332
+ ]);
333
+ if (!result?.code) {
334
+ throw new Error('Timed out waiting for OAuth callback');
335
+ }
336
+ return result;
337
+ }
338
+ finally {
339
+ callbackServer.cancelWait();
340
+ await closeServer(callbackServer.server);
341
+ }
342
+ }
343
+ function parseManualInput(input) {
344
+ try {
345
+ const url = new URL(input);
346
+ const code = url.searchParams.get('code');
347
+ const state = url.searchParams.get('state');
348
+ if (code)
349
+ return { code, state: state || '' };
350
+ }
351
+ catch {
352
+ // not a URL
353
+ }
354
+ if (input.includes('#')) {
355
+ const [code = '', state = ''] = input.split('#', 2);
356
+ return { code, state };
357
+ }
358
+ if (input.includes('code=')) {
359
+ const params = new URLSearchParams(input);
360
+ const code = params.get('code');
361
+ if (code)
362
+ return { code, state: params.get('state') || '' };
363
+ }
364
+ return { code: input, state: '' };
365
+ }
366
+ // Unified authorize handler: returns either OAuth tokens or an API key,
367
+ // for both auto and remote-first modes.
368
+ function buildAuthorizeHandler(mode) {
369
+ return async () => {
370
+ const auth = await beginAuthorizationFlow();
371
+ const isRemote = Boolean(process.env.KIMAKI);
372
+ let pendingAuthResult;
373
+ const finalize = async (result) => {
374
+ const verifier = auth.verifier;
375
+ const creds = await exchangeAuthorizationCode(result.code, result.state || verifier, verifier, REDIRECT_URI);
376
+ if (mode === 'apikey') {
377
+ return createApiKey(creds.access);
378
+ }
379
+ await rememberAnthropicOAuth({
380
+ type: 'oauth',
381
+ refresh: creds.refresh,
382
+ access: creds.access,
383
+ expires: creds.expires,
384
+ });
385
+ return creds;
386
+ };
387
+ if (!isRemote) {
388
+ return {
389
+ url: auth.url,
390
+ instructions: 'Complete login in your browser on this machine. OpenCode will catch the localhost callback automatically.',
391
+ method: 'auto',
392
+ callback: async () => {
393
+ pendingAuthResult ??= (async () => {
394
+ try {
395
+ const result = await waitForCallback(auth.callbackServer);
396
+ return await finalize(result);
397
+ }
398
+ catch (error) {
399
+ console.error(`[anthropic-auth] ${error}`);
400
+ return { type: 'failed' };
401
+ }
402
+ })();
403
+ return pendingAuthResult;
404
+ },
405
+ };
406
+ }
407
+ return {
408
+ url: auth.url,
409
+ instructions: 'Complete login in your browser, then paste the final redirect URL from the address bar here. Pasting just the authorization code also works.',
410
+ method: 'code',
411
+ callback: async (input) => {
412
+ pendingAuthResult ??= (async () => {
413
+ try {
414
+ const result = await waitForCallback(auth.callbackServer, input);
415
+ return await finalize(result);
416
+ }
417
+ catch (error) {
418
+ console.error(`[anthropic-auth] ${error}`);
419
+ return { type: 'failed' };
420
+ }
421
+ })();
422
+ return pendingAuthResult;
423
+ },
424
+ };
425
+ };
426
+ }
427
+ // --- Request/response rewriting ---
428
+ // Renames opencode tool names to Claude Code tool names in requests,
429
+ // and reverses the mapping in streamed responses.
430
+ function toClaudeCodeToolName(name) {
431
+ return OPENCODE_TO_CLAUDE_CODE_TOOL_NAME[name.toLowerCase()] ?? name;
432
+ }
433
+ function sanitizeSystemText(text) {
434
+ return text.replaceAll(OPENCODE_IDENTITY, CLAUDE_CODE_IDENTITY);
435
+ }
436
+ function prependClaudeCodeIdentity(system) {
437
+ const identityBlock = { type: 'text', text: CLAUDE_CODE_IDENTITY };
438
+ if (typeof system === 'undefined')
439
+ return [identityBlock];
440
+ if (typeof system === 'string') {
441
+ const sanitized = sanitizeSystemText(system);
442
+ if (sanitized === CLAUDE_CODE_IDENTITY)
443
+ return [identityBlock];
444
+ return [identityBlock, { type: 'text', text: sanitized }];
445
+ }
446
+ if (!Array.isArray(system))
447
+ return [identityBlock, system];
448
+ const sanitized = system.map((item) => {
449
+ if (typeof item === 'string')
450
+ return { type: 'text', text: sanitizeSystemText(item) };
451
+ if (item && typeof item === 'object' && item.type === 'text') {
452
+ const text = item.text;
453
+ if (typeof text === 'string') {
454
+ return { ...item, text: sanitizeSystemText(text) };
455
+ }
456
+ }
457
+ return item;
458
+ });
459
+ const first = sanitized[0];
460
+ if (first &&
461
+ typeof first === 'object' &&
462
+ first.type === 'text' &&
463
+ first.text === CLAUDE_CODE_IDENTITY) {
464
+ return sanitized;
465
+ }
466
+ return [identityBlock, ...sanitized];
467
+ }
468
+ function rewriteRequestPayload(body) {
469
+ if (!body)
470
+ return { body, modelId: undefined, reverseToolNameMap: new Map() };
471
+ try {
472
+ const payload = JSON.parse(body);
473
+ const reverseToolNameMap = new Map();
474
+ const modelId = typeof payload.model === 'string' ? payload.model : undefined;
475
+ // Build reverse map and rename tools
476
+ if (Array.isArray(payload.tools)) {
477
+ payload.tools = payload.tools.map((tool) => {
478
+ if (!tool || typeof tool !== 'object')
479
+ return tool;
480
+ const name = tool.name;
481
+ if (typeof name !== 'string')
482
+ return tool;
483
+ const mapped = toClaudeCodeToolName(name);
484
+ reverseToolNameMap.set(mapped, name);
485
+ return { ...tool, name: mapped };
486
+ });
487
+ }
488
+ // Rename system prompt
489
+ payload.system = prependClaudeCodeIdentity(payload.system);
490
+ // Rename tool_choice
491
+ if (payload.tool_choice &&
492
+ typeof payload.tool_choice === 'object' &&
493
+ payload.tool_choice.type === 'tool') {
494
+ const name = payload.tool_choice.name;
495
+ if (typeof name === 'string') {
496
+ payload.tool_choice = {
497
+ ...payload.tool_choice,
498
+ name: toClaudeCodeToolName(name),
499
+ };
500
+ }
501
+ }
502
+ // Rename tool_use blocks in messages
503
+ if (Array.isArray(payload.messages)) {
504
+ payload.messages = payload.messages.map((message) => {
505
+ if (!message || typeof message !== 'object')
506
+ return message;
507
+ const content = message.content;
508
+ if (!Array.isArray(content))
509
+ return message;
510
+ return {
511
+ ...message,
512
+ content: content.map((block) => {
513
+ if (!block || typeof block !== 'object')
514
+ return block;
515
+ const b = block;
516
+ if (b.type !== 'tool_use' || typeof b.name !== 'string')
517
+ return block;
518
+ return { ...block, name: toClaudeCodeToolName(b.name) };
519
+ }),
520
+ };
521
+ });
522
+ }
523
+ return { body: JSON.stringify(payload), modelId, reverseToolNameMap };
524
+ }
525
+ catch {
526
+ return { body, modelId: undefined, reverseToolNameMap: new Map() };
527
+ }
528
+ }
529
+ function wrapResponseStream(response, reverseToolNameMap) {
530
+ if (!response.body || reverseToolNameMap.size === 0)
531
+ return response;
532
+ const reader = response.body.getReader();
533
+ const decoder = new TextDecoder();
534
+ const encoder = new TextEncoder();
535
+ let carry = '';
536
+ const transform = (text) => {
537
+ return text.replace(/"name"\s*:\s*"([^"]+)"/g, (full, name) => {
538
+ const original = reverseToolNameMap.get(name);
539
+ return original ? full.replace(`"${name}"`, `"${original}"`) : full;
540
+ });
541
+ };
542
+ const stream = new ReadableStream({
543
+ async pull(controller) {
544
+ const { done, value } = await reader.read();
545
+ if (done) {
546
+ const finalText = carry + decoder.decode();
547
+ if (finalText)
548
+ controller.enqueue(encoder.encode(transform(finalText)));
549
+ controller.close();
550
+ return;
551
+ }
552
+ carry += decoder.decode(value, { stream: true });
553
+ // Buffer 256 chars to avoid splitting JSON keys across chunks
554
+ if (carry.length <= 256)
555
+ return;
556
+ const output = carry.slice(0, -256);
557
+ carry = carry.slice(-256);
558
+ controller.enqueue(encoder.encode(transform(output)));
559
+ },
560
+ async cancel(reason) {
561
+ await reader.cancel(reason);
562
+ },
563
+ });
564
+ return new Response(stream, {
565
+ status: response.status,
566
+ statusText: response.statusText,
567
+ headers: response.headers,
568
+ });
569
+ }
570
+ // --- Beta headers ---
571
+ function getRequiredBetas(modelId) {
572
+ const betas = [CLAUDE_CODE_BETA, OAUTH_BETA, FINE_GRAINED_TOOL_STREAMING_BETA];
573
+ const isAdaptive = modelId?.includes('opus-4-6') ||
574
+ modelId?.includes('opus-4.6') ||
575
+ modelId?.includes('sonnet-4-6') ||
576
+ modelId?.includes('sonnet-4.6');
577
+ if (!isAdaptive)
578
+ betas.push(INTERLEAVED_THINKING_BETA);
579
+ return betas;
580
+ }
581
+ function mergeBetas(existing, required) {
582
+ return [
583
+ ...new Set([
584
+ ...required,
585
+ ...(existing || '')
586
+ .split(',')
587
+ .map((s) => s.trim())
588
+ .filter(Boolean),
589
+ ]),
590
+ ].join(',');
591
+ }
592
+ // --- Token refresh with dedup ---
593
+ function isOAuthStored(auth) {
594
+ return auth.type === 'oauth';
595
+ }
596
+ async function getFreshOAuth(getAuth, client) {
597
+ const auth = await getAuth();
598
+ if (!isOAuthStored(auth))
599
+ return undefined;
600
+ if (auth.access && auth.expires > Date.now())
601
+ return auth;
602
+ const pending = pendingRefresh.get(auth.refresh);
603
+ if (pending) {
604
+ return pending;
605
+ }
606
+ const refreshPromise = withAuthStateLock(async () => {
607
+ const latest = await getAuth();
608
+ if (!isOAuthStored(latest)) {
609
+ throw new Error('Anthropic OAuth credentials disappeared during refresh');
610
+ }
611
+ if (latest.access && latest.expires > Date.now())
612
+ return latest;
613
+ const refreshed = await refreshAnthropicToken(latest.refresh);
614
+ await setAnthropicAuth(refreshed, client);
615
+ const store = await loadAccountStore();
616
+ if (store.accounts.length > 0) {
617
+ upsertAccount(store, refreshed);
618
+ await saveAccountStore(store);
619
+ }
620
+ return refreshed;
621
+ });
622
+ pendingRefresh.set(auth.refresh, refreshPromise);
623
+ return refreshPromise.finally(() => {
624
+ pendingRefresh.delete(auth.refresh);
625
+ });
626
+ }
627
+ // --- Plugin export ---
628
+ const AnthropicAuthPlugin = async ({ client }) => {
629
+ return {
630
+ auth: {
631
+ provider: 'anthropic',
632
+ async loader(getAuth, provider) {
633
+ const auth = await getAuth();
634
+ if (auth.type !== 'oauth')
635
+ return {};
636
+ // Zero out costs for OAuth users (Claude Pro/Max subscription)
637
+ for (const model of Object.values(provider.models)) {
638
+ model.cost = { input: 0, output: 0, cache: { read: 0, write: 0 } };
639
+ }
640
+ return {
641
+ apiKey: '',
642
+ async fetch(input, init) {
643
+ const url = (() => {
644
+ try {
645
+ return new URL(input instanceof Request ? input.url : input.toString());
646
+ }
647
+ catch {
648
+ return null;
649
+ }
650
+ })();
651
+ if (!url || !ANTHROPIC_HOSTS.has(url.hostname))
652
+ return fetch(input, init);
653
+ const originalBody = typeof init?.body === 'string'
654
+ ? init.body
655
+ : input instanceof Request
656
+ ? await input
657
+ .clone()
658
+ .text()
659
+ .catch(() => undefined)
660
+ : undefined;
661
+ const rewritten = rewriteRequestPayload(originalBody);
662
+ const headers = new Headers(init?.headers);
663
+ if (input instanceof Request) {
664
+ input.headers.forEach((v, k) => {
665
+ if (!headers.has(k))
666
+ headers.set(k, v);
667
+ });
668
+ }
669
+ const betas = getRequiredBetas(rewritten.modelId);
670
+ const runRequest = async (auth) => {
671
+ const requestHeaders = new Headers(headers);
672
+ requestHeaders.set('accept', 'application/json');
673
+ requestHeaders.set('anthropic-beta', mergeBetas(requestHeaders.get('anthropic-beta'), betas));
674
+ requestHeaders.set('anthropic-dangerous-direct-browser-access', 'true');
675
+ requestHeaders.set('authorization', `Bearer ${auth.access}`);
676
+ requestHeaders.set('user-agent', process.env.OPENCODE_ANTHROPIC_USER_AGENT || `claude-cli/${CLAUDE_CODE_VERSION}`);
677
+ requestHeaders.set('x-app', 'cli');
678
+ requestHeaders.delete('x-api-key');
679
+ return fetch(input, {
680
+ ...(init ?? {}),
681
+ body: rewritten.body,
682
+ headers: requestHeaders,
683
+ });
684
+ };
685
+ const freshAuth = await getFreshOAuth(getAuth, client);
686
+ if (!freshAuth)
687
+ return fetch(input, init);
688
+ let response = await runRequest(freshAuth);
689
+ if (!response.ok) {
690
+ const bodyText = await response
691
+ .clone()
692
+ .text()
693
+ .catch(() => '');
694
+ if (shouldRotateAuth(response.status, bodyText)) {
695
+ const rotated = await rotateAnthropicAccount(freshAuth, client);
696
+ if (rotated) {
697
+ const retryAuth = await getFreshOAuth(getAuth, client);
698
+ if (retryAuth) {
699
+ response = await runRequest(retryAuth);
700
+ }
701
+ }
702
+ }
703
+ }
704
+ return wrapResponseStream(response, rewritten.reverseToolNameMap);
705
+ },
706
+ };
707
+ },
708
+ methods: [
709
+ {
710
+ label: 'Claude Pro/Max',
711
+ type: 'oauth',
712
+ authorize: buildAuthorizeHandler('oauth'),
713
+ },
714
+ {
715
+ label: 'Create an API Key',
716
+ type: 'oauth',
717
+ authorize: buildAuthorizeHandler('apikey'),
718
+ },
719
+ {
720
+ provider: 'anthropic',
721
+ label: 'Manually enter API Key',
722
+ type: 'api',
723
+ },
724
+ ],
725
+ },
726
+ };
727
+ };
728
+ export { AnthropicAuthPlugin as anthropicAuthPlugin, };