rird 2.1.231 → 2.3.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 (381) hide show
  1. package/AGENTS.md +86 -0
  2. package/COMPLETED_TEST_SUITE.txt +280 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +397 -6
  5. package/RIRD_ERROR_HANDLING_SUMMARY.md +307 -0
  6. package/TESTING.md +512 -0
  7. package/TEST_IMPLEMENTATION_REPORT.md +463 -0
  8. package/TEST_SUITE.md +307 -0
  9. package/TEST_SUMMARY.txt +380 -0
  10. package/bin/rird-perf.js +37 -0
  11. package/bin/rird.js +43 -8
  12. package/bunfig.toml +4 -0
  13. package/create-wrapper.ps1 +51 -0
  14. package/docs/ARCHITECTURE.md +768 -0
  15. package/docs/CLI_REFERENCE.md +681 -0
  16. package/docs/DOCUMENTATION_MANIFEST.md +392 -0
  17. package/docs/INDEX.md +295 -0
  18. package/docs/PRODUCTION_SETUP.md +633 -0
  19. package/docs/TROUBLESHOOTING.md +914 -0
  20. package/facebook_ads_library.png +0 -0
  21. package/nul +0 -0
  22. package/nul`nif +0 -0
  23. package/package.json +104 -15
  24. package/parsers-config.ts +239 -0
  25. package/rird-1.0.199.tgz +0 -0
  26. package/rird-1.0.205.tgz +0 -0
  27. package/script/build-windows.ts +56 -0
  28. package/script/build.ts +165 -0
  29. package/{postinstall.mjs → script/postinstall.mjs} +47 -68
  30. package/script/publish-registries.ts +187 -0
  31. package/script/publish.ts +85 -0
  32. package/script/schema.ts +47 -0
  33. package/src/acp/README.md +164 -0
  34. package/src/acp/agent.ts +1063 -0
  35. package/src/acp/session.ts +101 -0
  36. package/src/acp/types.ts +22 -0
  37. package/src/agent/agent.ts +367 -0
  38. package/src/agent/generate.txt +75 -0
  39. package/src/agent/prompt/compaction.txt +12 -0
  40. package/src/agent/prompt/explore.txt +18 -0
  41. package/src/agent/prompt/summary.txt +10 -0
  42. package/src/agent/prompt/title.txt +36 -0
  43. package/src/auth/index.ts +70 -0
  44. package/src/bun/index.ts +114 -0
  45. package/src/bus/bus-event.ts +43 -0
  46. package/src/bus/global.ts +10 -0
  47. package/src/bus/index.ts +105 -0
  48. package/src/cli/bootstrap.ts +17 -0
  49. package/src/cli/cmd/acp.ts +104 -0
  50. package/src/cli/cmd/activate.ts +50 -0
  51. package/src/cli/cmd/agent.ts +256 -0
  52. package/src/cli/cmd/auth.ts +412 -0
  53. package/src/cli/cmd/cmd.ts +7 -0
  54. package/src/cli/cmd/debug/config.ts +15 -0
  55. package/src/cli/cmd/debug/file.ts +91 -0
  56. package/src/cli/cmd/debug/index.ts +43 -0
  57. package/src/cli/cmd/debug/lsp.ts +48 -0
  58. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  59. package/src/cli/cmd/debug/scrap.ts +15 -0
  60. package/src/cli/cmd/debug/skill.ts +15 -0
  61. package/src/cli/cmd/debug/snapshot.ts +48 -0
  62. package/src/cli/cmd/export.ts +88 -0
  63. package/src/cli/cmd/generate.ts +38 -0
  64. package/src/cli/cmd/github.ts +1400 -0
  65. package/src/cli/cmd/import.ts +98 -0
  66. package/src/cli/cmd/mcp.ts +654 -0
  67. package/src/cli/cmd/models.ts +68 -0
  68. package/src/cli/cmd/pr.ts +112 -0
  69. package/src/cli/cmd/run.ts +434 -0
  70. package/src/cli/cmd/serve.ts +31 -0
  71. package/src/cli/cmd/session.ts +106 -0
  72. package/src/cli/cmd/stats.ts +298 -0
  73. package/src/cli/cmd/tui/app.tsx +694 -0
  74. package/src/cli/cmd/tui/attach.ts +30 -0
  75. package/src/cli/cmd/tui/component/border.tsx +21 -0
  76. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  77. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  78. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  79. package/src/cli/cmd/tui/component/dialog-model.tsx +236 -0
  80. package/src/cli/cmd/tui/component/dialog-provider.tsx +240 -0
  81. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  82. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  83. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  84. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  85. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  86. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  87. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  88. package/src/cli/cmd/tui/component/logo.tsx +48 -0
  89. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  90. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  91. package/src/cli/cmd/tui/component/prompt/index.tsx +1087 -0
  92. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  93. package/src/cli/cmd/tui/component/tips.ts +27 -0
  94. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  95. package/src/cli/cmd/tui/context/args.tsx +14 -0
  96. package/src/cli/cmd/tui/context/directory.ts +13 -0
  97. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  98. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  99. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  100. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  101. package/src/cli/cmd/tui/context/local.tsx +345 -0
  102. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  103. package/src/cli/cmd/tui/context/route.tsx +46 -0
  104. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  105. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  106. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  107. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  108. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  109. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  110. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  111. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  112. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  113. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  114. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  115. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  116. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  117. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  118. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  119. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  120. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  121. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  122. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  123. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  124. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  125. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  126. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  127. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  128. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  129. package/src/cli/cmd/tui/context/theme/rird.json +245 -0
  130. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  131. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  132. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  133. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  134. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  135. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  136. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  137. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  138. package/src/cli/cmd/tui/event.ts +40 -0
  139. package/src/cli/cmd/tui/hooks/use-safe-terminal-dimensions.ts +12 -0
  140. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  141. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  142. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  143. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  144. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  145. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  146. package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
  147. package/src/cli/cmd/tui/routes/session/index.tsx +1876 -0
  148. package/src/cli/cmd/tui/routes/session/sidebar.tsx +320 -0
  149. package/src/cli/cmd/tui/spawn.ts +60 -0
  150. package/src/cli/cmd/tui/thread.ts +142 -0
  151. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  152. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  153. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  154. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  155. package/src/cli/cmd/tui/ui/dialog-select.tsx +333 -0
  156. package/src/cli/cmd/tui/ui/dialog.tsx +171 -0
  157. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  158. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  159. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  160. package/src/cli/cmd/tui/util/editor.ts +32 -0
  161. package/src/cli/cmd/tui/util/terminal.ts +146 -0
  162. package/src/cli/cmd/tui/worker.ts +63 -0
  163. package/src/cli/cmd/uninstall.ts +344 -0
  164. package/src/cli/cmd/upgrade.ts +127 -0
  165. package/src/cli/cmd/web.ts +84 -0
  166. package/src/cli/error.ts +69 -0
  167. package/src/cli/ui.ts +101 -0
  168. package/src/cli/upgrade.ts +28 -0
  169. package/src/command/index.ts +80 -0
  170. package/src/command/template/initialize.txt +10 -0
  171. package/src/command/template/review.txt +97 -0
  172. package/src/config/config.ts +994 -0
  173. package/src/config/markdown.ts +41 -0
  174. package/src/env/index.ts +26 -0
  175. package/src/file/ignore.ts +83 -0
  176. package/src/file/index.ts +328 -0
  177. package/src/file/ripgrep.ts +393 -0
  178. package/src/file/time.ts +64 -0
  179. package/src/file/watcher.ts +103 -0
  180. package/src/flag/flag.ts +84 -0
  181. package/src/format/formatter.ts +315 -0
  182. package/src/format/index.ts +137 -0
  183. package/src/global/index.ts +101 -0
  184. package/src/id/id.ts +73 -0
  185. package/src/ide/index.ts +76 -0
  186. package/src/index.ts +297 -0
  187. package/src/index.ts.backup +271 -0
  188. package/src/installation/index.ts +258 -0
  189. package/src/lib/IMPLEMENTATION_NOTES.md +345 -0
  190. package/src/lib/error-handler.ts +225 -0
  191. package/src/lib/error-testing-guide.md +258 -0
  192. package/src/lib/errors.ts +285 -0
  193. package/src/lib/performance.ts +70 -0
  194. package/src/lib/telemetry.ts +282 -0
  195. package/src/lsp/client.ts +229 -0
  196. package/src/lsp/index.ts +485 -0
  197. package/src/lsp/language.ts +116 -0
  198. package/src/lsp/server.ts +1895 -0
  199. package/src/mcp/auth.ts +135 -0
  200. package/src/mcp/index.ts +1117 -0
  201. package/src/mcp/intent-analyzer.ts +376 -0
  202. package/src/mcp/oauth-callback.ts +200 -0
  203. package/src/mcp/oauth-provider.ts +154 -0
  204. package/src/patch/index.ts +632 -0
  205. package/src/permission/index.ts +199 -0
  206. package/src/plugin/index.ts +91 -0
  207. package/src/project/bootstrap.ts +33 -0
  208. package/src/project/instance.ts +78 -0
  209. package/src/project/project.ts +236 -0
  210. package/src/project/state.ts +65 -0
  211. package/src/project/vcs.ts +76 -0
  212. package/src/provider/auth.ts +143 -0
  213. package/src/provider/models-macro.ts +55 -0
  214. package/src/provider/models.ts +161 -0
  215. package/src/provider/provider.ts +1109 -0
  216. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  217. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  218. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  219. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  220. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  221. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  222. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  223. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  224. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  225. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  226. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  227. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  228. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  229. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  230. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  231. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  232. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  233. package/src/provider/transform.ts +455 -0
  234. package/src/pty/index.ts +231 -0
  235. package/src/security/guardrails.test.ts +341 -0
  236. package/src/security/guardrails.ts +570 -0
  237. package/src/security/index.ts +19 -0
  238. package/src/server/error.ts +36 -0
  239. package/src/server/project.ts +79 -0
  240. package/src/server/server.ts +2641 -0
  241. package/src/server/tui.ts +71 -0
  242. package/src/session/compaction.ts +228 -0
  243. package/src/session/index.ts +464 -0
  244. package/src/session/llm.ts +201 -0
  245. package/src/session/message-v2.ts +695 -0
  246. package/src/session/message.ts +189 -0
  247. package/src/session/processor.ts +409 -0
  248. package/src/session/prompt/act-switch.txt +5 -0
  249. package/src/session/prompt/anthropic-20250930.txt +166 -0
  250. package/src/session/prompt/anthropic.txt +63 -0
  251. package/src/session/prompt/anthropic_spoof.txt +1 -0
  252. package/src/session/prompt/beast.txt +76 -0
  253. package/src/session/prompt/codex.txt +304 -0
  254. package/src/session/prompt/copilot-gpt-5.txt +137 -0
  255. package/src/session/prompt/gemini.txt +62 -0
  256. package/src/session/prompt/max-steps.txt +16 -0
  257. package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
  258. package/src/session/prompt/plan.txt +24 -0
  259. package/src/session/prompt/polaris.txt +88 -0
  260. package/src/session/prompt/qwen.txt +59 -0
  261. package/src/session/prompt.ts +1552 -0
  262. package/src/session/retry.ts +86 -0
  263. package/src/session/revert.ts +108 -0
  264. package/src/session/sensitive-filter.test.ts +327 -0
  265. package/src/session/sensitive-filter.ts +466 -0
  266. package/src/session/status.ts +76 -0
  267. package/src/session/summary.ts +209 -0
  268. package/src/session/system.ts +122 -0
  269. package/src/session/todo.ts +37 -0
  270. package/src/share/share-next.ts +222 -0
  271. package/src/share/share.ts +87 -0
  272. package/src/shell/shell.ts +67 -0
  273. package/src/skill/index.ts +1 -0
  274. package/src/skill/skill.ts +83 -0
  275. package/src/snapshot/index.ts +197 -0
  276. package/src/storage/storage.ts +226 -0
  277. package/src/tests/agent.test.ts +308 -0
  278. package/src/tests/build-guards.test.ts +267 -0
  279. package/src/tests/config.test.ts +664 -0
  280. package/src/tests/tool-registry.test.ts +589 -0
  281. package/src/tool/bash.ts +314 -0
  282. package/src/tool/bash.txt +158 -0
  283. package/src/tool/batch.ts +175 -0
  284. package/src/tool/batch.txt +24 -0
  285. package/src/tool/codesearch.ts +184 -0
  286. package/src/tool/codesearch.txt +12 -0
  287. package/src/tool/edit.ts +675 -0
  288. package/src/tool/edit.txt +10 -0
  289. package/src/tool/glob.ts +65 -0
  290. package/src/tool/glob.txt +6 -0
  291. package/src/tool/grep.ts +121 -0
  292. package/src/tool/grep.txt +8 -0
  293. package/src/tool/invalid.ts +17 -0
  294. package/src/tool/ls.ts +110 -0
  295. package/src/tool/ls.txt +1 -0
  296. package/src/tool/lsp-diagnostics.ts +26 -0
  297. package/src/tool/lsp-diagnostics.txt +1 -0
  298. package/src/tool/lsp-hover.ts +31 -0
  299. package/src/tool/lsp-hover.txt +1 -0
  300. package/src/tool/lsp.ts +87 -0
  301. package/src/tool/lsp.txt +19 -0
  302. package/src/tool/multiedit.ts +46 -0
  303. package/src/tool/multiedit.txt +41 -0
  304. package/src/tool/patch.ts +233 -0
  305. package/src/tool/patch.txt +1 -0
  306. package/src/tool/read.ts +219 -0
  307. package/src/tool/read.txt +12 -0
  308. package/src/tool/registry.ts +162 -0
  309. package/src/tool/skill.ts +100 -0
  310. package/src/tool/task.ts +136 -0
  311. package/src/tool/task.txt +51 -0
  312. package/src/tool/todo.ts +39 -0
  313. package/src/tool/todoread.txt +14 -0
  314. package/src/tool/todowrite.txt +167 -0
  315. package/src/tool/tool.ts +71 -0
  316. package/src/tool/webfetch.ts +198 -0
  317. package/src/tool/webfetch.txt +13 -0
  318. package/src/tool/websearch.ts +268 -0
  319. package/src/tool/websearch.txt +13 -0
  320. package/src/tool/write.ts +110 -0
  321. package/src/tool/write.txt +8 -0
  322. package/src/util/archive.ts +16 -0
  323. package/src/util/color.ts +19 -0
  324. package/src/util/context.ts +25 -0
  325. package/src/util/defer.ts +12 -0
  326. package/src/util/eventloop.ts +20 -0
  327. package/src/util/filesystem.ts +83 -0
  328. package/src/util/fn.ts +11 -0
  329. package/src/util/iife.ts +3 -0
  330. package/src/util/keybind.ts +102 -0
  331. package/src/util/lazy.ts +11 -0
  332. package/src/util/license.ts +362 -0
  333. package/src/util/locale.ts +81 -0
  334. package/src/util/lock.ts +98 -0
  335. package/src/util/log.ts +180 -0
  336. package/src/util/queue.ts +32 -0
  337. package/src/util/rpc.ts +42 -0
  338. package/src/util/scrap.ts +10 -0
  339. package/src/util/signal.ts +12 -0
  340. package/src/util/timeout.ts +14 -0
  341. package/src/util/token.ts +7 -0
  342. package/src/util/wildcard.ts +54 -0
  343. package/sst-env.d.ts +9 -0
  344. package/test/agent/agent.test.ts +146 -0
  345. package/test/bun.test.ts +53 -0
  346. package/test/cli/cmd/acp.test.ts +144 -0
  347. package/test/cli/cmd/run.test.ts +250 -0
  348. package/test/cli/github-remote.test.ts +80 -0
  349. package/test/config/agent-color.test.ts +66 -0
  350. package/test/config/config.test.ts +536 -0
  351. package/test/config/markdown.test.ts +89 -0
  352. package/test/file/ignore.test.ts +10 -0
  353. package/test/fixture/fixture.ts +37 -0
  354. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  355. package/test/helpers.ts +172 -0
  356. package/test/ide/ide.test.ts +82 -0
  357. package/test/installation/installation.test.ts +143 -0
  358. package/test/keybind.test.ts +421 -0
  359. package/test/lsp/client.test.ts +95 -0
  360. package/test/mcp/headers.test.ts +153 -0
  361. package/test/patch/patch.test.ts +348 -0
  362. package/test/preload.ts +57 -0
  363. package/test/project/project.test.ts +74 -0
  364. package/test/provider/provider.test.ts +74 -0
  365. package/test/provider/transform.test.ts +411 -0
  366. package/test/session/retry.test.ts +111 -0
  367. package/test/session/session.test.ts +71 -0
  368. package/test/skill/skill.test.ts +131 -0
  369. package/test/snapshot/snapshot.test.ts +940 -0
  370. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  371. package/test/tool/bash.test.ts +434 -0
  372. package/test/tool/grep.test.ts +108 -0
  373. package/test/tool/patch.test.ts +259 -0
  374. package/test/tool/read.test.ts +42 -0
  375. package/test/util/iife.test.ts +36 -0
  376. package/test/util/lazy.test.ts +50 -0
  377. package/test/util/license.test.ts +235 -0
  378. package/test/util/timeout.test.ts +21 -0
  379. package/test/util/wildcard.test.ts +55 -0
  380. package/tsconfig.json +16 -0
  381. package/update-versions.ps1 +65 -0
@@ -0,0 +1,1109 @@
1
+ import z from "zod"
2
+ import fuzzysort from "fuzzysort"
3
+ import { Config } from "../config/config"
4
+ import { mapValues, mergeDeep, sortBy } from "remeda"
5
+ import { NoSuchModelError, type Provider as SDK } from "ai"
6
+ import { Log } from "../util/log"
7
+ import { BunProc } from "../bun"
8
+ import { Plugin } from "../plugin"
9
+ import { ModelsDev } from "./models"
10
+ import { NamedError } from "@opencode-ai/util/error"
11
+ import { Auth } from "../auth"
12
+ import { Env } from "../env"
13
+ import { Instance } from "../project/instance"
14
+ import { Flag } from "../flag/flag"
15
+ import { iife } from "@/util/iife"
16
+
17
+ // Direct imports for bundled providers
18
+ import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"
19
+ import { createAnthropic } from "@ai-sdk/anthropic"
20
+ import { createAzure } from "@ai-sdk/azure"
21
+ import { createGoogleGenerativeAI } from "@ai-sdk/google"
22
+ import { createVertex } from "@ai-sdk/google-vertex"
23
+ import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic"
24
+ import { createOpenAI } from "@ai-sdk/openai"
25
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
26
+ import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider"
27
+ import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/openai-compatible/src"
28
+ import { createXai } from "@ai-sdk/xai"
29
+ import { createMistral } from "@ai-sdk/mistral"
30
+ import { createGroq } from "@ai-sdk/groq"
31
+ import { createDeepInfra } from "@ai-sdk/deepinfra"
32
+ import { createCerebras } from "@ai-sdk/cerebras"
33
+ import { createCohere } from "@ai-sdk/cohere"
34
+ import { createGateway } from "@ai-sdk/gateway"
35
+ import { createTogetherAI } from "@ai-sdk/togetherai"
36
+ import { createPerplexity } from "@ai-sdk/perplexity"
37
+
38
+ export namespace Provider {
39
+ const log = Log.create({ service: "provider" })
40
+
41
+ function publicHostedProviderID(providerID: string) {
42
+ return providerID
43
+ }
44
+
45
+ const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
46
+ "@ai-sdk/amazon-bedrock": createAmazonBedrock,
47
+ "@ai-sdk/anthropic": createAnthropic,
48
+ "@ai-sdk/azure": createAzure,
49
+ "@ai-sdk/google": createGoogleGenerativeAI,
50
+ "@ai-sdk/google-vertex": createVertex,
51
+ "@ai-sdk/google-vertex/anthropic": createVertexAnthropic,
52
+ "@ai-sdk/openai": createOpenAI,
53
+ "@ai-sdk/openai-compatible": createOpenAICompatible,
54
+ "@openrouter/ai-sdk-provider": createOpenRouter,
55
+ "@ai-sdk/xai": createXai,
56
+ "@ai-sdk/mistral": createMistral,
57
+ "@ai-sdk/groq": createGroq,
58
+ "@ai-sdk/deepinfra": createDeepInfra,
59
+ "@ai-sdk/cerebras": createCerebras,
60
+ "@ai-sdk/cohere": createCohere,
61
+ "@ai-sdk/gateway": createGateway,
62
+ "@ai-sdk/togetherai": createTogetherAI,
63
+ "@ai-sdk/perplexity": createPerplexity,
64
+ // @ts-ignore (TODO: kill this code so we dont have to maintain it)
65
+ "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
66
+ }
67
+
68
+ type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
69
+ type CustomLoader = (provider: Info) => Promise<{
70
+ autoload: boolean
71
+ getModel?: CustomModelLoader
72
+ options?: Record<string, any>
73
+ key?: string
74
+ }>
75
+
76
+ const CUSTOM_LOADERS: Record<string, CustomLoader> = {
77
+ async anthropic() {
78
+ return {
79
+ autoload: false,
80
+ options: {
81
+ headers: {
82
+ "anthropic-beta":
83
+ "claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
84
+ },
85
+ },
86
+ }
87
+ },
88
+ async rird(input) {
89
+ if (!input) return { autoload: false }
90
+
91
+ const rirdApiKey = process.env.RIRD_API_KEY
92
+ return {
93
+ autoload: Object.keys(input.models).length > 0,
94
+ key: rirdApiKey ?? undefined,
95
+ options: {
96
+ baseURL: "https://rird.ai/api/llm/v1",
97
+ },
98
+ }
99
+ },
100
+ openai: async () => {
101
+ return {
102
+ autoload: false,
103
+ async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
104
+ return sdk.responses(modelID)
105
+ },
106
+ options: {},
107
+ }
108
+ },
109
+ "github-copilot": async () => {
110
+ return {
111
+ autoload: false,
112
+ async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
113
+ if (modelID.includes("codex")) {
114
+ return sdk.responses(modelID)
115
+ }
116
+ return sdk.chat(modelID)
117
+ },
118
+ options: {},
119
+ }
120
+ },
121
+ "github-copilot-enterprise": async () => {
122
+ return {
123
+ autoload: false,
124
+ async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
125
+ if (modelID.includes("codex")) {
126
+ return sdk.responses(modelID)
127
+ }
128
+ return sdk.chat(modelID)
129
+ },
130
+ options: {},
131
+ }
132
+ },
133
+ azure: async () => {
134
+ return {
135
+ autoload: false,
136
+ async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
137
+ if (options?.["useCompletionUrls"]) {
138
+ return sdk.chat(modelID)
139
+ } else {
140
+ return sdk.responses(modelID)
141
+ }
142
+ },
143
+ options: {},
144
+ }
145
+ },
146
+ "azure-cognitive-services": async () => {
147
+ const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
148
+ return {
149
+ autoload: false,
150
+ async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
151
+ if (options?.["useCompletionUrls"]) {
152
+ return sdk.chat(modelID)
153
+ } else {
154
+ return sdk.responses(modelID)
155
+ }
156
+ },
157
+ options: {
158
+ baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined,
159
+ },
160
+ }
161
+ },
162
+ "amazon-bedrock": async () => {
163
+ const [awsProfile, awsAccessKeyId, awsBearerToken, awsRegion] = await Promise.all([
164
+ Env.get("AWS_PROFILE"),
165
+ Env.get("AWS_ACCESS_KEY_ID"),
166
+ Env.get("AWS_BEARER_TOKEN_BEDROCK"),
167
+ Env.get("AWS_REGION"),
168
+ ])
169
+ if (!awsProfile && !awsAccessKeyId && !awsBearerToken) return { autoload: false }
170
+
171
+ const region = awsRegion ?? "us-east-1"
172
+
173
+ const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
174
+ return {
175
+ autoload: true,
176
+ options: {
177
+ region,
178
+ credentialProvider: fromNodeProviderChain(),
179
+ },
180
+ async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
181
+ // Skip region prefixing if model already has global prefix
182
+ if (modelID.startsWith("global.")) {
183
+ return sdk.languageModel(modelID)
184
+ }
185
+
186
+ let regionPrefix = region.split("-")[0]
187
+
188
+ switch (regionPrefix) {
189
+ case "us": {
190
+ const modelRequiresPrefix = [
191
+ "nova-micro",
192
+ "nova-lite",
193
+ "nova-pro",
194
+ "nova-premier",
195
+ "claude",
196
+ "deepseek",
197
+ ].some((m) => modelID.includes(m))
198
+ const isGovCloud = region.startsWith("us-gov")
199
+ if (modelRequiresPrefix && !isGovCloud) {
200
+ modelID = `${regionPrefix}.${modelID}`
201
+ }
202
+ break
203
+ }
204
+ case "eu": {
205
+ const regionRequiresPrefix = [
206
+ "eu-west-1",
207
+ "eu-west-2",
208
+ "eu-west-3",
209
+ "eu-north-1",
210
+ "eu-central-1",
211
+ "eu-south-1",
212
+ "eu-south-2",
213
+ ].some((r) => region.includes(r))
214
+ const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) =>
215
+ modelID.includes(m),
216
+ )
217
+ if (regionRequiresPrefix && modelRequiresPrefix) {
218
+ modelID = `${regionPrefix}.${modelID}`
219
+ }
220
+ break
221
+ }
222
+ case "ap": {
223
+ const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region)
224
+ if (
225
+ isAustraliaRegion &&
226
+ ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m))
227
+ ) {
228
+ regionPrefix = "au"
229
+ modelID = `${regionPrefix}.${modelID}`
230
+ } else {
231
+ const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
232
+ modelID.includes(m),
233
+ )
234
+ if (modelRequiresPrefix) {
235
+ regionPrefix = "apac"
236
+ modelID = `${regionPrefix}.${modelID}`
237
+ }
238
+ }
239
+ break
240
+ }
241
+ }
242
+
243
+ return sdk.languageModel(modelID)
244
+ },
245
+ }
246
+ },
247
+ openrouter: async () => {
248
+ return {
249
+ autoload: false,
250
+ options: {
251
+ headers: {
252
+ "HTTP-Referer": "https://rird.ai/",
253
+ "X-Title": "rird",
254
+ },
255
+ },
256
+ }
257
+ },
258
+ vercel: async () => {
259
+ return {
260
+ autoload: false,
261
+ options: {
262
+ headers: {
263
+ "http-referer": "https://rird.ai/",
264
+ "x-title": "rird",
265
+ },
266
+ },
267
+ }
268
+ },
269
+ "google-vertex": async () => {
270
+ const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
271
+ const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-east5"
272
+ const autoload = Boolean(project)
273
+ if (!autoload) return { autoload: false }
274
+ return {
275
+ autoload: true,
276
+ options: {
277
+ project,
278
+ location,
279
+ },
280
+ async getModel(sdk: any, modelID: string) {
281
+ const id = String(modelID).trim()
282
+ return sdk.languageModel(id)
283
+ },
284
+ }
285
+ },
286
+ "google-vertex-anthropic": async () => {
287
+ const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
288
+ const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "global"
289
+ const autoload = Boolean(project)
290
+ if (!autoload) return { autoload: false }
291
+ return {
292
+ autoload: true,
293
+ options: {
294
+ project,
295
+ location,
296
+ },
297
+ async getModel(sdk: any, modelID) {
298
+ const id = String(modelID).trim()
299
+ return sdk.languageModel(id)
300
+ },
301
+ }
302
+ },
303
+ "sap-ai-core": async () => {
304
+ const auth = await Auth.get("sap-ai-core")
305
+ const envServiceKey = iife(() => {
306
+ const envAICoreServiceKey = Env.get("AICORE_SERVICE_KEY")
307
+ if (envAICoreServiceKey) return envAICoreServiceKey
308
+ if (auth?.type === "api") {
309
+ Env.set("AICORE_SERVICE_KEY", auth.key)
310
+ return auth.key
311
+ }
312
+ return undefined
313
+ })
314
+ const deploymentId = Env.get("AICORE_DEPLOYMENT_ID")
315
+ const resourceGroup = Env.get("AICORE_RESOURCE_GROUP")
316
+
317
+ return {
318
+ autoload: !!envServiceKey,
319
+ options: envServiceKey ? { deploymentId, resourceGroup } : {},
320
+ async getModel(sdk: any, modelID: string) {
321
+ return sdk(modelID)
322
+ },
323
+ }
324
+ },
325
+ zenmux: async () => {
326
+ return {
327
+ autoload: false,
328
+ options: {
329
+ headers: {
330
+ "HTTP-Referer": "https://rird.ai/",
331
+ "X-Title": "rird",
332
+ },
333
+ },
334
+ }
335
+ },
336
+ "cloudflare-ai-gateway": async (input) => {
337
+ const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
338
+ const gateway = Env.get("CLOUDFLARE_GATEWAY_ID")
339
+
340
+ if (!accountId || !gateway) return { autoload: false }
341
+
342
+ // Get API token from env or auth prompt
343
+ const apiToken = await (async () => {
344
+ const envToken = Env.get("CLOUDFLARE_API_TOKEN")
345
+ if (envToken) return envToken
346
+ const auth = await Auth.get(input.id)
347
+ if (auth?.type === "api") return auth.key
348
+ return undefined
349
+ })()
350
+
351
+ return {
352
+ autoload: true,
353
+ async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
354
+ return sdk.chat(modelID)
355
+ },
356
+ options: {
357
+ baseURL: `https://gateway.ai.cloudflare.com/v1/${accountId}/${gateway}/compat`,
358
+ headers: {
359
+ // Cloudflare AI Gateway uses cf-aig-authorization for authenticated gateways
360
+ // This enables Unified Billing where Cloudflare handles upstream provider auth
361
+ ...(apiToken ? { "cf-aig-authorization": `Bearer ${apiToken}` } : {}),
362
+ "HTTP-Referer": "https://rird.ai/",
363
+ "X-Title": "rird",
364
+ },
365
+ // Custom fetch to strip Authorization header - AI Gateway uses cf-aig-authorization instead
366
+ // Sending Authorization header with invalid value causes auth errors
367
+ fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
368
+ const headers = new Headers(init?.headers)
369
+ headers.delete("Authorization")
370
+ return fetch(input, { ...init, headers })
371
+ },
372
+ },
373
+ }
374
+ },
375
+ cerebras: async () => {
376
+ return {
377
+ autoload: false,
378
+ options: {
379
+ headers: {
380
+ "X-Cerebras-3rd-Party-Integration": "RIRD",
381
+ },
382
+ },
383
+ }
384
+ },
385
+ }
386
+
387
+ export const Model = z
388
+ .object({
389
+ id: z.string(),
390
+ providerID: z.string(),
391
+ api: z.object({
392
+ id: z.string(),
393
+ url: z.string(),
394
+ npm: z.string(),
395
+ }),
396
+ name: z.string(),
397
+ family: z.string().optional(),
398
+ capabilities: z.object({
399
+ temperature: z.boolean(),
400
+ reasoning: z.boolean(),
401
+ attachment: z.boolean(),
402
+ toolcall: z.boolean(),
403
+ input: z.object({
404
+ text: z.boolean(),
405
+ audio: z.boolean(),
406
+ image: z.boolean(),
407
+ video: z.boolean(),
408
+ pdf: z.boolean(),
409
+ }),
410
+ output: z.object({
411
+ text: z.boolean(),
412
+ audio: z.boolean(),
413
+ image: z.boolean(),
414
+ video: z.boolean(),
415
+ pdf: z.boolean(),
416
+ }),
417
+ interleaved: z.union([
418
+ z.boolean(),
419
+ z.object({
420
+ field: z.enum(["reasoning_content", "reasoning_details"]),
421
+ }),
422
+ ]),
423
+ }),
424
+ cost: z.object({
425
+ input: z.number(),
426
+ output: z.number(),
427
+ cache: z.object({
428
+ read: z.number(),
429
+ write: z.number(),
430
+ }),
431
+ experimentalOver200K: z
432
+ .object({
433
+ input: z.number(),
434
+ output: z.number(),
435
+ cache: z.object({
436
+ read: z.number(),
437
+ write: z.number(),
438
+ }),
439
+ })
440
+ .optional(),
441
+ }),
442
+ limit: z.object({
443
+ context: z.number(),
444
+ output: z.number(),
445
+ }),
446
+ status: z.enum(["alpha", "beta", "deprecated", "active"]),
447
+ options: z.record(z.string(), z.any()),
448
+ headers: z.record(z.string(), z.string()),
449
+ release_date: z.string(),
450
+ })
451
+ .meta({
452
+ ref: "Model",
453
+ })
454
+ export type Model = z.infer<typeof Model>
455
+
456
+ export const Info = z
457
+ .object({
458
+ id: z.string(),
459
+ name: z.string(),
460
+ source: z.enum(["env", "config", "custom", "api"]),
461
+ env: z.string().array(),
462
+ key: z.string().optional(),
463
+ options: z.record(z.string(), z.any()),
464
+ models: z.record(z.string(), Model),
465
+ })
466
+ .meta({
467
+ ref: "Provider",
468
+ })
469
+ export type Info = z.infer<typeof Info>
470
+
471
+ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
472
+ return {
473
+ id: model.id,
474
+ providerID: provider.id,
475
+ name: model.name,
476
+ family: model.family,
477
+ api: {
478
+ id: model.id,
479
+ url: provider.api!,
480
+ npm: model.provider?.npm ?? provider.npm ?? provider.id,
481
+ },
482
+ status: model.status ?? "active",
483
+ headers: model.headers ?? {},
484
+ options: model.options ?? {},
485
+ cost: {
486
+ input: model.cost?.input ?? 0,
487
+ output: model.cost?.output ?? 0,
488
+ cache: {
489
+ read: model.cost?.cache_read ?? 0,
490
+ write: model.cost?.cache_write ?? 0,
491
+ },
492
+ experimentalOver200K: model.cost?.context_over_200k
493
+ ? {
494
+ cache: {
495
+ read: model.cost.context_over_200k.cache_read ?? 0,
496
+ write: model.cost.context_over_200k.cache_write ?? 0,
497
+ },
498
+ input: model.cost.context_over_200k.input,
499
+ output: model.cost.context_over_200k.output,
500
+ }
501
+ : undefined,
502
+ },
503
+ limit: {
504
+ context: model.limit.context,
505
+ output: model.limit.output,
506
+ },
507
+ capabilities: {
508
+ temperature: model.temperature,
509
+ reasoning: model.reasoning,
510
+ attachment: model.attachment,
511
+ toolcall: model.tool_call,
512
+ input: {
513
+ text: model.modalities?.input?.includes("text") ?? false,
514
+ audio: model.modalities?.input?.includes("audio") ?? false,
515
+ image: model.modalities?.input?.includes("image") ?? false,
516
+ video: model.modalities?.input?.includes("video") ?? false,
517
+ pdf: model.modalities?.input?.includes("pdf") ?? false,
518
+ },
519
+ output: {
520
+ text: model.modalities?.output?.includes("text") ?? false,
521
+ audio: model.modalities?.output?.includes("audio") ?? false,
522
+ image: model.modalities?.output?.includes("image") ?? false,
523
+ video: model.modalities?.output?.includes("video") ?? false,
524
+ pdf: model.modalities?.output?.includes("pdf") ?? false,
525
+ },
526
+ interleaved: model.interleaved ?? false,
527
+ },
528
+ release_date: model.release_date,
529
+ }
530
+ }
531
+
532
+ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
533
+ return {
534
+ id: provider.id,
535
+ source: "custom",
536
+ name: provider.name,
537
+ env: provider.env ?? [],
538
+ options: {},
539
+ models: mapValues(provider.models, (model) => fromModelsDevModel(provider, model)),
540
+ }
541
+ }
542
+
543
+ const state = Instance.state(async () => {
544
+ using _ = log.time("state")
545
+ const config = await Config.get()
546
+ const modelsDev = await ModelsDev.get()
547
+ const database = mapValues(modelsDev, fromModelsDevProvider)
548
+
549
+ const disabled = new Set(config.disabled_providers ?? [])
550
+ const enabled = config.enabled_providers ? new Set(config.enabled_providers) : null
551
+
552
+ // RIRD must always remain available.
553
+ disabled.delete("rird")
554
+ enabled?.add("rird")
555
+
556
+ function isProviderAllowed(providerID: string): boolean {
557
+ if (providerID !== "rird") return false
558
+ return true
559
+ }
560
+
561
+ const providers: { [providerID: string]: Info } = {}
562
+ const languages = new Map<string, LanguageModelV2>()
563
+ const modelLoaders: {
564
+ [providerID: string]: CustomModelLoader
565
+ } = {}
566
+ const sdk = new Map<number, SDK>()
567
+
568
+ log.info("init")
569
+
570
+ const configProviders = Object.entries(config.provider ?? {})
571
+
572
+ // Add GitHub Copilot Enterprise provider that inherits from GitHub Copilot
573
+ if (database["github-copilot"]) {
574
+ const githubCopilot = database["github-copilot"]
575
+ database["github-copilot-enterprise"] = {
576
+ ...githubCopilot,
577
+ id: "github-copilot-enterprise",
578
+ name: "GitHub Copilot Enterprise",
579
+ models: mapValues(githubCopilot.models, (model) => ({
580
+ ...model,
581
+ providerID: "github-copilot-enterprise",
582
+ })),
583
+ }
584
+ }
585
+
586
+ function mergeProvider(providerID: string, provider: Partial<Info>) {
587
+ const existing = providers[providerID]
588
+ if (existing) {
589
+ // @ts-expect-error
590
+ providers[providerID] = mergeDeep(existing, provider)
591
+ return
592
+ }
593
+ const match = database[providerID]
594
+ if (!match) return
595
+ // @ts-expect-error
596
+ providers[providerID] = mergeDeep(match, provider)
597
+ }
598
+
599
+ // extend database from config (RIRD only)
600
+ for (const [providerID, provider] of configProviders) {
601
+ if (providerID !== "rird") continue
602
+ const existing = database[providerID]
603
+ const parsed: Info = {
604
+ id: providerID,
605
+ name: provider.name ?? existing?.name ?? providerID,
606
+ env: provider.env ?? existing?.env ?? [],
607
+ options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
608
+ source: "config",
609
+ models: existing?.models ?? {},
610
+ }
611
+
612
+ for (const [modelID, model] of Object.entries(provider.models ?? {})) {
613
+ const existingModel = parsed.models[model.id ?? modelID]
614
+ const name = iife(() => {
615
+ if (model.name) return model.name
616
+ if (model.id && model.id !== modelID) return modelID
617
+ return existingModel?.name ?? modelID
618
+ })
619
+ const parsedModel: Model = {
620
+ id: modelID,
621
+ api: {
622
+ id: model.id ?? existingModel?.api.id ?? modelID,
623
+ npm:
624
+ model.provider?.npm ?? provider.npm ?? existingModel?.api.npm ?? modelsDev[providerID]?.npm ?? providerID,
625
+ url: provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
626
+ },
627
+ status: model.status ?? existingModel?.status ?? "active",
628
+ name,
629
+ providerID,
630
+ capabilities: {
631
+ temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
632
+ reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
633
+ attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
634
+ toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
635
+ input: {
636
+ text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
637
+ audio: model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
638
+ image: model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
639
+ video: model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
640
+ pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
641
+ },
642
+ output: {
643
+ text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
644
+ audio: model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false,
645
+ image: model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false,
646
+ video: model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
647
+ pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
648
+ },
649
+ interleaved: model.interleaved ?? false,
650
+ },
651
+ cost: {
652
+ input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
653
+ output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
654
+ cache: {
655
+ read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
656
+ write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
657
+ },
658
+ },
659
+ options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
660
+ limit: {
661
+ context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
662
+ output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
663
+ },
664
+ headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
665
+ family: model.family ?? existingModel?.family ?? "",
666
+ release_date: model.release_date ?? existingModel?.release_date ?? "",
667
+ }
668
+ parsed.models[modelID] = parsedModel
669
+ }
670
+ database[providerID] = parsed
671
+ }
672
+
673
+ // load env
674
+ const env = Env.all()
675
+ for (const [providerID, provider] of Object.entries(database)) {
676
+ if (providerID !== "rird") continue
677
+ if (disabled.has(providerID)) continue
678
+ const apiKey = provider.env.map((item) => env[item]).find(Boolean)
679
+ if (!apiKey) continue
680
+ mergeProvider(providerID, {
681
+ source: "env",
682
+ key: provider.env.length === 1 ? apiKey : undefined,
683
+ })
684
+ }
685
+
686
+ // load apikeys
687
+ for (const [providerID, provider] of Object.entries(await Auth.all())) {
688
+ if (providerID !== "rird") continue
689
+ if (disabled.has(providerID)) continue
690
+ if (provider.type === "api") {
691
+ mergeProvider(providerID, {
692
+ source: "api",
693
+ key: provider.key,
694
+ })
695
+ }
696
+ }
697
+
698
+ for (const plugin of await Plugin.list()) {
699
+ if (!plugin.auth) continue
700
+ const providerID = plugin.auth.provider
701
+ if (providerID !== "rird") continue
702
+ if (disabled.has(providerID)) continue
703
+
704
+ let auth = await Auth.get(providerID)
705
+ let hasAuth = !!auth
706
+
707
+ if (!hasAuth) continue
708
+ if (!plugin.auth.loader) continue
709
+
710
+ // Load for the main provider if auth exists
711
+ if (auth) {
712
+ const options = await plugin.auth.loader(() => Auth.get(providerID) as any, database[plugin.auth.provider])
713
+ mergeProvider(plugin.auth.provider, {
714
+ source: "custom",
715
+ options: options,
716
+ })
717
+ }
718
+ }
719
+
720
+ for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) {
721
+ if (disabled.has(providerID)) continue
722
+ const data = database[providerID]
723
+ // Skip if no data, except for hardcoded providers that don't need data
724
+ if (!data && !["rird", "anthropic", "openai", "azure"].includes(providerID)) continue
725
+ const result = await fn(data)
726
+ if (result && (result.autoload || providers[providerID])) {
727
+ if (result.getModel) modelLoaders[providerID] = result.getModel
728
+ mergeProvider(providerID, {
729
+ source: "custom",
730
+ options: result.options,
731
+ key: result.key,
732
+ })
733
+ }
734
+ }
735
+
736
+ // load config
737
+ for (const [providerID, provider] of configProviders) {
738
+ if (providerID !== "rird") continue
739
+ const partial: Partial<Info> = { source: "config" }
740
+ if (provider.env) partial.env = provider.env
741
+ if (provider.name) partial.name = provider.name
742
+ if (provider.options) partial.options = provider.options
743
+ mergeProvider(providerID, partial)
744
+ }
745
+
746
+ for (const [providerID, provider] of Object.entries(providers)) {
747
+ if (!isProviderAllowed(providerID)) {
748
+ delete providers[providerID]
749
+ continue
750
+ }
751
+
752
+ if (providerID === "github-copilot" || providerID === "github-copilot-enterprise") {
753
+ provider.models = mapValues(provider.models, (model) => ({
754
+ ...model,
755
+ api: {
756
+ ...model.api,
757
+ npm: "@ai-sdk/github-copilot",
758
+ },
759
+ }))
760
+ }
761
+
762
+ const configProvider = config.provider?.[providerID]
763
+
764
+ for (const [modelID, model] of Object.entries(provider.models)) {
765
+ model.api.id = model.api.id ?? model.id ?? modelID
766
+ if (modelID === "gpt-5-chat-latest" || (providerID === "openrouter" && modelID === "openai/gpt-5-chat"))
767
+ delete provider.models[modelID]
768
+ if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) delete provider.models[modelID]
769
+ if (
770
+ (configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
771
+ (configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
772
+ )
773
+ delete provider.models[modelID]
774
+ }
775
+
776
+ if (Object.keys(provider.models).length === 0) {
777
+ delete providers[providerID]
778
+ continue
779
+ }
780
+
781
+ log.info("found", { providerID })
782
+ }
783
+
784
+ // Enforce RIRD-only surface area
785
+ for (const key of Object.keys(providers)) {
786
+ if (key !== "rird") delete providers[key]
787
+ }
788
+
789
+ return {
790
+ models: languages,
791
+ providers,
792
+ sdk,
793
+ modelLoaders,
794
+ }
795
+ })
796
+
797
+ export async function list() {
798
+ return state().then((state) => state.providers)
799
+ }
800
+
801
+ async function getSDK(model: Model) {
802
+ try {
803
+ using _ = log.time("getSDK", {
804
+ providerID: model.providerID,
805
+ })
806
+ const s = await state()
807
+ const provider = s.providers[model.providerID]
808
+ const options = { ...provider.options }
809
+
810
+ if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
811
+ options["includeUsage"] = true
812
+ }
813
+
814
+ if (!options["baseURL"]) options["baseURL"] = model.api.url
815
+ if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
816
+ if (model.headers)
817
+ options["headers"] = {
818
+ ...options["headers"],
819
+ ...model.headers,
820
+ }
821
+
822
+ const key = Bun.hash.xxHash32(JSON.stringify({ npm: model.api.npm, options }))
823
+ const existing = s.sdk.get(key)
824
+ if (existing) return existing
825
+
826
+ const customFetch = options["fetch"]
827
+
828
+ options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
829
+ // Preserve custom fetch if it exists, wrap it with timeout logic
830
+ const fetchFn = customFetch ?? fetch
831
+ const opts = init ?? {}
832
+
833
+ if (options["timeout"] !== undefined && options["timeout"] !== null) {
834
+ const signals: AbortSignal[] = []
835
+ if (opts.signal) signals.push(opts.signal)
836
+ if (options["timeout"] !== false) signals.push(AbortSignal.timeout(options["timeout"]))
837
+
838
+ const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0]
839
+
840
+ opts.signal = combined
841
+ }
842
+
843
+ return fetchFn(input, {
844
+ ...opts,
845
+ // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
846
+ timeout: false,
847
+ })
848
+ }
849
+
850
+ // Special case: google-vertex-anthropic uses a subpath import
851
+ const bundledKey =
852
+ model.providerID === "google-vertex-anthropic" ? "@ai-sdk/google-vertex/anthropic" : model.api.npm
853
+ const bundledFn = BUNDLED_PROVIDERS[bundledKey]
854
+ if (bundledFn) {
855
+ log.info("using bundled provider", { providerID: model.providerID, pkg: bundledKey })
856
+ const loaded = bundledFn({
857
+ name: model.providerID,
858
+ ...options,
859
+ })
860
+ s.sdk.set(key, loaded)
861
+ return loaded as SDK
862
+ }
863
+
864
+ let installedPath: string
865
+ if (!model.api.npm.startsWith("file://")) {
866
+ installedPath = await BunProc.install(model.api.npm, "latest")
867
+ } else {
868
+ log.info("loading local provider", { pkg: model.api.npm })
869
+ installedPath = model.api.npm
870
+ }
871
+
872
+ const mod = await import(installedPath)
873
+
874
+ const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
875
+ const loaded = fn({
876
+ name: model.providerID,
877
+ ...options,
878
+ })
879
+ s.sdk.set(key, loaded)
880
+ return loaded as SDK
881
+ } catch (e) {
882
+ throw new InitError({ providerID: model.providerID }, { cause: e })
883
+ }
884
+ }
885
+
886
+ export async function getProvider(providerID: string) {
887
+ return state().then((s) => s.providers[providerID])
888
+ }
889
+
890
+ export async function getModel(providerID: string, modelID: string) {
891
+ const s = await state()
892
+ const provider = s.providers[providerID]
893
+ if (!provider) {
894
+ const availableProviders = Object.keys(s.providers)
895
+ const matches = fuzzysort.go(providerID, availableProviders, { limit: 3, threshold: -10000 })
896
+ const suggestions = matches.map((m) => m.target)
897
+ throw new ModelNotFoundError({ providerID, modelID, suggestions })
898
+ }
899
+
900
+ const info = provider.models[modelID]
901
+ if (!info) {
902
+ const availableModels = Object.keys(provider.models)
903
+ const matches = fuzzysort.go(modelID, availableModels, { limit: 3, threshold: -10000 })
904
+ const suggestions = matches.map((m) => m.target)
905
+ throw new ModelNotFoundError({ providerID, modelID, suggestions })
906
+ }
907
+ return info
908
+ }
909
+
910
+ export async function getLanguage(model: Model): Promise<LanguageModelV2> {
911
+ const s = await state()
912
+ const key = `${model.providerID}/${model.id}`
913
+ if (s.models.has(key)) return s.models.get(key)!
914
+
915
+ const provider = s.providers[model.providerID]
916
+ const sdk = await getSDK(model)
917
+
918
+ try {
919
+ const language = s.modelLoaders[model.providerID]
920
+ ? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
921
+ : sdk.languageModel(model.api.id)
922
+ s.models.set(key, language)
923
+ return language
924
+ } catch (e) {
925
+ if (e instanceof NoSuchModelError)
926
+ throw new ModelNotFoundError(
927
+ {
928
+ modelID: model.id,
929
+ providerID: model.providerID,
930
+ },
931
+ { cause: e },
932
+ )
933
+ throw e
934
+ }
935
+ }
936
+
937
+ export async function closest(providerID: string, query: string[]) {
938
+ const s = await state()
939
+ const provider = s.providers[providerID]
940
+ if (!provider) return undefined
941
+ for (const item of query) {
942
+ for (const modelID of Object.keys(provider.models)) {
943
+ if (modelID.includes(item))
944
+ return {
945
+ providerID,
946
+ modelID,
947
+ }
948
+ }
949
+ }
950
+ }
951
+
952
+ export async function getSmallModel(providerID: string) {
953
+ const cfg = await Config.get()
954
+
955
+ if (cfg.small_model) {
956
+ const parsed = parseModel(cfg.small_model)
957
+ const s = await state()
958
+ if (s.providers[parsed.providerID]) {
959
+ return getModel(parsed.providerID, parsed.modelID)
960
+ }
961
+ }
962
+
963
+ const provider = await state().then((state) => state.providers[providerID])
964
+ if (provider) {
965
+ if (providerID.startsWith("rird")) {
966
+ const priority = ["qwen3:8b", "deepseek-reasoner", "qwen2.5-vl-7b"]
967
+ for (const item of priority) {
968
+ for (const model of Object.keys(provider.models)) {
969
+ if (model === item) return getModel(providerID, model)
970
+ }
971
+ }
972
+ const first = Object.keys(provider.models)[0]
973
+ if (first) return getModel(providerID, first)
974
+ }
975
+ }
976
+
977
+ // Check if rird provider is available
978
+ const rirdProvider = await state().then((state) => state.providers["rird"])
979
+ if (rirdProvider && rirdProvider.models["qwen3:8b"]) {
980
+ return getModel("rird", "qwen3:8b")
981
+ }
982
+
983
+ return undefined
984
+ }
985
+
986
+ export async function getVisionModel(providerID: string) {
987
+ const cfg = await Config.get()
988
+
989
+ if (cfg.visionModel) {
990
+ const parsed = parseModel(cfg.visionModel)
991
+ const s = await state()
992
+ if (s.providers[parsed.providerID]) {
993
+ return getModel(parsed.providerID, parsed.modelID)
994
+ }
995
+ }
996
+
997
+ if (providerID.startsWith("rird")) {
998
+ const rirdProvider = await state().then((state) => state.providers["rird"])
999
+ if (rirdProvider?.models["qwen2.5-vl-7b"]) return getModel("rird", "qwen2.5-vl-7b")
1000
+ }
1001
+
1002
+ return undefined
1003
+ }
1004
+
1005
+ const priority = ["qwen3:8b", "deepseek-reasoner", "qwen2.5-vl-7b"]
1006
+ export function sort(models: Model[]) {
1007
+ return sortBy(
1008
+ models,
1009
+ [(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
1010
+ [(model) => (model.id.includes("latest") ? 0 : 1), "asc"],
1011
+ [(model) => model.id, "desc"],
1012
+ )
1013
+ }
1014
+
1015
+ export async function defaultModel() {
1016
+ const cfg = await Config.get()
1017
+ if (cfg.model) {
1018
+ const parsed = parseModel(cfg.model)
1019
+ const s = await state()
1020
+ if (s.providers[parsed.providerID]) return parsed
1021
+ }
1022
+
1023
+ const available = await list().then((val) => Object.values(val))
1024
+ const provider =
1025
+ available.find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id)) ??
1026
+ available[0]
1027
+ if (!provider) throw new Error("no providers found")
1028
+ if (provider.id === "rird" && provider.models["qwen3:8b"]) {
1029
+ return { providerID: "rird", modelID: "qwen3:8b" }
1030
+ }
1031
+ const [model] = sort(Object.values(provider.models))
1032
+ if (!model) throw new Error("no models found")
1033
+ return {
1034
+ providerID: provider.id,
1035
+ modelID: model.id,
1036
+ }
1037
+ }
1038
+
1039
+ export function parseModel(model: string) {
1040
+ const [providerID, ...rest] = model.split("/")
1041
+ return {
1042
+ providerID: publicHostedProviderID(providerID),
1043
+ modelID: rest.join("/"),
1044
+ }
1045
+ }
1046
+
1047
+ export const ModelNotFoundError = NamedError.create(
1048
+ "ProviderModelNotFoundError",
1049
+ z.object({
1050
+ providerID: z.string(),
1051
+ modelID: z.string(),
1052
+ suggestions: z.array(z.string()).optional(),
1053
+ }),
1054
+ )
1055
+
1056
+ export const InitError = NamedError.create(
1057
+ "ProviderInitError",
1058
+ z.object({
1059
+ providerID: z.string(),
1060
+ }),
1061
+ )
1062
+
1063
+ /**
1064
+ * Map model IDs to display names
1065
+ */
1066
+ export function getRirdModelName(modelId: string, fallbackName?: string): string {
1067
+ const lower = modelId.toLowerCase()
1068
+ const lowerFallback = (fallbackName || "").toLowerCase()
1069
+
1070
+ // Vision models
1071
+ if (
1072
+ lower.includes("vision") ||
1073
+ lower.includes("-vl") ||
1074
+ lower.includes("image") ||
1075
+ lowerFallback.includes("vision")
1076
+ ) {
1077
+ return "Vision"
1078
+ }
1079
+
1080
+ // Reasoning/thinking models (o1, o3, thinking, reasoning)
1081
+ if (
1082
+ lower.includes("o1") ||
1083
+ lower.includes("o3") ||
1084
+ lower.includes("thinking") ||
1085
+ lower.includes("reason") ||
1086
+ lowerFallback.includes("thinking") ||
1087
+ lowerFallback.includes("reason")
1088
+ ) {
1089
+ return "Reasoning"
1090
+ }
1091
+
1092
+ // Fast/small models for actions
1093
+ if (
1094
+ lower.includes("-mini") ||
1095
+ lower.includes("nano") ||
1096
+ lower.includes("small") ||
1097
+ lower.includes("fast") ||
1098
+ lower.includes("flash") ||
1099
+ lower.includes("minimax") ||
1100
+ lowerFallback.includes("mini") ||
1101
+ lowerFallback.includes("fast")
1102
+ ) {
1103
+ return "Fast"
1104
+ }
1105
+
1106
+ // Default for all other models
1107
+ return "AI"
1108
+ }
1109
+ }