rird 1.0.200

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