orynacode-ai 1.16.2

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 (938) hide show
  1. package/AGENTS.md +131 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/orynacode +199 -0
  6. package/bunfig.toml +7 -0
  7. package/git +0 -0
  8. package/migration/20260511173437_session-metadata/migration.sql +1 -0
  9. package/migration/20260511173437_session-metadata/snapshot.json +1500 -0
  10. package/package.json +155 -0
  11. package/parsers-config.ts +386 -0
  12. package/script/bench-search.ts +115 -0
  13. package/script/bench-test-suite.ts +52 -0
  14. package/script/build.ts +244 -0
  15. package/script/generate.ts +14 -0
  16. package/script/httpapi-exercise.ts +1 -0
  17. package/script/postinstall.mjs +189 -0
  18. package/script/profile-test-files.ts +42 -0
  19. package/script/publish.ts +213 -0
  20. package/script/run-workspace-server +106 -0
  21. package/script/schema.ts +77 -0
  22. package/script/time.ts +6 -0
  23. package/script/trace-imports.ts +153 -0
  24. package/specs/effect/error-boundaries-plan.md +235 -0
  25. package/specs/effect/errors.md +207 -0
  26. package/specs/effect/facades.md +218 -0
  27. package/specs/effect/guide.md +247 -0
  28. package/specs/effect/instance-context.md +13 -0
  29. package/specs/effect/loose-ends.md +30 -0
  30. package/specs/effect/migration.md +62 -0
  31. package/specs/effect/routes.md +61 -0
  32. package/specs/effect/schema.md +88 -0
  33. package/specs/effect/server-package.md +58 -0
  34. package/specs/effect/todo.md +241 -0
  35. package/specs/effect/tools.md +88 -0
  36. package/specs/openapi-translation-cleanup.md +204 -0
  37. package/specs/tui-plugins.md +544 -0
  38. package/specs/v2/api.ts +67 -0
  39. package/specs/v2/message-shape.md +136 -0
  40. package/specs/v2/notifications.md +13 -0
  41. package/specs/v2/tui-command-shim.md +67 -0
  42. package/src/account/account.ts +459 -0
  43. package/src/account/repo.ts +170 -0
  44. package/src/account/schema.ts +99 -0
  45. package/src/account/url.ts +8 -0
  46. package/src/acp/agent.ts +95 -0
  47. package/src/acp/config-option.ts +203 -0
  48. package/src/acp/content.ts +250 -0
  49. package/src/acp/directory.ts +210 -0
  50. package/src/acp/error.ts +90 -0
  51. package/src/acp/event.ts +344 -0
  52. package/src/acp/permission.ts +145 -0
  53. package/src/acp/profile.ts +42 -0
  54. package/src/acp/service.ts +1062 -0
  55. package/src/acp/session.ts +231 -0
  56. package/src/acp/tool.ts +317 -0
  57. package/src/acp/usage.ts +239 -0
  58. package/src/agent/agent.ts +433 -0
  59. package/src/agent/generate.txt +75 -0
  60. package/src/agent/prompt/compaction.txt +9 -0
  61. package/src/agent/prompt/explore.txt +18 -0
  62. package/src/agent/prompt/summary.txt +11 -0
  63. package/src/agent/prompt/title.txt +44 -0
  64. package/src/agent/subagent-permissions.ts +35 -0
  65. package/src/audio.d.ts +14 -0
  66. package/src/auth/index.ts +96 -0
  67. package/src/background/job.ts +36 -0
  68. package/src/bus/global.ts +22 -0
  69. package/src/cli/bootstrap.ts +11 -0
  70. package/src/cli/cmd/account.ts +264 -0
  71. package/src/cli/cmd/acp.ts +76 -0
  72. package/src/cli/cmd/agent.ts +259 -0
  73. package/src/cli/cmd/cmd.ts +7 -0
  74. package/src/cli/cmd/db.ts +62 -0
  75. package/src/cli/cmd/debug/agent.handler.ts +193 -0
  76. package/src/cli/cmd/debug/agent.ts +27 -0
  77. package/src/cli/cmd/debug/config.ts +14 -0
  78. package/src/cli/cmd/debug/file.ts +87 -0
  79. package/src/cli/cmd/debug/index.ts +87 -0
  80. package/src/cli/cmd/debug/lsp.ts +51 -0
  81. package/src/cli/cmd/debug/ripgrep.ts +99 -0
  82. package/src/cli/cmd/debug/scrap.ts +18 -0
  83. package/src/cli/cmd/debug/skill.ts +15 -0
  84. package/src/cli/cmd/debug/snapshot.ts +50 -0
  85. package/src/cli/cmd/debug/startup.ts +11 -0
  86. package/src/cli/cmd/debug/v2.ts +46 -0
  87. package/src/cli/cmd/export.ts +292 -0
  88. package/src/cli/cmd/generate.ts +54 -0
  89. package/src/cli/cmd/github.handler.ts +1593 -0
  90. package/src/cli/cmd/github.shared.ts +30 -0
  91. package/src/cli/cmd/github.ts +42 -0
  92. package/src/cli/cmd/import.ts +224 -0
  93. package/src/cli/cmd/mcp.ts +846 -0
  94. package/src/cli/cmd/models.ts +66 -0
  95. package/src/cli/cmd/plug.ts +230 -0
  96. package/src/cli/cmd/pr.ts +115 -0
  97. package/src/cli/cmd/prompt-display.ts +48 -0
  98. package/src/cli/cmd/providers.ts +506 -0
  99. package/src/cli/cmd/run/demo.ts +1274 -0
  100. package/src/cli/cmd/run/entry.body.ts +194 -0
  101. package/src/cli/cmd/run/footer.command.tsx +899 -0
  102. package/src/cli/cmd/run/footer.menu.tsx +306 -0
  103. package/src/cli/cmd/run/footer.permission.tsx +475 -0
  104. package/src/cli/cmd/run/footer.prompt.tsx +1207 -0
  105. package/src/cli/cmd/run/footer.question.tsx +579 -0
  106. package/src/cli/cmd/run/footer.subagent.tsx +171 -0
  107. package/src/cli/cmd/run/footer.ts +1092 -0
  108. package/src/cli/cmd/run/footer.view.tsx +935 -0
  109. package/src/cli/cmd/run/otel.ts +117 -0
  110. package/src/cli/cmd/run/permission.shared.ts +256 -0
  111. package/src/cli/cmd/run/prompt.shared.ts +147 -0
  112. package/src/cli/cmd/run/question.shared.ts +340 -0
  113. package/src/cli/cmd/run/runtime.boot.ts +210 -0
  114. package/src/cli/cmd/run/runtime.lifecycle.ts +369 -0
  115. package/src/cli/cmd/run/runtime.queue.ts +347 -0
  116. package/src/cli/cmd/run/runtime.shared.ts +17 -0
  117. package/src/cli/cmd/run/runtime.stdin.ts +37 -0
  118. package/src/cli/cmd/run/runtime.ts +879 -0
  119. package/src/cli/cmd/run/scrollback.shared.ts +92 -0
  120. package/src/cli/cmd/run/scrollback.surface.ts +435 -0
  121. package/src/cli/cmd/run/scrollback.writer.tsx +335 -0
  122. package/src/cli/cmd/run/session-data.ts +1113 -0
  123. package/src/cli/cmd/run/session-replay.ts +301 -0
  124. package/src/cli/cmd/run/session.shared.ts +196 -0
  125. package/src/cli/cmd/run/splash.ts +310 -0
  126. package/src/cli/cmd/run/stream.transport.ts +1465 -0
  127. package/src/cli/cmd/run/stream.ts +175 -0
  128. package/src/cli/cmd/run/subagent-data.ts +844 -0
  129. package/src/cli/cmd/run/theme.ts +603 -0
  130. package/src/cli/cmd/run/tool.ts +1489 -0
  131. package/src/cli/cmd/run/trace.ts +94 -0
  132. package/src/cli/cmd/run/types.ts +342 -0
  133. package/src/cli/cmd/run/variant.shared.ts +215 -0
  134. package/src/cli/cmd/run.ts +879 -0
  135. package/src/cli/cmd/serve.ts +24 -0
  136. package/src/cli/cmd/session.ts +147 -0
  137. package/src/cli/cmd/stats.ts +393 -0
  138. package/src/cli/cmd/tui/app.tsx +1113 -0
  139. package/src/cli/cmd/tui/attach.ts +103 -0
  140. package/src/cli/cmd/tui/attention.ts +262 -0
  141. package/src/cli/cmd/tui/component/bg-pulse-render.ts +436 -0
  142. package/src/cli/cmd/tui/component/bg-pulse.tsx +99 -0
  143. package/src/cli/cmd/tui/component/border.tsx +21 -0
  144. package/src/cli/cmd/tui/component/command-palette.tsx +79 -0
  145. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  146. package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
  147. package/src/cli/cmd/tui/component/dialog-mcp.tsx +85 -0
  148. package/src/cli/cmd/tui/component/dialog-model.tsx +185 -0
  149. package/src/cli/cmd/tui/component/dialog-move-session.tsx +240 -0
  150. package/src/cli/cmd/tui/component/dialog-provider.tsx +687 -0
  151. package/src/cli/cmd/tui/component/dialog-retry-action.tsx +160 -0
  152. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +99 -0
  153. package/src/cli/cmd/tui/component/dialog-session-list.tsx +318 -0
  154. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  155. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  156. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  157. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  158. package/src/cli/cmd/tui/component/dialog-tag.tsx +47 -0
  159. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  160. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  161. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +313 -0
  162. package/src/cli/cmd/tui/component/dialog-workspace-file-changes.tsx +144 -0
  163. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +112 -0
  164. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +69 -0
  165. package/src/cli/cmd/tui/component/error-component.tsx +81 -0
  166. package/src/cli/cmd/tui/component/logo.tsx +885 -0
  167. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  168. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +799 -0
  169. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  170. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  171. package/src/cli/cmd/tui/component/prompt/history.tsx +117 -0
  172. package/src/cli/cmd/tui/component/prompt/index.tsx +1725 -0
  173. package/src/cli/cmd/tui/component/prompt/move.tsx +192 -0
  174. package/src/cli/cmd/tui/component/prompt/part.ts +31 -0
  175. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  176. package/src/cli/cmd/tui/component/prompt/traits.ts +35 -0
  177. package/src/cli/cmd/tui/component/prompt/workspace.tsx +137 -0
  178. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  179. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  180. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  181. package/src/cli/cmd/tui/component/use-connected.tsx +9 -0
  182. package/src/cli/cmd/tui/component/workspace-label.tsx +19 -0
  183. package/src/cli/cmd/tui/config/cwd.ts +5 -0
  184. package/src/cli/cmd/tui/config/keybind.ts +467 -0
  185. package/src/cli/cmd/tui/config/tui-migrate.ts +154 -0
  186. package/src/cli/cmd/tui/config/tui-schema.ts +88 -0
  187. package/src/cli/cmd/tui/config/tui.ts +308 -0
  188. package/src/cli/cmd/tui/context/agent.tsx +11 -0
  189. package/src/cli/cmd/tui/context/aggregate-failures.ts +51 -0
  190. package/src/cli/cmd/tui/context/args.tsx +15 -0
  191. package/src/cli/cmd/tui/context/directory.ts +15 -0
  192. package/src/cli/cmd/tui/context/editor-zed.ts +287 -0
  193. package/src/cli/cmd/tui/context/editor.ts +469 -0
  194. package/src/cli/cmd/tui/context/event.ts +38 -0
  195. package/src/cli/cmd/tui/context/exit.tsx +42 -0
  196. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  197. package/src/cli/cmd/tui/context/kv.tsx +76 -0
  198. package/src/cli/cmd/tui/context/local.tsx +510 -0
  199. package/src/cli/cmd/tui/context/path-format.tsx +39 -0
  200. package/src/cli/cmd/tui/context/project.tsx +111 -0
  201. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  202. package/src/cli/cmd/tui/context/route.tsx +52 -0
  203. package/src/cli/cmd/tui/context/sdk.tsx +142 -0
  204. package/src/cli/cmd/tui/context/sync-v2.tsx +447 -0
  205. package/src/cli/cmd/tui/context/sync.tsx +628 -0
  206. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  207. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  208. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  209. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +230 -0
  210. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +230 -0
  211. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  212. package/src/cli/cmd/tui/context/theme/cobalt2.json +225 -0
  213. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  214. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  215. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  216. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  217. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  218. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  219. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  220. package/src/cli/cmd/tui/context/theme/lucent-orng.json +234 -0
  221. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  222. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  223. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  224. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  225. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  226. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  227. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  228. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  229. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  230. package/src/cli/cmd/tui/context/theme/oryna.json +95 -0
  231. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  232. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  233. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  234. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  235. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  236. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  237. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  238. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  239. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  240. package/src/cli/cmd/tui/context/theme.tsx +1341 -0
  241. package/src/cli/cmd/tui/context/thinking.ts +67 -0
  242. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  243. package/src/cli/cmd/tui/event.ts +53 -0
  244. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +98 -0
  245. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +288 -0
  246. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +59 -0
  247. package/src/cli/cmd/tui/feature-plugins/session/dialog.tsx +356 -0
  248. package/src/cli/cmd/tui/feature-plugins/session/index.tsx +32 -0
  249. package/src/cli/cmd/tui/feature-plugins/session/preview-pane.tsx +288 -0
  250. package/src/cli/cmd/tui/feature-plugins/session/util.tsx +54 -0
  251. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +65 -0
  252. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +70 -0
  253. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +96 -0
  254. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +65 -0
  255. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +97 -0
  256. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +49 -0
  257. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer-file-tree-utils.ts +232 -0
  258. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer-file-tree.tsx +162 -0
  259. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer-ui.tsx +103 -0
  260. package/src/cli/cmd/tui/feature-plugins/system/diff-viewer.tsx +1058 -0
  261. package/src/cli/cmd/tui/feature-plugins/system/notifications.ts +94 -0
  262. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +269 -0
  263. package/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx +1184 -0
  264. package/src/cli/cmd/tui/feature-plugins/system/which-key.tsx +608 -0
  265. package/src/cli/cmd/tui/keymap.tsx +283 -0
  266. package/src/cli/cmd/tui/layer.ts +6 -0
  267. package/src/cli/cmd/tui/plugin/api.tsx +390 -0
  268. package/src/cli/cmd/tui/plugin/command-shim.ts +109 -0
  269. package/src/cli/cmd/tui/plugin/internal.ts +42 -0
  270. package/src/cli/cmd/tui/plugin/runtime.ts +1131 -0
  271. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  272. package/src/cli/cmd/tui/routes/home/session-destination.tsx +39 -0
  273. package/src/cli/cmd/tui/routes/home.tsx +149 -0
  274. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
  275. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +108 -0
  276. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  277. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  278. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  279. package/src/cli/cmd/tui/routes/session/index.tsx +2629 -0
  280. package/src/cli/cmd/tui/routes/session/permission.tsx +729 -0
  281. package/src/cli/cmd/tui/routes/session/question.tsx +514 -0
  282. package/src/cli/cmd/tui/routes/session/sidebar.tsx +102 -0
  283. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +132 -0
  284. package/src/cli/cmd/tui/thread.ts +264 -0
  285. package/src/cli/cmd/tui/ui/dialog-alert.tsx +66 -0
  286. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +108 -0
  287. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +217 -0
  288. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  289. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +126 -0
  290. package/src/cli/cmd/tui/ui/dialog-select.tsx +712 -0
  291. package/src/cli/cmd/tui/ui/dialog.tsx +218 -0
  292. package/src/cli/cmd/tui/ui/link.tsx +34 -0
  293. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  294. package/src/cli/cmd/tui/ui/toast.tsx +102 -0
  295. package/src/cli/cmd/tui/util/audio.ts +58 -0
  296. package/src/cli/cmd/tui/util/clipboard.ts +181 -0
  297. package/src/cli/cmd/tui/util/collapse-tool-output.ts +19 -0
  298. package/src/cli/cmd/tui/util/editor.ts +43 -0
  299. package/src/cli/cmd/tui/util/layout.ts +25 -0
  300. package/src/cli/cmd/tui/util/model.ts +23 -0
  301. package/src/cli/cmd/tui/util/provider-origin.ts +7 -0
  302. package/src/cli/cmd/tui/util/revert-diff.ts +18 -0
  303. package/src/cli/cmd/tui/util/scroll.ts +25 -0
  304. package/src/cli/cmd/tui/util/selection.ts +73 -0
  305. package/src/cli/cmd/tui/util/signal.ts +41 -0
  306. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  307. package/src/cli/cmd/tui/validate-session.ts +29 -0
  308. package/src/cli/cmd/tui/win32.ts +130 -0
  309. package/src/cli/cmd/tui/worker.ts +99 -0
  310. package/src/cli/cmd/uninstall.ts +353 -0
  311. package/src/cli/cmd/upgrade.ts +74 -0
  312. package/src/cli/cmd/web.ts +84 -0
  313. package/src/cli/effect/prompt.ts +37 -0
  314. package/src/cli/effect-cmd.ts +96 -0
  315. package/src/cli/error.ts +118 -0
  316. package/src/cli/heap.ts +59 -0
  317. package/src/cli/logo.ts +21 -0
  318. package/src/cli/network.ts +64 -0
  319. package/src/cli/ui.ts +132 -0
  320. package/src/cli/upgrade.ts +53 -0
  321. package/src/command/index.ts +181 -0
  322. package/src/command/template/initialize.txt +66 -0
  323. package/src/command/template/review.txt +101 -0
  324. package/src/config/agent.ts +68 -0
  325. package/src/config/command.ts +45 -0
  326. package/src/config/config.ts +679 -0
  327. package/src/config/entry-name.ts +19 -0
  328. package/src/config/managed.ts +77 -0
  329. package/src/config/markdown.ts +36 -0
  330. package/src/config/parse.ts +79 -0
  331. package/src/config/paths.ts +45 -0
  332. package/src/config/plugin.ts +79 -0
  333. package/src/config/reference.ts +48 -0
  334. package/src/config/variable.ts +91 -0
  335. package/src/control-plane/adapters/index.ts +41 -0
  336. package/src/control-plane/adapters/worktree.ts +96 -0
  337. package/src/control-plane/dev/README.md +19 -0
  338. package/src/control-plane/dev/debug-workspace-plugin.ts +73 -0
  339. package/src/control-plane/types.ts +59 -0
  340. package/src/control-plane/util.ts +39 -0
  341. package/src/control-plane/workspace-adapter-runtime.ts +51 -0
  342. package/src/control-plane/workspace-context.ts +26 -0
  343. package/src/control-plane/workspace.ts +1075 -0
  344. package/src/effect/app-runtime.ts +133 -0
  345. package/src/effect/bootstrap-runtime.ts +23 -0
  346. package/src/effect/bridge.ts +84 -0
  347. package/src/effect/config-service.ts +67 -0
  348. package/src/effect/instance-ref.ts +11 -0
  349. package/src/effect/instance-registry.ts +12 -0
  350. package/src/effect/instance-state.ts +72 -0
  351. package/src/effect/promise.ts +17 -0
  352. package/src/effect/run-service.ts +47 -0
  353. package/src/effect/runner.ts +217 -0
  354. package/src/effect/runtime-flags.ts +76 -0
  355. package/src/env/index.ts +40 -0
  356. package/src/event-v2-bridge.ts +76 -0
  357. package/src/format/formatter.ts +404 -0
  358. package/src/format/index.ts +212 -0
  359. package/src/git/index.ts +347 -0
  360. package/src/id/id.ts +80 -0
  361. package/src/ide/index.ts +70 -0
  362. package/src/image/image.ts +177 -0
  363. package/src/index.ts +208 -0
  364. package/src/installation/index.ts +349 -0
  365. package/src/lsp/client.ts +686 -0
  366. package/src/lsp/diagnostic.ts +29 -0
  367. package/src/lsp/language.ts +121 -0
  368. package/src/lsp/launch.ts +21 -0
  369. package/src/lsp/lsp.ts +517 -0
  370. package/src/lsp/server.ts +2064 -0
  371. package/src/markdown.d.ts +4 -0
  372. package/src/mcp/auth.ts +171 -0
  373. package/src/mcp/index.ts +982 -0
  374. package/src/mcp/oauth-callback.ts +232 -0
  375. package/src/mcp/oauth-provider.ts +217 -0
  376. package/src/node.ts +5 -0
  377. package/src/oryna/agent.ts +112 -0
  378. package/src/oryna/reply-service.ts +8 -0
  379. package/src/patch/index.ts +689 -0
  380. package/src/permission/arity.ts +163 -0
  381. package/src/permission/evaluate.ts +1 -0
  382. package/src/permission/index.ts +230 -0
  383. package/src/plugin/azure.ts +26 -0
  384. package/src/plugin/cloudflare.ts +76 -0
  385. package/src/plugin/digitalocean.ts +391 -0
  386. package/src/plugin/github-copilot/copilot.ts +417 -0
  387. package/src/plugin/github-copilot/models.ts +246 -0
  388. package/src/plugin/index.ts +323 -0
  389. package/src/plugin/install.ts +439 -0
  390. package/src/plugin/loader.ts +237 -0
  391. package/src/plugin/meta.ts +188 -0
  392. package/src/plugin/openai/README.md +31 -0
  393. package/src/plugin/openai/codex.ts +647 -0
  394. package/src/plugin/openai/ws-pool.ts +290 -0
  395. package/src/plugin/openai/ws.ts +381 -0
  396. package/src/plugin/oryna.ts +349 -0
  397. package/src/plugin/shared.ts +323 -0
  398. package/src/plugin/xai.ts +742 -0
  399. package/src/project/bootstrap-service.ts +9 -0
  400. package/src/project/bootstrap.ts +80 -0
  401. package/src/project/instance-context.ts +24 -0
  402. package/src/project/instance-layer.ts +11 -0
  403. package/src/project/instance-runtime.ts +16 -0
  404. package/src/project/instance-store.ts +207 -0
  405. package/src/project/project.ts +520 -0
  406. package/src/project/vcs.ts +435 -0
  407. package/src/provider/auth.ts +230 -0
  408. package/src/provider/error.ts +188 -0
  409. package/src/provider/model-status.ts +8 -0
  410. package/src/provider/provider.ts +2009 -0
  411. package/src/provider/transform.ts +1363 -0
  412. package/src/pty-preparation.ts +30 -0
  413. package/src/question/index.ts +229 -0
  414. package/src/question/schema.ts +10 -0
  415. package/src/reference/reference.ts +239 -0
  416. package/src/reference/repository-cache.ts +320 -0
  417. package/src/server/auth.ts +48 -0
  418. package/src/server/cors.ts +34 -0
  419. package/src/server/event.ts +13 -0
  420. package/src/server/global-lifecycle.ts +37 -0
  421. package/src/server/init-projectors.ts +3 -0
  422. package/src/server/mdns.ts +60 -0
  423. package/src/server/projectors.ts +1 -0
  424. package/src/server/proxy-util.ts +48 -0
  425. package/src/server/routes/instance/httpapi/AGENTS.md +39 -0
  426. package/src/server/routes/instance/httpapi/api.ts +78 -0
  427. package/src/server/routes/instance/httpapi/errors.ts +193 -0
  428. package/src/server/routes/instance/httpapi/groups/config.ts +65 -0
  429. package/src/server/routes/instance/httpapi/groups/control-plane.ts +35 -0
  430. package/src/server/routes/instance/httpapi/groups/control.ts +76 -0
  431. package/src/server/routes/instance/httpapi/groups/event.ts +29 -0
  432. package/src/server/routes/instance/httpapi/groups/experimental.ts +260 -0
  433. package/src/server/routes/instance/httpapi/groups/file.ts +172 -0
  434. package/src/server/routes/instance/httpapi/groups/global.ts +138 -0
  435. package/src/server/routes/instance/httpapi/groups/instance.ts +206 -0
  436. package/src/server/routes/instance/httpapi/groups/mcp.ts +156 -0
  437. package/src/server/routes/instance/httpapi/groups/metadata.ts +18 -0
  438. package/src/server/routes/instance/httpapi/groups/permission.ts +61 -0
  439. package/src/server/routes/instance/httpapi/groups/project-copy.ts +88 -0
  440. package/src/server/routes/instance/httpapi/groups/project.ts +93 -0
  441. package/src/server/routes/instance/httpapi/groups/provider.ts +101 -0
  442. package/src/server/routes/instance/httpapi/groups/pty.ts +172 -0
  443. package/src/server/routes/instance/httpapi/groups/query.ts +12 -0
  444. package/src/server/routes/instance/httpapi/groups/question.ts +74 -0
  445. package/src/server/routes/instance/httpapi/groups/session.ts +462 -0
  446. package/src/server/routes/instance/httpapi/groups/sync.ts +113 -0
  447. package/src/server/routes/instance/httpapi/groups/tui.ts +208 -0
  448. package/src/server/routes/instance/httpapi/groups/workspace.ts +141 -0
  449. package/src/server/routes/instance/httpapi/handlers/config.ts +151 -0
  450. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +37 -0
  451. package/src/server/routes/instance/httpapi/handlers/control.ts +40 -0
  452. package/src/server/routes/instance/httpapi/handlers/event.ts +102 -0
  453. package/src/server/routes/instance/httpapi/handlers/experimental.ts +187 -0
  454. package/src/server/routes/instance/httpapi/handlers/file.ts +103 -0
  455. package/src/server/routes/instance/httpapi/handlers/global.ts +157 -0
  456. package/src/server/routes/instance/httpapi/handlers/instance.ts +110 -0
  457. package/src/server/routes/instance/httpapi/handlers/mcp.ts +111 -0
  458. package/src/server/routes/instance/httpapi/handlers/permission.ts +41 -0
  459. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +157 -0
  460. package/src/server/routes/instance/httpapi/handlers/project.ts +63 -0
  461. package/src/server/routes/instance/httpapi/handlers/provider.ts +160 -0
  462. package/src/server/routes/instance/httpapi/handlers/pty.ts +258 -0
  463. package/src/server/routes/instance/httpapi/handlers/question.ts +54 -0
  464. package/src/server/routes/instance/httpapi/handlers/session-errors.ts +21 -0
  465. package/src/server/routes/instance/httpapi/handlers/session.ts +442 -0
  466. package/src/server/routes/instance/httpapi/handlers/sync.ts +95 -0
  467. package/src/server/routes/instance/httpapi/handlers/tui.ts +131 -0
  468. package/src/server/routes/instance/httpapi/handlers/workspace.ts +102 -0
  469. package/src/server/routes/instance/httpapi/lifecycle.ts +57 -0
  470. package/src/server/routes/instance/httpapi/middleware/authorization.ts +147 -0
  471. package/src/server/routes/instance/httpapi/middleware/compression.ts +64 -0
  472. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +29 -0
  473. package/src/server/routes/instance/httpapi/middleware/error.ts +36 -0
  474. package/src/server/routes/instance/httpapi/middleware/fence.ts +25 -0
  475. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +43 -0
  476. package/src/server/routes/instance/httpapi/middleware/proxy.ts +108 -0
  477. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +42 -0
  478. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +250 -0
  479. package/src/server/routes/instance/httpapi/public.ts +535 -0
  480. package/src/server/routes/instance/httpapi/server.ts +277 -0
  481. package/src/server/routes/instance/httpapi/websocket-tracker.ts +57 -0
  482. package/src/server/server.ts +218 -0
  483. package/src/server/shared/fence.ts +68 -0
  484. package/src/server/shared/pty-ticket.ts +15 -0
  485. package/src/server/shared/public-ui.ts +12 -0
  486. package/src/server/shared/tui-control.ts +28 -0
  487. package/src/server/shared/ui.ts +108 -0
  488. package/src/server/shared/workspace-routing.ts +38 -0
  489. package/src/session/compaction.ts +609 -0
  490. package/src/session/instruction.ts +237 -0
  491. package/src/session/llm/AGENTS.md +90 -0
  492. package/src/session/llm/ai-sdk.ts +288 -0
  493. package/src/session/llm/native-request.ts +196 -0
  494. package/src/session/llm/native-runtime.ts +195 -0
  495. package/src/session/llm/request.ts +215 -0
  496. package/src/session/llm.ts +402 -0
  497. package/src/session/message-error.ts +14 -0
  498. package/src/session/message-v2.ts +745 -0
  499. package/src/session/message.ts +148 -0
  500. package/src/session/overflow.ts +34 -0
  501. package/src/session/processor.ts +1063 -0
  502. package/src/session/prompt/anthropic.txt +105 -0
  503. package/src/session/prompt/beast.txt +147 -0
  504. package/src/session/prompt/build-switch.txt +5 -0
  505. package/src/session/prompt/codex.txt +79 -0
  506. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  507. package/src/session/prompt/default.txt +95 -0
  508. package/src/session/prompt/gemini.txt +155 -0
  509. package/src/session/prompt/gpt.txt +107 -0
  510. package/src/session/prompt/kimi.txt +95 -0
  511. package/src/session/prompt/max-steps.txt +16 -0
  512. package/src/session/prompt/plan-mode.txt +70 -0
  513. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  514. package/src/session/prompt/plan.txt +26 -0
  515. package/src/session/prompt/reference.ts +72 -0
  516. package/src/session/prompt/trinity.txt +97 -0
  517. package/src/session/prompt.ts +1755 -0
  518. package/src/session/reminders.ts +92 -0
  519. package/src/session/retry.ts +201 -0
  520. package/src/session/revert.ts +153 -0
  521. package/src/session/run-state.ts +153 -0
  522. package/src/session/schema.ts +26 -0
  523. package/src/session/session.ts +1116 -0
  524. package/src/session/status.ts +94 -0
  525. package/src/session/summary.ts +162 -0
  526. package/src/session/system.ts +84 -0
  527. package/src/session/todo.ts +87 -0
  528. package/src/session/tools.ts +211 -0
  529. package/src/share/session.ts +58 -0
  530. package/src/share/share-next.ts +379 -0
  531. package/src/shell/shell.ts +215 -0
  532. package/src/skill/discovery.ts +115 -0
  533. package/src/skill/index.ts +357 -0
  534. package/src/snapshot/index.ts +759 -0
  535. package/src/sql.d.ts +4 -0
  536. package/src/storage/schema.ts +5 -0
  537. package/src/storage/storage.ts +329 -0
  538. package/src/sync/README.md +179 -0
  539. package/src/sync/schema.ts +11 -0
  540. package/src/temporary.ts +33 -0
  541. package/src/tool/apply_patch.ts +313 -0
  542. package/src/tool/apply_patch.txt +33 -0
  543. package/src/tool/edit.ts +737 -0
  544. package/src/tool/edit.txt +10 -0
  545. package/src/tool/external-directory.ts +49 -0
  546. package/src/tool/glob.ts +84 -0
  547. package/src/tool/glob.txt +6 -0
  548. package/src/tool/grep.ts +140 -0
  549. package/src/tool/grep.txt +8 -0
  550. package/src/tool/invalid.ts +21 -0
  551. package/src/tool/json-schema.ts +164 -0
  552. package/src/tool/lsp.ts +113 -0
  553. package/src/tool/lsp.txt +24 -0
  554. package/src/tool/mcp-websearch.ts +96 -0
  555. package/src/tool/plan-enter.txt +14 -0
  556. package/src/tool/plan-exit.txt +13 -0
  557. package/src/tool/plan.ts +79 -0
  558. package/src/tool/question.ts +44 -0
  559. package/src/tool/question.txt +10 -0
  560. package/src/tool/read.ts +392 -0
  561. package/src/tool/read.txt +14 -0
  562. package/src/tool/registry.ts +475 -0
  563. package/src/tool/reply.ts +29 -0
  564. package/src/tool/schema.ts +14 -0
  565. package/src/tool/shell/id.ts +19 -0
  566. package/src/tool/shell/prompt.ts +307 -0
  567. package/src/tool/shell/shell.txt +21 -0
  568. package/src/tool/shell.ts +660 -0
  569. package/src/tool/skill.ts +72 -0
  570. package/src/tool/skill.txt +5 -0
  571. package/src/tool/task.ts +338 -0
  572. package/src/tool/task.txt +19 -0
  573. package/src/tool/todo.ts +57 -0
  574. package/src/tool/todowrite.txt +44 -0
  575. package/src/tool/tool.ts +183 -0
  576. package/src/tool/truncate.ts +160 -0
  577. package/src/tool/truncation-dir.ts +4 -0
  578. package/src/tool/webfetch.ts +192 -0
  579. package/src/tool/webfetch.txt +13 -0
  580. package/src/tool/websearch.ts +143 -0
  581. package/src/tool/websearch.txt +14 -0
  582. package/src/tool/write.ts +104 -0
  583. package/src/tool/write.txt +8 -0
  584. package/src/util/archive.ts +17 -0
  585. package/src/util/bom.ts +27 -0
  586. package/src/util/data-url.ts +9 -0
  587. package/src/util/defer.ts +10 -0
  588. package/src/util/effect-http-client.ts +11 -0
  589. package/src/util/error.ts +88 -0
  590. package/src/util/filesystem.ts +251 -0
  591. package/src/util/format.ts +20 -0
  592. package/src/util/iife.ts +3 -0
  593. package/src/util/lan-scan.ts +90 -0
  594. package/src/util/lazy.ts +20 -0
  595. package/src/util/local-context.ts +25 -0
  596. package/src/util/locale.ts +86 -0
  597. package/src/util/media.ts +26 -0
  598. package/src/util/process.ts +176 -0
  599. package/src/util/proxy-env.ts +72 -0
  600. package/src/util/queue.ts +32 -0
  601. package/src/util/record.ts +3 -0
  602. package/src/util/repository.ts +232 -0
  603. package/src/util/rpc.ts +66 -0
  604. package/src/util/signal.ts +12 -0
  605. package/src/util/timeout.ts +13 -0
  606. package/src/util/token.ts +1 -0
  607. package/src/util/wildcard.ts +59 -0
  608. package/src/worktree/index.ts +645 -0
  609. package/sst-env.d.ts +10 -0
  610. package/test/AGENTS.md +204 -0
  611. package/test/EFFECT_TEST_MIGRATION.md +169 -0
  612. package/test/account/repo.test.ts +353 -0
  613. package/test/account/service.test.ts +453 -0
  614. package/test/acp/config-option.test.ts +229 -0
  615. package/test/acp/content.test.ts +201 -0
  616. package/test/acp/directory.test.ts +186 -0
  617. package/test/acp/error.test.ts +67 -0
  618. package/test/acp/event.test.ts +711 -0
  619. package/test/acp/permission.test.ts +273 -0
  620. package/test/acp/service-session.test.ts +1174 -0
  621. package/test/acp/session.test.ts +200 -0
  622. package/test/acp/tool.test.ts +210 -0
  623. package/test/acp/usage.test.ts +315 -0
  624. package/test/agent/agent.test.ts +710 -0
  625. package/test/agent/plan-mode-subagent-bypass.test.ts +213 -0
  626. package/test/agent/plugin-agent-regression.test.ts +62 -0
  627. package/test/auth/auth.test.ts +77 -0
  628. package/test/background/job.test.ts +243 -0
  629. package/test/cli/account.test.ts +30 -0
  630. package/test/cli/acp/acp-test-client.ts +97 -0
  631. package/test/cli/acp/config-options.test.ts +103 -0
  632. package/test/cli/acp/helpers.ts +96 -0
  633. package/test/cli/acp/initialize-auth.test.ts +61 -0
  634. package/test/cli/acp/lifecycle.test.ts +118 -0
  635. package/test/cli/acp/prompt-content.test.ts +97 -0
  636. package/test/cli/acp/skills.test.ts +38 -0
  637. package/test/cli/cmd/tui/aggregate-failures.test.ts +93 -0
  638. package/test/cli/cmd/tui/attention.test.ts +484 -0
  639. package/test/cli/cmd/tui/dialog-workspace-create.test.ts +28 -0
  640. package/test/cli/cmd/tui/model-options.test.ts +30 -0
  641. package/test/cli/cmd/tui/notifications.test.ts +267 -0
  642. package/test/cli/cmd/tui/prompt-history.test.ts +44 -0
  643. package/test/cli/cmd/tui/prompt-part.test.ts +77 -0
  644. package/test/cli/cmd/tui/prompt-traits.test.ts +29 -0
  645. package/test/cli/cmd/tui/provider-options.test.ts +29 -0
  646. package/test/cli/cmd/tui/sync-fixture.tsx +64 -0
  647. package/test/cli/cmd/tui/sync-live-hydration.test.tsx +278 -0
  648. package/test/cli/cmd/tui/sync-undefined-messages.test.tsx +47 -0
  649. package/test/cli/cmd/tui/sync.test.tsx +70 -0
  650. package/test/cli/effect-cmd-instance-als.test.ts +39 -0
  651. package/test/cli/error.test.ts +95 -0
  652. package/test/cli/github-action.test.ts +199 -0
  653. package/test/cli/github-remote.test.ts +90 -0
  654. package/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +631 -0
  655. package/test/cli/help/help-snapshots.test.ts +137 -0
  656. package/test/cli/import.test.ts +54 -0
  657. package/test/cli/mcp-add.test.ts +74 -0
  658. package/test/cli/plugin-auth-picker.test.ts +120 -0
  659. package/test/cli/run/entry.body.test.ts +536 -0
  660. package/test/cli/run/footer.menu.test.ts +43 -0
  661. package/test/cli/run/footer.view.test.tsx +927 -0
  662. package/test/cli/run/permission.shared.test.ts +144 -0
  663. package/test/cli/run/prompt.shared.test.ts +133 -0
  664. package/test/cli/run/question.shared.test.ts +115 -0
  665. package/test/cli/run/run-process.test.ts +84 -0
  666. package/test/cli/run/runtime.boot.test.ts +282 -0
  667. package/test/cli/run/runtime.queue.test.ts +465 -0
  668. package/test/cli/run/runtime.stdin.test.ts +71 -0
  669. package/test/cli/run/scrollback.surface.test.ts +1048 -0
  670. package/test/cli/run/session-data.test.ts +595 -0
  671. package/test/cli/run/session-replay.test.ts +456 -0
  672. package/test/cli/run/session.shared.test.ts +247 -0
  673. package/test/cli/run/stream.test.ts +56 -0
  674. package/test/cli/run/stream.transport.test.ts +2363 -0
  675. package/test/cli/run/subagent-data.test.ts +456 -0
  676. package/test/cli/run/theme.test.ts +152 -0
  677. package/test/cli/run/variant.shared.test.ts +217 -0
  678. package/test/cli/serve/serve-process.test.ts +61 -0
  679. package/test/cli/smokes/read-only.test.ts +115 -0
  680. package/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap +72 -0
  681. package/test/cli/tui/app-lifecycle.test.ts +261 -0
  682. package/test/cli/tui/dialog-prompt.test.tsx +146 -0
  683. package/test/cli/tui/diff-viewer-file-tree-utils.test.ts +323 -0
  684. package/test/cli/tui/diff-viewer-file-tree.test.tsx +197 -0
  685. package/test/cli/tui/diff-viewer.test.tsx +230 -0
  686. package/test/cli/tui/editor-context-zed.test.ts +384 -0
  687. package/test/cli/tui/editor-context.test.tsx +288 -0
  688. package/test/cli/tui/inline-tool-wrap-snapshot.test.tsx +232 -0
  689. package/test/cli/tui/keymap.test.tsx +136 -0
  690. package/test/cli/tui/plugin-add.test.ts +110 -0
  691. package/test/cli/tui/plugin-install.test.ts +87 -0
  692. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  693. package/test/cli/tui/plugin-loader-entrypoint.test.ts +485 -0
  694. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  695. package/test/cli/tui/plugin-loader.test.ts +1332 -0
  696. package/test/cli/tui/plugin-toggle.test.ts +264 -0
  697. package/test/cli/tui/prompt-submit-race.test.ts +98 -0
  698. package/test/cli/tui/revert-diff.test.ts +35 -0
  699. package/test/cli/tui/slot-replace.test.tsx +50 -0
  700. package/test/cli/tui/sync-v2.test.tsx +558 -0
  701. package/test/cli/tui/theme-store.test.ts +76 -0
  702. package/test/cli/tui/thinking.test.ts +36 -0
  703. package/test/cli/tui/thread.test.ts +28 -0
  704. package/test/cli/tui/transcript.test.ts +426 -0
  705. package/test/cli/tui/use-event.test.tsx +145 -0
  706. package/test/config/agent-color.test.ts +47 -0
  707. package/test/config/config.test.ts +1991 -0
  708. package/test/config/entry-name.test.ts +57 -0
  709. package/test/config/fixtures/empty-frontmatter.md +4 -0
  710. package/test/config/fixtures/frontmatter.md +28 -0
  711. package/test/config/fixtures/markdown-header.md +11 -0
  712. package/test/config/fixtures/no-frontmatter.md +1 -0
  713. package/test/config/fixtures/weird-model-id.md +13 -0
  714. package/test/config/lsp.test.ts +69 -0
  715. package/test/config/markdown.test.ts +228 -0
  716. package/test/config/plugin.test.ts +0 -0
  717. package/test/config/tui.test.ts +878 -0
  718. package/test/control-plane/adapters.test.ts +71 -0
  719. package/test/control-plane/workspace.test.ts +1704 -0
  720. package/test/effect/app-runtime-logger.test.ts +105 -0
  721. package/test/effect/config-service.test.ts +65 -0
  722. package/test/effect/instance-state.test.ts +391 -0
  723. package/test/effect/run-service.test.ts +89 -0
  724. package/test/effect/runner.test.ts +514 -0
  725. package/test/effect/runtime-flags.test.ts +373 -0
  726. package/test/fake/account.ts +9 -0
  727. package/test/fake/auth.ts +8 -0
  728. package/test/fake/npm.ts +8 -0
  729. package/test/fake/provider.ts +82 -0
  730. package/test/fake/skill.ts +8 -0
  731. package/test/filesystem/filesystem.test.ts +319 -0
  732. package/test/fixture/agent-plugin.constants.ts +6 -0
  733. package/test/fixture/agent-plugin.ts +12 -0
  734. package/test/fixture/config.ts +23 -0
  735. package/test/fixture/db.ts +11 -0
  736. package/test/fixture/fixture.test.ts +26 -0
  737. package/test/fixture/fixture.ts +224 -0
  738. package/test/fixture/flag.ts +20 -0
  739. package/test/fixture/flock-worker.ts +72 -0
  740. package/test/fixture/lsp/fake-lsp-server.js +249 -0
  741. package/test/fixture/plug-worker.ts +93 -0
  742. package/test/fixture/plugin-meta-worker.ts +19 -0
  743. package/test/fixture/plugin.ts +10 -0
  744. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  745. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  746. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  747. package/test/fixture/skills/index.json +6 -0
  748. package/test/fixture/tui-plugin.ts +355 -0
  749. package/test/fixture/tui-runtime.ts +64 -0
  750. package/test/fixture/tui-sdk.ts +82 -0
  751. package/test/fixture/workspace.ts +30 -0
  752. package/test/fixtures/recordings/session/native-anthropic-tool-loop.json +49 -0
  753. package/test/fixtures/recordings/session/native-openai-oauth-tool-loop.json +45 -0
  754. package/test/fixtures/recordings/session/native-zen-tool-loop.json +49 -0
  755. package/test/format/format.test.ts +228 -0
  756. package/test/git/git.test.ts +178 -0
  757. package/test/ide/ide.test.ts +82 -0
  758. package/test/image/fixtures/picture-5mb-base64.png +0 -0
  759. package/test/image/image.test.ts +123 -0
  760. package/test/installation/installation.test.ts +230 -0
  761. package/test/lib/cli-process.ts +459 -0
  762. package/test/lib/effect.ts +177 -0
  763. package/test/lib/filesystem.ts +10 -0
  764. package/test/lib/llm-server.ts +771 -0
  765. package/test/lib/snapshot.ts +73 -0
  766. package/test/lib/test-provider.ts +37 -0
  767. package/test/lib/websocket.ts +46 -0
  768. package/test/lsp/client.test.ts +493 -0
  769. package/test/lsp/index.test.ts +232 -0
  770. package/test/lsp/launch.test.ts +22 -0
  771. package/test/lsp/lifecycle.test.ts +160 -0
  772. package/test/mcp/auth.test.ts +78 -0
  773. package/test/mcp/headers.test.ts +126 -0
  774. package/test/mcp/lifecycle.test.ts +888 -0
  775. package/test/mcp/oauth-auto-connect.test.ts +236 -0
  776. package/test/mcp/oauth-browser.test.ts +228 -0
  777. package/test/mcp/oauth-callback.test.ts +34 -0
  778. package/test/mcp/oauth-provider.test.ts +61 -0
  779. package/test/patch/patch.test.ts +383 -0
  780. package/test/permission/arity.test.ts +33 -0
  781. package/test/permission/next.test.ts +1176 -0
  782. package/test/permission-task.test.ts +318 -0
  783. package/test/plugin/auth-override.test.ts +105 -0
  784. package/test/plugin/cloudflare.test.ts +68 -0
  785. package/test/plugin/codex.test.ts +247 -0
  786. package/test/plugin/github-copilot-models.test.ts +332 -0
  787. package/test/plugin/install-concurrency.test.ts +140 -0
  788. package/test/plugin/install.test.ts +570 -0
  789. package/test/plugin/loader-shared.test.ts +1303 -0
  790. package/test/plugin/meta.test.ts +137 -0
  791. package/test/plugin/openai-rollout.test.ts +17 -0
  792. package/test/plugin/openai-ws.test.ts +877 -0
  793. package/test/plugin/shared.test.ts +88 -0
  794. package/test/plugin/trigger.test.ts +120 -0
  795. package/test/plugin/workspace-adapter.test.ts +137 -0
  796. package/test/plugin/xai.test.ts +634 -0
  797. package/test/preload.ts +95 -0
  798. package/test/project/instance-bootstrap.test.ts +110 -0
  799. package/test/project/instance.test.ts +245 -0
  800. package/test/project/migrate-global.test.ts +170 -0
  801. package/test/project/project-directory.test.ts +169 -0
  802. package/test/project/project.test.ts +818 -0
  803. package/test/project/vcs.test.ts +336 -0
  804. package/test/project/worktree-remove.test.ts +126 -0
  805. package/test/project/worktree.test.ts +320 -0
  806. package/test/provider/amazon-bedrock.test.ts +360 -0
  807. package/test/provider/cf-ai-gateway-e2e.test.ts +132 -0
  808. package/test/provider/digitalocean.test.ts +123 -0
  809. package/test/provider/gitlab-duo.test.ts +412 -0
  810. package/test/provider/header-timeout.test.ts +233 -0
  811. package/test/provider/model-status.test.ts +61 -0
  812. package/test/provider/provider.test.ts +1793 -0
  813. package/test/provider/transform.test.ts +3937 -0
  814. package/test/pty/pty-shell.test.ts +102 -0
  815. package/test/question/question.test.ts +465 -0
  816. package/test/reference/reference.test.ts +310 -0
  817. package/test/server/AGENTS.md +15 -0
  818. package/test/server/auth.test.ts +59 -0
  819. package/test/server/global-bus.ts +31 -0
  820. package/test/server/global-session-list.test.ts +107 -0
  821. package/test/server/httpapi-authorization.test.ts +174 -0
  822. package/test/server/httpapi-compression.test.ts +154 -0
  823. package/test/server/httpapi-config.test.ts +113 -0
  824. package/test/server/httpapi-control-plane.test.ts +63 -0
  825. package/test/server/httpapi-cors-vary.test.ts +66 -0
  826. package/test/server/httpapi-cors.test.ts +122 -0
  827. package/test/server/httpapi-error-middleware.test.ts +96 -0
  828. package/test/server/httpapi-event.test.ts +97 -0
  829. package/test/server/httpapi-exercise/assertions.ts +64 -0
  830. package/test/server/httpapi-exercise/backend.ts +144 -0
  831. package/test/server/httpapi-exercise/dsl.ts +210 -0
  832. package/test/server/httpapi-exercise/environment.ts +40 -0
  833. package/test/server/httpapi-exercise/index.ts +1535 -0
  834. package/test/server/httpapi-exercise/report.ts +66 -0
  835. package/test/server/httpapi-exercise/routing.ts +96 -0
  836. package/test/server/httpapi-exercise/runner.ts +267 -0
  837. package/test/server/httpapi-exercise/runtime.ts +52 -0
  838. package/test/server/httpapi-exercise/types.ts +123 -0
  839. package/test/server/httpapi-experimental.test.ts +300 -0
  840. package/test/server/httpapi-file.test.ts +76 -0
  841. package/test/server/httpapi-global.test.ts +66 -0
  842. package/test/server/httpapi-instance-context.test.ts +347 -0
  843. package/test/server/httpapi-instance-route-auth.test.ts +84 -0
  844. package/test/server/httpapi-instance.test.ts +265 -0
  845. package/test/server/httpapi-layer.ts +33 -0
  846. package/test/server/httpapi-listen.test.ts +415 -0
  847. package/test/server/httpapi-mcp-oauth.test.ts +73 -0
  848. package/test/server/httpapi-mcp.test.ts +226 -0
  849. package/test/server/httpapi-mdns.test.ts +82 -0
  850. package/test/server/httpapi-promptasync-context.test.ts +222 -0
  851. package/test/server/httpapi-provider.test.ts +403 -0
  852. package/test/server/httpapi-pty.test.ts +275 -0
  853. package/test/server/httpapi-public-openapi.test.ts +297 -0
  854. package/test/server/httpapi-query-schema-drift.test.ts +330 -0
  855. package/test/server/httpapi-schema-error-body.test.ts +165 -0
  856. package/test/server/httpapi-sdk.test.ts +909 -0
  857. package/test/server/httpapi-session.test.ts +1013 -0
  858. package/test/server/httpapi-sync.test.ts +154 -0
  859. package/test/server/httpapi-ui.test.ts +456 -0
  860. package/test/server/httpapi-v2-location.test.ts +85 -0
  861. package/test/server/httpapi-workspace-routing.test.ts +554 -0
  862. package/test/server/httpapi-workspace.test.ts +515 -0
  863. package/test/server/negative-tokens-regression.test.ts +83 -0
  864. package/test/server/project-copy.test.ts +101 -0
  865. package/test/server/project-init-git.test.ts +117 -0
  866. package/test/server/proxy-util.test.ts +113 -0
  867. package/test/server/sdk-error-shape.test.ts +84 -0
  868. package/test/server/sdk-v1-smoke.test.ts +60 -0
  869. package/test/server/session-actions.test.ts +112 -0
  870. package/test/server/session-diff-missing-patch.test.ts +99 -0
  871. package/test/server/session-list.test.ts +314 -0
  872. package/test/server/session-messages.test.ts +182 -0
  873. package/test/server/session-select.test.ts +69 -0
  874. package/test/server/workspace-proxy.test.ts +181 -0
  875. package/test/server/workspace-routing.test.ts +94 -0
  876. package/test/server/worktree-endpoint-repro.test.ts +307 -0
  877. package/test/session/compaction.test.ts +1835 -0
  878. package/test/session/instruction.test.ts +256 -0
  879. package/test/session/llm-native-recorded.test.ts +433 -0
  880. package/test/session/llm-native.test.ts +760 -0
  881. package/test/session/llm.test.ts +1932 -0
  882. package/test/session/message-v2.test.ts +1661 -0
  883. package/test/session/messages-pagination.test.ts +1059 -0
  884. package/test/session/processor-effect.test.ts +1101 -0
  885. package/test/session/prompt.test.ts +2318 -0
  886. package/test/session/retry.test.ts +439 -0
  887. package/test/session/revert-compact.test.ts +642 -0
  888. package/test/session/schema-decoding.test.ts +313 -0
  889. package/test/session/session-schema.test.ts +78 -0
  890. package/test/session/session.test.ts +251 -0
  891. package/test/session/snapshot-tool-race.test.ts +280 -0
  892. package/test/session/structured-output-integration.test.ts +235 -0
  893. package/test/session/structured-output.test.ts +387 -0
  894. package/test/session/system.test.ts +84 -0
  895. package/test/share/share-next.test.ts +344 -0
  896. package/test/shell/shell.test.ts +99 -0
  897. package/test/skill/discovery.test.ts +139 -0
  898. package/test/skill/skill.test.ts +571 -0
  899. package/test/snapshot/snapshot.test.ts +1121 -0
  900. package/test/storage/storage.test.ts +296 -0
  901. package/test/storage/workspace-time-migration.test.ts +50 -0
  902. package/test/tool/__snapshots__/parameters.test.ts.snap +484 -0
  903. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  904. package/test/tool/apply_patch.test.ts +533 -0
  905. package/test/tool/edit.test.ts +578 -0
  906. package/test/tool/external-directory.test.ts +155 -0
  907. package/test/tool/fixtures/large-image.png +0 -0
  908. package/test/tool/fixtures/models-api.json +117299 -0
  909. package/test/tool/glob.test.ts +188 -0
  910. package/test/tool/grep.test.ts +266 -0
  911. package/test/tool/lsp.test.ts +181 -0
  912. package/test/tool/parameters.test.ts +293 -0
  913. package/test/tool/question.test.ts +138 -0
  914. package/test/tool/read.test.ts +654 -0
  915. package/test/tool/registry.test.ts +539 -0
  916. package/test/tool/shell.test.ts +1238 -0
  917. package/test/tool/skill.test.ts +132 -0
  918. package/test/tool/task.test.ts +901 -0
  919. package/test/tool/tool-define.test.ts +153 -0
  920. package/test/tool/truncation.test.ts +266 -0
  921. package/test/tool/webfetch.test.ts +113 -0
  922. package/test/tool/websearch.test.ts +99 -0
  923. package/test/tool/write.test.ts +276 -0
  924. package/test/util/data-url.test.ts +14 -0
  925. package/test/util/error.test.ts +64 -0
  926. package/test/util/filesystem.test.ts +656 -0
  927. package/test/util/format.test.ts +59 -0
  928. package/test/util/glob.test.ts +164 -0
  929. package/test/util/iife.test.ts +36 -0
  930. package/test/util/lazy.test.ts +50 -0
  931. package/test/util/log.test.ts +77 -0
  932. package/test/util/module.test.ts +59 -0
  933. package/test/util/process.test.ts +128 -0
  934. package/test/util/repository.test.ts +93 -0
  935. package/test/util/timeout.test.ts +21 -0
  936. package/test/util/wildcard.test.ts +90 -0
  937. package/test/v2/session-message-updater.test.ts +270 -0
  938. package/tsconfig.json +17 -0
@@ -0,0 +1,3937 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { Effect } from "effect"
3
+ import { ProviderTransform } from "@/provider/transform"
4
+ import { LLMRequestPrep } from "@/session/llm/request"
5
+ import { ProviderV2 } from "@opencode-ai/core/provider"
6
+ import { ModelV2 } from "@opencode-ai/core/model"
7
+
8
+ describe("ProviderTransform.options - setCacheKey", () => {
9
+ const sessionID = "test-session-123"
10
+
11
+ const mockModel = {
12
+ id: "anthropic/claude-3-5-sonnet",
13
+ providerID: "anthropic",
14
+ api: {
15
+ id: "claude-3-5-sonnet-20241022",
16
+ url: "https://api.anthropic.com",
17
+ npm: "@ai-sdk/anthropic",
18
+ },
19
+ name: "Claude 3.5 Sonnet",
20
+ capabilities: {
21
+ temperature: true,
22
+ reasoning: false,
23
+ attachment: true,
24
+ toolcall: true,
25
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
26
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
27
+ interleaved: false,
28
+ },
29
+ cost: {
30
+ input: 0.003,
31
+ output: 0.015,
32
+ cache: { read: 0.0003, write: 0.00375 },
33
+ },
34
+ limit: {
35
+ context: 200000,
36
+ output: 8192,
37
+ },
38
+ status: "active",
39
+ options: {},
40
+ headers: {},
41
+ } as any
42
+
43
+ test("should set promptCacheKey when providerOptions.setCacheKey is true", () => {
44
+ const result = ProviderTransform.options({
45
+ model: mockModel,
46
+ sessionID,
47
+ providerOptions: { setCacheKey: true },
48
+ })
49
+ expect(result.promptCacheKey).toBe(sessionID)
50
+ })
51
+
52
+ test("should not set promptCacheKey when providerOptions.setCacheKey is false", () => {
53
+ const result = ProviderTransform.options({
54
+ model: mockModel,
55
+ sessionID,
56
+ providerOptions: { setCacheKey: false },
57
+ })
58
+ expect(result.promptCacheKey).toBeUndefined()
59
+ })
60
+
61
+ test("should not set promptCacheKey when providerOptions is undefined", () => {
62
+ const result = ProviderTransform.options({
63
+ model: mockModel,
64
+ sessionID,
65
+ providerOptions: undefined,
66
+ })
67
+ expect(result.promptCacheKey).toBeUndefined()
68
+ })
69
+
70
+ test("should not set promptCacheKey when providerOptions does not have setCacheKey", () => {
71
+ const result = ProviderTransform.options({ model: mockModel, sessionID, providerOptions: {} })
72
+ expect(result.promptCacheKey).toBeUndefined()
73
+ })
74
+
75
+ test("should set promptCacheKey for openai provider regardless of setCacheKey", () => {
76
+ const openaiModel = {
77
+ ...mockModel,
78
+ providerID: "openai",
79
+ api: {
80
+ id: "gpt-4",
81
+ url: "https://api.openai.com",
82
+ npm: "@ai-sdk/openai",
83
+ },
84
+ }
85
+ const result = ProviderTransform.options({ model: openaiModel, sessionID, providerOptions: {} })
86
+ expect(result.promptCacheKey).toBe(sessionID)
87
+ })
88
+
89
+ test("should set store=false for openai provider", () => {
90
+ const openaiModel = {
91
+ ...mockModel,
92
+ providerID: "openai",
93
+ api: {
94
+ id: "gpt-4",
95
+ url: "https://api.openai.com",
96
+ npm: "@ai-sdk/openai",
97
+ },
98
+ }
99
+ const result = ProviderTransform.options({
100
+ model: openaiModel,
101
+ sessionID,
102
+ providerOptions: {},
103
+ })
104
+ expect(result.store).toBe(false)
105
+ })
106
+
107
+ test("should set store=false for azure provider by default", () => {
108
+ const azureModel = {
109
+ ...mockModel,
110
+ providerID: "azure",
111
+ api: {
112
+ id: "gpt-4",
113
+ url: "https://azure.com",
114
+ npm: "@ai-sdk/azure",
115
+ },
116
+ }
117
+ const result = ProviderTransform.options({
118
+ model: azureModel,
119
+ sessionID,
120
+ providerOptions: {},
121
+ })
122
+ expect(result.store).toBe(false)
123
+ })
124
+ })
125
+
126
+ describe("ProviderTransform.options - zai/zhipuai thinking", () => {
127
+ const sessionID = "test-session-123"
128
+
129
+ const createModel = (providerID: string) =>
130
+ ({
131
+ id: `${providerID}/glm-4.6`,
132
+ providerID,
133
+ api: {
134
+ id: "glm-4.6",
135
+ url: "https://open.bigmodel.cn/api/paas/v4",
136
+ npm: "@ai-sdk/openai-compatible",
137
+ },
138
+ name: "GLM 4.6",
139
+ capabilities: {
140
+ temperature: true,
141
+ reasoning: true,
142
+ attachment: true,
143
+ toolcall: true,
144
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
145
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
146
+ interleaved: false,
147
+ },
148
+ cost: {
149
+ input: 0.001,
150
+ output: 0.002,
151
+ cache: { read: 0.0001, write: 0.0002 },
152
+ },
153
+ limit: {
154
+ context: 128000,
155
+ output: 8192,
156
+ },
157
+ status: "active",
158
+ options: {},
159
+ headers: {},
160
+ }) as any
161
+
162
+ for (const providerID of ["zai-coding-plan", "zai", "zhipuai-coding-plan", "zhipuai"]) {
163
+ test(`${providerID} should set thinking cfg`, () => {
164
+ const result = ProviderTransform.options({
165
+ model: createModel(providerID),
166
+ sessionID,
167
+ providerOptions: {},
168
+ })
169
+
170
+ expect(result.thinking).toEqual({
171
+ type: "enabled",
172
+ clear_thinking: false,
173
+ })
174
+ })
175
+ }
176
+ })
177
+
178
+ describe("ProviderTransform.options - google thinkingConfig gating", () => {
179
+ const sessionID = "test-session-123"
180
+
181
+ const createGoogleModel = (reasoning: boolean, npm: "@ai-sdk/google" | "@ai-sdk/google-vertex") =>
182
+ ({
183
+ id: `${npm === "@ai-sdk/google" ? "google" : "google-vertex"}/gemini-2.0-flash`,
184
+ providerID: npm === "@ai-sdk/google" ? "google" : "google-vertex",
185
+ api: {
186
+ id: "gemini-2.0-flash",
187
+ url: npm === "@ai-sdk/google" ? "https://generativelanguage.googleapis.com" : "https://vertexai.googleapis.com",
188
+ npm,
189
+ },
190
+ name: "Gemini 2.0 Flash",
191
+ capabilities: {
192
+ temperature: true,
193
+ reasoning,
194
+ attachment: true,
195
+ toolcall: true,
196
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
197
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
198
+ interleaved: false,
199
+ },
200
+ cost: {
201
+ input: 0.001,
202
+ output: 0.002,
203
+ cache: { read: 0.0001, write: 0.0002 },
204
+ },
205
+ limit: {
206
+ context: 1_000_000,
207
+ output: 8192,
208
+ },
209
+ status: "active",
210
+ options: {},
211
+ headers: {},
212
+ }) as any
213
+
214
+ test("does not set thinkingConfig for google models without reasoning capability", () => {
215
+ const result = ProviderTransform.options({
216
+ model: createGoogleModel(false, "@ai-sdk/google"),
217
+ sessionID,
218
+ providerOptions: {},
219
+ })
220
+ expect(result.thinkingConfig).toBeUndefined()
221
+ })
222
+
223
+ test("sets thinkingConfig for google models with reasoning capability", () => {
224
+ const result = ProviderTransform.options({
225
+ model: createGoogleModel(true, "@ai-sdk/google"),
226
+ sessionID,
227
+ providerOptions: {},
228
+ })
229
+ expect(result.thinkingConfig).toEqual({
230
+ includeThoughts: true,
231
+ })
232
+ })
233
+
234
+ test("does not set thinkingConfig for vertex models without reasoning capability", () => {
235
+ const result = ProviderTransform.options({
236
+ model: createGoogleModel(false, "@ai-sdk/google-vertex"),
237
+ sessionID,
238
+ providerOptions: {},
239
+ })
240
+ expect(result.thinkingConfig).toBeUndefined()
241
+ })
242
+ })
243
+
244
+ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
245
+ const sessionID = "test-session-123"
246
+
247
+ const createGpt5Model = (apiId: string) =>
248
+ ({
249
+ id: `openai/${apiId}`,
250
+ providerID: "openai",
251
+ api: {
252
+ id: apiId,
253
+ url: "https://api.openai.com",
254
+ npm: "@ai-sdk/openai",
255
+ },
256
+ name: apiId,
257
+ capabilities: {
258
+ temperature: true,
259
+ reasoning: true,
260
+ attachment: true,
261
+ toolcall: true,
262
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
263
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
264
+ interleaved: false,
265
+ },
266
+ cost: { input: 0.03, output: 0.06, cache: { read: 0.001, write: 0.002 } },
267
+ limit: { context: 128000, output: 4096 },
268
+ status: "active",
269
+ options: {},
270
+ headers: {},
271
+ }) as any
272
+
273
+ test("gpt-5.2 should have textVerbosity set to low", () => {
274
+ const model = createGpt5Model("gpt-5.2")
275
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
276
+ expect(result.textVerbosity).toBe("low")
277
+ expect(result.include).toEqual(["reasoning.encrypted_content"])
278
+ })
279
+
280
+ test("Bedrock Mantle gpt-5.5 uses OpenAI Responses defaults", () => {
281
+ const model = {
282
+ ...createGpt5Model("openai.gpt-5.5"),
283
+ id: "amazon-bedrock/openai.gpt-5.5",
284
+ providerID: "amazon-bedrock",
285
+ api: {
286
+ id: "openai.gpt-5.5",
287
+ url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
288
+ npm: "@ai-sdk/amazon-bedrock/mantle",
289
+ },
290
+ }
291
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
292
+ expect(result.store).toBe(false)
293
+ expect(result.reasoningEffort).toBe("medium")
294
+ expect(result.reasoningSummary).toBe("auto")
295
+ expect(result.include).toEqual(["reasoning.encrypted_content"])
296
+ expect(result.textVerbosity).toBe("low")
297
+ })
298
+
299
+ test("openai-compatible gpt-5 models omit Responses-only reasoningSummary", () => {
300
+ const model = {
301
+ ...createGpt5Model("gpt-5.4"),
302
+ id: "cortecs/gpt-5.4",
303
+ providerID: "cortecs",
304
+ api: {
305
+ id: "gpt-5.4",
306
+ url: "https://api.cortecs.ai/v1",
307
+ npm: "@ai-sdk/openai-compatible",
308
+ },
309
+ }
310
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
311
+ expect(result.reasoningEffort).toBe("medium")
312
+ expect(result.reasoningSummary).toBeUndefined()
313
+ expect(result.include).toBeUndefined()
314
+ })
315
+
316
+ test("azure chat completions omit Responses-only reasoning options after variants merge", async () => {
317
+ const model = {
318
+ ...createGpt5Model("gpt-5.4"),
319
+ id: "azure/gpt-5.4",
320
+ providerID: "azure",
321
+ api: {
322
+ id: "gpt-5.4",
323
+ url: "https://azure.com",
324
+ npm: "@ai-sdk/azure",
325
+ },
326
+ variants: {
327
+ high: {
328
+ reasoningEffort: "high",
329
+ reasoningSummary: "auto",
330
+ include: ["reasoning.encrypted_content"],
331
+ },
332
+ },
333
+ }
334
+ const result = await Effect.runPromise(
335
+ LLMRequestPrep.prepare({
336
+ user: {
337
+ id: "msg_user-test",
338
+ sessionID,
339
+ role: "user",
340
+ time: { created: Date.now() },
341
+ agent: "test",
342
+ model: { providerID: "azure", modelID: "gpt-5.4", variant: "high" },
343
+ } as any,
344
+ sessionID,
345
+ model,
346
+ agent: {
347
+ name: "test",
348
+ mode: "primary",
349
+ options: {},
350
+ permission: [],
351
+ } as any,
352
+ system: [],
353
+ messages: [{ role: "user", content: "Hello" }],
354
+ tools: {},
355
+ provider: { id: "azure", options: { useCompletionUrls: true } } as any,
356
+ auth: undefined,
357
+ plugin: {
358
+ trigger: (_name: string, _input: unknown, output: unknown) => Effect.succeed(output),
359
+ list: () => Effect.succeed([]),
360
+ init: () => Effect.void,
361
+ } as any,
362
+ flags: { outputTokenMax: 32_000, client: "test" } as any,
363
+ isWorkflow: false,
364
+ }),
365
+ )
366
+ expect(result.params.options.reasoningEffort).toBe("high")
367
+ expect(result.params.options.reasoningSummary).toBeUndefined()
368
+ expect(result.params.options.include).toBeUndefined()
369
+ })
370
+
371
+ test("gpt-5.1 should have textVerbosity set to low", () => {
372
+ const model = createGpt5Model("gpt-5.1")
373
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
374
+ expect(result.textVerbosity).toBe("low")
375
+ })
376
+
377
+ test("gpt-5.2-chat-latest should NOT have textVerbosity set (only supports medium)", () => {
378
+ const model = createGpt5Model("gpt-5.2-chat-latest")
379
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
380
+ expect(result.textVerbosity).toBeUndefined()
381
+ })
382
+
383
+ test("gpt-5.1-chat-latest should NOT have textVerbosity set (only supports medium)", () => {
384
+ const model = createGpt5Model("gpt-5.1-chat-latest")
385
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
386
+ expect(result.textVerbosity).toBeUndefined()
387
+ })
388
+
389
+ test("gpt-5.2-chat should NOT have textVerbosity set", () => {
390
+ const model = createGpt5Model("gpt-5.2-chat")
391
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
392
+ expect(result.textVerbosity).toBeUndefined()
393
+ })
394
+
395
+ test("gpt-5-chat should NOT have textVerbosity set", () => {
396
+ const model = createGpt5Model("gpt-5-chat")
397
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
398
+ expect(result.textVerbosity).toBeUndefined()
399
+ })
400
+
401
+ test("gpt-5.2-codex should NOT have textVerbosity set (codex models excluded)", () => {
402
+ const model = createGpt5Model("gpt-5.2-codex")
403
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
404
+ expect(result.textVerbosity).toBeUndefined()
405
+ })
406
+ })
407
+
408
+ describe("ProviderTransform.options - gpt-5 reasoningEffort", () => {
409
+ const sessionID = "test-session-123"
410
+
411
+ const createModel = (apiId: string) =>
412
+ ({
413
+ id: `azure/${apiId}`,
414
+ providerID: "azure",
415
+ api: {
416
+ id: apiId,
417
+ url: "https://azure.com",
418
+ npm: "@ai-sdk/azure",
419
+ },
420
+ name: apiId,
421
+ capabilities: {
422
+ temperature: true,
423
+ reasoning: true,
424
+ attachment: true,
425
+ toolcall: true,
426
+ input: {
427
+ text: true,
428
+ audio: false,
429
+ image: true,
430
+ video: false,
431
+ pdf: false,
432
+ },
433
+ output: {
434
+ text: true,
435
+ audio: false,
436
+ image: false,
437
+ video: false,
438
+ pdf: false,
439
+ },
440
+ interleaved: false,
441
+ },
442
+ cost: {
443
+ input: 0.03,
444
+ output: 0.06,
445
+ cache: { read: 0.001, write: 0.002 },
446
+ },
447
+ limit: {
448
+ context: 128000,
449
+ output: 4096,
450
+ },
451
+ status: "active",
452
+ options: {},
453
+ headers: {},
454
+ }) as any
455
+
456
+ test("gpt-5-chat should NOT set reasoningEffort", () => {
457
+ const result = ProviderTransform.options({
458
+ model: createModel("gpt-5-chat"),
459
+ sessionID,
460
+ providerOptions: {},
461
+ })
462
+
463
+ expect(result.reasoningEffort).toBeUndefined()
464
+ })
465
+
466
+ test("gpt-5.5 should NOT set reasoningEffort", () => {
467
+ const result = ProviderTransform.options({
468
+ model: createModel("gpt-5.5"),
469
+ sessionID,
470
+ providerOptions: {},
471
+ })
472
+
473
+ expect(result.reasoningEffort).toBeUndefined()
474
+ })
475
+ })
476
+
477
+ describe("ProviderTransform.options - gateway", () => {
478
+ const sessionID = "test-session-123"
479
+
480
+ const createModel = (id: string) =>
481
+ ({
482
+ id,
483
+ providerID: "vercel",
484
+ api: {
485
+ id,
486
+ url: "https://ai-gateway.vercel.sh/v3/ai",
487
+ npm: "@ai-sdk/gateway",
488
+ },
489
+ name: id,
490
+ capabilities: {
491
+ temperature: true,
492
+ reasoning: true,
493
+ attachment: true,
494
+ toolcall: true,
495
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
496
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
497
+ interleaved: false,
498
+ },
499
+ cost: {
500
+ input: 0.001,
501
+ output: 0.002,
502
+ cache: { read: 0.0001, write: 0.0002 },
503
+ },
504
+ limit: {
505
+ context: 200_000,
506
+ output: 8192,
507
+ },
508
+ status: "active",
509
+ options: {},
510
+ headers: {},
511
+ release_date: "2024-01-01",
512
+ }) as any
513
+
514
+ test("puts gateway defaults under gateway key", () => {
515
+ const model = createModel("anthropic/claude-sonnet-4")
516
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
517
+ expect(result).toEqual({
518
+ gateway: {
519
+ caching: "auto",
520
+ },
521
+ })
522
+ })
523
+ })
524
+
525
+ describe("ProviderTransform.providerOptions", () => {
526
+ const createModel = (overrides: Partial<any> = {}) =>
527
+ ({
528
+ id: "test/test-model",
529
+ providerID: "test",
530
+ api: {
531
+ id: "test-model",
532
+ url: "https://api.test.com",
533
+ npm: "@ai-sdk/openai",
534
+ },
535
+ name: "Test Model",
536
+ capabilities: {
537
+ temperature: true,
538
+ reasoning: true,
539
+ attachment: true,
540
+ toolcall: true,
541
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
542
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
543
+ interleaved: false,
544
+ },
545
+ cost: {
546
+ input: 0.001,
547
+ output: 0.002,
548
+ cache: { read: 0.0001, write: 0.0002 },
549
+ },
550
+ limit: {
551
+ context: 200_000,
552
+ output: 64_000,
553
+ },
554
+ status: "active",
555
+ options: {},
556
+ headers: {},
557
+ release_date: "2024-01-01",
558
+ ...overrides,
559
+ }) as any
560
+
561
+ test("uses sdk key for non-gateway models", () => {
562
+ const model = createModel({
563
+ providerID: "my-bedrock",
564
+ api: {
565
+ id: "anthropic.claude-sonnet-4",
566
+ url: "https://bedrock.aws",
567
+ npm: "@ai-sdk/amazon-bedrock",
568
+ },
569
+ })
570
+
571
+ expect(ProviderTransform.providerOptions(model, { cachePoint: { type: "default" } })).toEqual({
572
+ bedrock: { cachePoint: { type: "default" } },
573
+ })
574
+ })
575
+
576
+ test("uses gateway model provider slug for gateway models", () => {
577
+ const model = createModel({
578
+ providerID: "vercel",
579
+ api: {
580
+ id: "anthropic/claude-sonnet-4",
581
+ url: "https://ai-gateway.vercel.sh/v3/ai",
582
+ npm: "@ai-sdk/gateway",
583
+ },
584
+ })
585
+
586
+ expect(ProviderTransform.providerOptions(model, { thinking: { type: "enabled", budgetTokens: 12_000 } })).toEqual({
587
+ anthropic: { thinking: { type: "enabled", budgetTokens: 12_000 } },
588
+ })
589
+ })
590
+
591
+ test("falls back to gateway key when gateway api id is unscoped", () => {
592
+ const model = createModel({
593
+ id: "anthropic/claude-sonnet-4",
594
+ providerID: "vercel",
595
+ api: {
596
+ id: "claude-sonnet-4",
597
+ url: "https://ai-gateway.vercel.sh/v3/ai",
598
+ npm: "@ai-sdk/gateway",
599
+ },
600
+ })
601
+
602
+ expect(ProviderTransform.providerOptions(model, { thinking: { type: "enabled", budgetTokens: 12_000 } })).toEqual({
603
+ gateway: { thinking: { type: "enabled", budgetTokens: 12_000 } },
604
+ })
605
+ })
606
+
607
+ test("splits gateway routing options from provider-specific options", () => {
608
+ const model = createModel({
609
+ providerID: "vercel",
610
+ api: {
611
+ id: "anthropic/claude-sonnet-4",
612
+ url: "https://ai-gateway.vercel.sh/v3/ai",
613
+ npm: "@ai-sdk/gateway",
614
+ },
615
+ })
616
+
617
+ expect(
618
+ ProviderTransform.providerOptions(model, {
619
+ gateway: { order: ["vertex", "anthropic"] },
620
+ thinking: { type: "enabled", budgetTokens: 12_000 },
621
+ }),
622
+ ).toEqual({
623
+ gateway: { order: ["vertex", "anthropic"] },
624
+ anthropic: { thinking: { type: "enabled", budgetTokens: 12_000 } },
625
+ } as any)
626
+ })
627
+
628
+ test("falls back to gateway key when model id has no provider slug", () => {
629
+ const model = createModel({
630
+ id: "claude-sonnet-4",
631
+ providerID: "vercel",
632
+ api: {
633
+ id: "claude-sonnet-4",
634
+ url: "https://ai-gateway.vercel.sh/v3/ai",
635
+ npm: "@ai-sdk/gateway",
636
+ },
637
+ })
638
+
639
+ expect(ProviderTransform.providerOptions(model, { reasoningEffort: "high" })).toEqual({
640
+ gateway: { reasoningEffort: "high" },
641
+ })
642
+ })
643
+
644
+ test("maps amazon slug to bedrock for provider options", () => {
645
+ const model = createModel({
646
+ providerID: "vercel",
647
+ api: {
648
+ id: "amazon/nova-2-lite",
649
+ url: "https://ai-gateway.vercel.sh/v3/ai",
650
+ npm: "@ai-sdk/gateway",
651
+ },
652
+ })
653
+
654
+ expect(ProviderTransform.providerOptions(model, { reasoningConfig: { type: "enabled" } })).toEqual({
655
+ bedrock: { reasoningConfig: { type: "enabled" } },
656
+ })
657
+ })
658
+
659
+ test("maps Bedrock Mantle provider options to OpenAI namespace", () => {
660
+ const model = createModel({
661
+ providerID: "amazon-bedrock",
662
+ api: {
663
+ id: "openai.gpt-5.5",
664
+ url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
665
+ npm: "@ai-sdk/amazon-bedrock/mantle",
666
+ },
667
+ })
668
+
669
+ expect(ProviderTransform.providerOptions(model, { reasoningEffort: "medium" })).toEqual({
670
+ openai: { reasoningEffort: "medium" },
671
+ })
672
+ })
673
+
674
+ test("uses groq slug for groq models", () => {
675
+ const model = createModel({
676
+ providerID: "vercel",
677
+ api: {
678
+ id: "groq/llama-3.3-70b-versatile",
679
+ url: "https://ai-gateway.vercel.sh/v3/ai",
680
+ npm: "@ai-sdk/gateway",
681
+ },
682
+ })
683
+
684
+ expect(ProviderTransform.providerOptions(model, { reasoningFormat: "parsed" })).toEqual({
685
+ groq: { reasoningFormat: "parsed" },
686
+ })
687
+ })
688
+ })
689
+
690
+ describe("ProviderTransform.schema - gemini array items", () => {
691
+ test("adds missing items for array properties", () => {
692
+ const geminiModel = {
693
+ providerID: "google",
694
+ api: {
695
+ id: "gemini-3-pro",
696
+ },
697
+ } as any
698
+
699
+ const schema = {
700
+ type: "object",
701
+ properties: {
702
+ nodes: { type: "array" },
703
+ edges: { type: "array", items: { type: "string" } },
704
+ },
705
+ } as any
706
+
707
+ const result = ProviderTransform.schema(geminiModel, schema) as any
708
+
709
+ expect(result.properties.nodes.items).toBeDefined()
710
+ expect(result.properties.edges.items.type).toBe("string")
711
+ })
712
+ })
713
+
714
+ describe("ProviderTransform.schema - gemini nested array items", () => {
715
+ const geminiModel = {
716
+ providerID: "google",
717
+ api: {
718
+ id: "gemini-3-pro",
719
+ },
720
+ } as any
721
+
722
+ test("adds type to 2D array with empty inner items", () => {
723
+ const schema = {
724
+ type: "object",
725
+ properties: {
726
+ values: {
727
+ type: "array",
728
+ items: {
729
+ type: "array",
730
+ items: {}, // Empty items object
731
+ },
732
+ },
733
+ },
734
+ } as any
735
+
736
+ const result = ProviderTransform.schema(geminiModel, schema) as any
737
+
738
+ // Inner items should have a default type
739
+ expect(result.properties.values.items.items.type).toBe("string")
740
+ })
741
+
742
+ test("adds items and type to 2D array with missing inner items", () => {
743
+ const schema = {
744
+ type: "object",
745
+ properties: {
746
+ data: {
747
+ type: "array",
748
+ items: { type: "array" }, // No items at all
749
+ },
750
+ },
751
+ } as any
752
+
753
+ const result = ProviderTransform.schema(geminiModel, schema) as any
754
+
755
+ expect(result.properties.data.items.items).toBeDefined()
756
+ expect(result.properties.data.items.items.type).toBe("string")
757
+ })
758
+
759
+ test("handles deeply nested arrays (3D)", () => {
760
+ const schema = {
761
+ type: "object",
762
+ properties: {
763
+ matrix: {
764
+ type: "array",
765
+ items: {
766
+ type: "array",
767
+ items: {
768
+ type: "array",
769
+ // No items
770
+ },
771
+ },
772
+ },
773
+ },
774
+ } as any
775
+
776
+ const result = ProviderTransform.schema(geminiModel, schema) as any
777
+
778
+ expect(result.properties.matrix.items.items.items).toBeDefined()
779
+ expect(result.properties.matrix.items.items.items.type).toBe("string")
780
+ })
781
+
782
+ test("preserves existing item types in nested arrays", () => {
783
+ const schema = {
784
+ type: "object",
785
+ properties: {
786
+ numbers: {
787
+ type: "array",
788
+ items: {
789
+ type: "array",
790
+ items: { type: "number" }, // Has explicit type
791
+ },
792
+ },
793
+ },
794
+ } as any
795
+
796
+ const result = ProviderTransform.schema(geminiModel, schema) as any
797
+
798
+ // Should preserve the explicit type
799
+ expect(result.properties.numbers.items.items.type).toBe("number")
800
+ })
801
+
802
+ test("handles mixed nested structures with objects and arrays", () => {
803
+ const schema = {
804
+ type: "object",
805
+ properties: {
806
+ spreadsheetData: {
807
+ type: "object",
808
+ properties: {
809
+ rows: {
810
+ type: "array",
811
+ items: {
812
+ type: "array",
813
+ items: {}, // Empty items
814
+ },
815
+ },
816
+ },
817
+ },
818
+ },
819
+ } as any
820
+
821
+ const result = ProviderTransform.schema(geminiModel, schema) as any
822
+
823
+ expect(result.properties.spreadsheetData.properties.rows.items.items.type).toBe("string")
824
+ })
825
+ })
826
+
827
+ describe("ProviderTransform.schema - gemini combiner nodes", () => {
828
+ const geminiModel = {
829
+ providerID: "google",
830
+ api: {
831
+ id: "gemini-3-pro",
832
+ },
833
+ } as any
834
+
835
+ const walk = (node: any, cb: (node: any, path: (string | number)[]) => void, path: (string | number)[] = []) => {
836
+ if (node === null || typeof node !== "object") {
837
+ return
838
+ }
839
+ if (Array.isArray(node)) {
840
+ node.forEach((item, i) => walk(item, cb, [...path, i]))
841
+ return
842
+ }
843
+ cb(node, path)
844
+ Object.entries(node).forEach(([key, value]) => walk(value, cb, [...path, key]))
845
+ }
846
+
847
+ test("keeps edits.items.anyOf without adding type", () => {
848
+ const schema = {
849
+ type: "object",
850
+ properties: {
851
+ edits: {
852
+ type: "array",
853
+ items: {
854
+ anyOf: [
855
+ {
856
+ type: "object",
857
+ properties: {
858
+ old_string: { type: "string" },
859
+ new_string: { type: "string" },
860
+ },
861
+ required: ["old_string", "new_string"],
862
+ },
863
+ {
864
+ type: "object",
865
+ properties: {
866
+ old_string: { type: "string" },
867
+ new_string: { type: "string" },
868
+ replace_all: { type: "boolean" },
869
+ },
870
+ required: ["old_string", "new_string"],
871
+ },
872
+ ],
873
+ },
874
+ },
875
+ },
876
+ required: ["edits"],
877
+ } as any
878
+
879
+ const result = ProviderTransform.schema(geminiModel, schema) as any
880
+
881
+ expect(Array.isArray(result.properties.edits.items.anyOf)).toBe(true)
882
+ expect(result.properties.edits.items.type).toBeUndefined()
883
+ })
884
+
885
+ test("does not add sibling keys to combiner nodes during sanitize", () => {
886
+ const schema = {
887
+ type: "object",
888
+ properties: {
889
+ edits: {
890
+ type: "array",
891
+ items: {
892
+ anyOf: [{ type: "string" }, { type: "number" }],
893
+ },
894
+ },
895
+ value: {
896
+ oneOf: [{ type: "string" }, { type: "boolean" }],
897
+ },
898
+ meta: {
899
+ allOf: [
900
+ {
901
+ type: "object",
902
+ properties: { a: { type: "string" } },
903
+ },
904
+ {
905
+ type: "object",
906
+ properties: { b: { type: "string" } },
907
+ },
908
+ ],
909
+ },
910
+ },
911
+ } as any
912
+ const input = JSON.parse(JSON.stringify(schema))
913
+ const result = ProviderTransform.schema(geminiModel, schema) as any
914
+
915
+ walk(result, (node, path) => {
916
+ const hasCombiner = Array.isArray(node.anyOf) || Array.isArray(node.oneOf) || Array.isArray(node.allOf)
917
+ if (!hasCombiner) {
918
+ return
919
+ }
920
+ const before = path.reduce((acc: any, key) => acc?.[key], input)
921
+ const added = Object.keys(node).filter((key) => !(key in before))
922
+ expect(added).toEqual([])
923
+ })
924
+ })
925
+ })
926
+
927
+ describe("ProviderTransform.schema - gemini non-object properties removal", () => {
928
+ const geminiModel = {
929
+ providerID: "google",
930
+ api: {
931
+ id: "gemini-3-pro",
932
+ },
933
+ } as any
934
+
935
+ test("removes properties from non-object types", () => {
936
+ const schema = {
937
+ type: "object",
938
+ properties: {
939
+ data: {
940
+ type: "string",
941
+ properties: { invalid: { type: "string" } },
942
+ },
943
+ },
944
+ } as any
945
+
946
+ const result = ProviderTransform.schema(geminiModel, schema) as any
947
+
948
+ expect(result.properties.data.type).toBe("string")
949
+ expect(result.properties.data.properties).toBeUndefined()
950
+ })
951
+
952
+ test("removes required from non-object types", () => {
953
+ const schema = {
954
+ type: "object",
955
+ properties: {
956
+ data: {
957
+ type: "array",
958
+ items: { type: "string" },
959
+ required: ["invalid"],
960
+ },
961
+ },
962
+ } as any
963
+
964
+ const result = ProviderTransform.schema(geminiModel, schema) as any
965
+
966
+ expect(result.properties.data.type).toBe("array")
967
+ expect(result.properties.data.required).toBeUndefined()
968
+ })
969
+
970
+ test("removes properties and required from nested non-object types", () => {
971
+ const schema = {
972
+ type: "object",
973
+ properties: {
974
+ outer: {
975
+ type: "object",
976
+ properties: {
977
+ inner: {
978
+ type: "number",
979
+ properties: { bad: { type: "string" } },
980
+ required: ["bad"],
981
+ },
982
+ },
983
+ },
984
+ },
985
+ } as any
986
+
987
+ const result = ProviderTransform.schema(geminiModel, schema) as any
988
+
989
+ expect(result.properties.outer.properties.inner.type).toBe("number")
990
+ expect(result.properties.outer.properties.inner.properties).toBeUndefined()
991
+ expect(result.properties.outer.properties.inner.required).toBeUndefined()
992
+ })
993
+
994
+ test("keeps properties and required on object types", () => {
995
+ const schema = {
996
+ type: "object",
997
+ properties: {
998
+ data: {
999
+ type: "object",
1000
+ properties: { name: { type: "string" } },
1001
+ required: ["name"],
1002
+ },
1003
+ },
1004
+ } as any
1005
+
1006
+ const result = ProviderTransform.schema(geminiModel, schema) as any
1007
+
1008
+ expect(result.properties.data.type).toBe("object")
1009
+ expect(result.properties.data.properties).toBeDefined()
1010
+ expect(result.properties.data.required).toEqual(["name"])
1011
+ })
1012
+
1013
+ test("does not affect non-gemini providers", () => {
1014
+ const openaiModel = {
1015
+ providerID: "openai",
1016
+ api: {
1017
+ id: "gpt-4",
1018
+ },
1019
+ } as any
1020
+
1021
+ const schema = {
1022
+ type: "object",
1023
+ properties: {
1024
+ data: {
1025
+ type: "string",
1026
+ properties: { invalid: { type: "string" } },
1027
+ },
1028
+ },
1029
+ } as any
1030
+
1031
+ const result = ProviderTransform.schema(openaiModel, schema) as any
1032
+
1033
+ expect(result.properties.data.properties).toBeDefined()
1034
+ })
1035
+ })
1036
+
1037
+ describe("ProviderTransform.schema - moonshot $ref siblings", () => {
1038
+ const moonshotModel = {
1039
+ providerID: "moonshotai",
1040
+ api: {
1041
+ id: "kimi-k2",
1042
+ },
1043
+ } as any
1044
+
1045
+ test("removes sibling descriptions from referenced tool parameter schemas", () => {
1046
+ const schema = {
1047
+ type: "object",
1048
+ properties: {
1049
+ deviceType: {
1050
+ description: "Optional. The type of device that captured the screenshot, e.g. mobile or desktop.",
1051
+ enum: ["DEVICE_TYPE_UNSPECIFIED", "MOBILE", "DESKTOP", "TABLET", "AGNOSTIC"],
1052
+ type: "string",
1053
+ },
1054
+ modelId: {
1055
+ description: "Optional. The model to use for generation.",
1056
+ enum: ["MODEL_ID_UNSPECIFIED", "GEMINI_3_PRO", "GEMINI_3_FLASH", "GEMINI_3_1_PRO"],
1057
+ type: "string",
1058
+ },
1059
+ projectId: {
1060
+ description: "Required. The project ID of screens to generate variants for.",
1061
+ type: "string",
1062
+ },
1063
+ prompt: {
1064
+ description: "Required. The input text used to generate the variants.",
1065
+ type: "string",
1066
+ },
1067
+ selectedScreenIds: {
1068
+ description: "Required. The screen ids of screen to generate variants for.",
1069
+ items: {
1070
+ type: "string",
1071
+ },
1072
+ type: "array",
1073
+ },
1074
+ variantOptions: {
1075
+ $ref: "#/$defs/VariantOptions",
1076
+ description:
1077
+ "Required. The variant options for generation, including the number of variants, creative range, and aspects to focus on.",
1078
+ },
1079
+ },
1080
+ required: ["projectId", "selectedScreenIds", "prompt", "variantOptions"],
1081
+ $defs: {
1082
+ VariantOptions: {
1083
+ description:
1084
+ "Configuration options for design variant generation. This message captures all parameters used to generate variants, allowing the configuration to be stored, replayed, or analyzed.",
1085
+ properties: {
1086
+ aspects: {
1087
+ description: "Optional. Specific aspects to focus on. If empty, all aspects may be varied.",
1088
+ items: {
1089
+ enum: ["VARIANT_ASPECT_UNSPECIFIED", "LAYOUT", "COLOR_SCHEME", "IMAGES", "TEXT_FONT", "TEXT_CONTENT"],
1090
+ type: "string",
1091
+ },
1092
+ type: "array",
1093
+ },
1094
+ creativeRange: {
1095
+ description: "Optional. Creative range for variations. Default: EXPLORE",
1096
+ enum: ["CREATIVE_RANGE_UNSPECIFIED", "REFINE", "EXPLORE", "REIMAGINE"],
1097
+ type: "string",
1098
+ },
1099
+ variantCount: {
1100
+ description: "Optional. Number of variants to generate (1-5). Default: 3",
1101
+ format: "int32",
1102
+ type: "integer",
1103
+ },
1104
+ },
1105
+ type: "object",
1106
+ },
1107
+ },
1108
+ description: "Request message for GenerateVariants.",
1109
+ additionalProperties: false,
1110
+ } as any
1111
+
1112
+ const result = ProviderTransform.schema(moonshotModel, schema) as any
1113
+
1114
+ expect(result.properties.variantOptions).toEqual({
1115
+ $ref: "#/$defs/VariantOptions",
1116
+ })
1117
+ expect(result.$defs.VariantOptions.description).toBe(schema.$defs.VariantOptions.description)
1118
+ })
1119
+
1120
+ test("also runs for kimi models outside the moonshot provider", () => {
1121
+ const result = ProviderTransform.schema(
1122
+ {
1123
+ providerID: "openrouter",
1124
+ name: "Kimi K2",
1125
+ api: {
1126
+ id: "moonshotai/kimi-k2",
1127
+ },
1128
+ } as any,
1129
+ {
1130
+ type: "object",
1131
+ properties: {
1132
+ value: {
1133
+ $ref: "#/$defs/Value",
1134
+ description: "Moonshot rejects this sibling after ref expansion.",
1135
+ },
1136
+ },
1137
+ $defs: {
1138
+ Value: {
1139
+ description: "Referenced schema description stays here.",
1140
+ type: "object",
1141
+ },
1142
+ },
1143
+ } as any,
1144
+ ) as any
1145
+
1146
+ expect(result.properties.value).toEqual({
1147
+ $ref: "#/$defs/Value",
1148
+ })
1149
+ })
1150
+
1151
+ test("converts tuple-style array items to a single item schema", () => {
1152
+ const result = ProviderTransform.schema(moonshotModel, {
1153
+ type: "object",
1154
+ properties: {
1155
+ codeSpec: {
1156
+ type: "object",
1157
+ properties: {
1158
+ accessibility: {
1159
+ type: "object",
1160
+ properties: {
1161
+ renderedSize: {
1162
+ description: "Rendered size [width, height] in px",
1163
+ type: "array",
1164
+ items: [{ type: "number" }, { type: "number" }],
1165
+ minItems: 2,
1166
+ maxItems: 2,
1167
+ },
1168
+ },
1169
+ },
1170
+ },
1171
+ },
1172
+ },
1173
+ } as any) as any
1174
+
1175
+ expect(result.properties.codeSpec.properties.accessibility.properties.renderedSize.items).toEqual({
1176
+ type: "number",
1177
+ })
1178
+ })
1179
+ })
1180
+
1181
+ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
1182
+ test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
1183
+ const msgs = [
1184
+ {
1185
+ role: "assistant",
1186
+ content: [
1187
+ { type: "reasoning", text: "Let me think about this..." },
1188
+ {
1189
+ type: "tool-call",
1190
+ toolCallId: "test",
1191
+ toolName: "bash",
1192
+ input: { command: "echo hello" },
1193
+ },
1194
+ ],
1195
+ },
1196
+ ] as any[]
1197
+
1198
+ const result = ProviderTransform.message(
1199
+ msgs,
1200
+ {
1201
+ id: ModelV2.ID.make("deepseek/deepseek-chat"),
1202
+ providerID: ProviderV2.ID.make("deepseek"),
1203
+ api: {
1204
+ id: "deepseek-chat",
1205
+ url: "https://api.deepseek.com",
1206
+ npm: "@ai-sdk/openai-compatible",
1207
+ },
1208
+ name: "DeepSeek Chat",
1209
+ capabilities: {
1210
+ temperature: true,
1211
+ reasoning: true,
1212
+ attachment: false,
1213
+ toolcall: true,
1214
+ input: { text: true, audio: false, image: false, video: false, pdf: false },
1215
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1216
+ interleaved: {
1217
+ field: "reasoning_content",
1218
+ },
1219
+ },
1220
+ cost: {
1221
+ input: 0.001,
1222
+ output: 0.002,
1223
+ cache: { read: 0.0001, write: 0.0002 },
1224
+ },
1225
+ limit: {
1226
+ context: 128000,
1227
+ output: 8192,
1228
+ },
1229
+ status: "active",
1230
+ options: {},
1231
+ headers: {},
1232
+ release_date: "2023-04-01",
1233
+ },
1234
+ {},
1235
+ )
1236
+
1237
+ expect(result).toHaveLength(1)
1238
+ expect(result[0].content).toEqual([
1239
+ {
1240
+ type: "tool-call",
1241
+ toolCallId: "test",
1242
+ toolName: "bash",
1243
+ input: { command: "echo hello" },
1244
+ },
1245
+ ])
1246
+ expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Let me think about this...")
1247
+ })
1248
+
1249
+ test("Non-DeepSeek providers leave reasoning content unchanged", () => {
1250
+ const msgs = [
1251
+ {
1252
+ role: "assistant",
1253
+ content: [
1254
+ { type: "reasoning", text: "Should not be processed" },
1255
+ { type: "text", text: "Answer" },
1256
+ ],
1257
+ },
1258
+ ] as any[]
1259
+
1260
+ const result = ProviderTransform.message(
1261
+ msgs,
1262
+ {
1263
+ id: ModelV2.ID.make("openai/gpt-4"),
1264
+ providerID: ProviderV2.ID.make("openai"),
1265
+ api: {
1266
+ id: "gpt-4",
1267
+ url: "https://api.openai.com",
1268
+ npm: "@ai-sdk/openai",
1269
+ },
1270
+ name: "GPT-4",
1271
+ capabilities: {
1272
+ temperature: true,
1273
+ reasoning: false,
1274
+ attachment: true,
1275
+ toolcall: true,
1276
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
1277
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1278
+ interleaved: false,
1279
+ },
1280
+ cost: {
1281
+ input: 0.03,
1282
+ output: 0.06,
1283
+ cache: { read: 0.001, write: 0.002 },
1284
+ },
1285
+ limit: {
1286
+ context: 128000,
1287
+ output: 4096,
1288
+ },
1289
+ status: "active",
1290
+ options: {},
1291
+ headers: {},
1292
+ release_date: "2023-04-01",
1293
+ },
1294
+ {},
1295
+ )
1296
+
1297
+ expect(result[0].content).toEqual([
1298
+ { type: "reasoning", text: "Should not be processed" },
1299
+ { type: "text", text: "Answer" },
1300
+ ])
1301
+ expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBeUndefined()
1302
+ })
1303
+ })
1304
+
1305
+ describe("ProviderTransform.message - surrogate sanitization", () => {
1306
+ const model = {
1307
+ id: "test/test-model",
1308
+ providerID: "test",
1309
+ api: {
1310
+ id: "test-model",
1311
+ url: "https://api.test.com",
1312
+ npm: "@ai-sdk/openai-compatible",
1313
+ },
1314
+ name: "Test Model",
1315
+ capabilities: {
1316
+ temperature: true,
1317
+ reasoning: true,
1318
+ attachment: true,
1319
+ toolcall: true,
1320
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
1321
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1322
+ interleaved: false,
1323
+ },
1324
+ cost: { input: 0.001, output: 0.002, cache: { read: 0.0001, write: 0.0002 } },
1325
+ limit: { context: 128000, output: 8192 },
1326
+ status: "active",
1327
+ options: {},
1328
+ headers: {},
1329
+ } as any
1330
+
1331
+ test("replaces lone surrogates in model-visible text", () => {
1332
+ const lone = "\uD83D"
1333
+ const valid = "🚀"
1334
+ const sanitized = "�"
1335
+ const text = (label: string) => `${label} ${lone} and ${valid}`
1336
+ const expected = (label: string) => `${label} ${sanitized} and ${valid}`
1337
+ const msgs = [
1338
+ { role: "system", content: text("system") },
1339
+ { role: "user", content: text("user string") },
1340
+ {
1341
+ role: "user",
1342
+ content: [
1343
+ { type: "text", text: text("user text") },
1344
+ { type: "image", image: "data:image/png;base64,abcd" },
1345
+ ],
1346
+ },
1347
+ { role: "assistant", content: text("assistant string") },
1348
+ {
1349
+ role: "assistant",
1350
+ content: [
1351
+ { type: "text", text: text("assistant text") },
1352
+ { type: "reasoning", text: text("assistant reasoning") },
1353
+ { type: "tool-call", toolCallId: "call-1", toolName: "Read", input: { filePath: ".opencode/tool/emoji.ts" } },
1354
+ {
1355
+ type: "tool-result",
1356
+ toolCallId: "call-2",
1357
+ toolName: "Read",
1358
+ output: { type: "text", value: text("assistant tool text") },
1359
+ },
1360
+ {
1361
+ type: "tool-result",
1362
+ toolCallId: "call-3",
1363
+ toolName: "Read",
1364
+ output: { type: "error-text", value: text("assistant tool error") },
1365
+ },
1366
+ {
1367
+ type: "tool-result",
1368
+ toolCallId: "call-4",
1369
+ toolName: "Read",
1370
+ output: { type: "content", value: [{ type: "text", text: text("assistant tool content") }] },
1371
+ },
1372
+ ],
1373
+ },
1374
+ {
1375
+ role: "tool",
1376
+ content: [
1377
+ {
1378
+ type: "tool-result",
1379
+ toolCallId: "call-5",
1380
+ toolName: "Read",
1381
+ output: { type: "text", value: text("tool text") },
1382
+ },
1383
+ {
1384
+ type: "tool-result",
1385
+ toolCallId: "call-6",
1386
+ toolName: "Read",
1387
+ output: { type: "error-text", value: text("tool error") },
1388
+ },
1389
+ {
1390
+ type: "tool-result",
1391
+ toolCallId: "call-7",
1392
+ toolName: "Read",
1393
+ output: { type: "content", value: [{ type: "text", text: text("tool content") }] },
1394
+ },
1395
+ ],
1396
+ },
1397
+ ] as any[]
1398
+
1399
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
1400
+
1401
+ expect(result[0].content).toBe(expected("system"))
1402
+ expect(result[1].content).toBe(expected("user string"))
1403
+ expect(result[2].content[0].text).toBe(expected("user text"))
1404
+ expect(result[3].content).toBe(expected("assistant string"))
1405
+ expect(result[4].content[0].text).toBe(expected("assistant text"))
1406
+ expect(result[4].content[1].text).toBe(expected("assistant reasoning"))
1407
+ expect(result[4].content[3].output.value).toBe(expected("assistant tool text"))
1408
+ expect(result[4].content[4].output.value).toBe(expected("assistant tool error"))
1409
+ expect(result[4].content[5].output.value[0].text).toBe(expected("assistant tool content"))
1410
+ expect(result[5].content[0].output.value).toBe(expected("tool text"))
1411
+ expect(result[5].content[1].output.value).toBe(expected("tool error"))
1412
+ expect(result[5].content[2].output.value[0].text).toBe(expected("tool content"))
1413
+ expect(result[2].content[1]).toEqual({ type: "image", image: "data:image/png;base64,abcd" })
1414
+ })
1415
+ })
1416
+
1417
+ describe("ProviderTransform.message - empty image handling", () => {
1418
+ const mockModel = {
1419
+ id: "anthropic/claude-3-5-sonnet",
1420
+ providerID: "anthropic",
1421
+ api: {
1422
+ id: "claude-3-5-sonnet-20241022",
1423
+ url: "https://api.anthropic.com",
1424
+ npm: "@ai-sdk/anthropic",
1425
+ },
1426
+ name: "Claude 3.5 Sonnet",
1427
+ capabilities: {
1428
+ temperature: true,
1429
+ reasoning: false,
1430
+ attachment: true,
1431
+ toolcall: true,
1432
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
1433
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1434
+ interleaved: false,
1435
+ },
1436
+ cost: {
1437
+ input: 0.003,
1438
+ output: 0.015,
1439
+ cache: { read: 0.0003, write: 0.00375 },
1440
+ },
1441
+ limit: {
1442
+ context: 200000,
1443
+ output: 8192,
1444
+ },
1445
+ status: "active",
1446
+ options: {},
1447
+ headers: {},
1448
+ } as any
1449
+
1450
+ test("should replace empty base64 image with error text", () => {
1451
+ const msgs = [
1452
+ {
1453
+ role: "user",
1454
+ content: [
1455
+ { type: "text", text: "What is in this image?" },
1456
+ { type: "image", image: "data:image/png;base64," },
1457
+ ],
1458
+ },
1459
+ ] as any[]
1460
+
1461
+ const result = ProviderTransform.message(msgs, mockModel, {})
1462
+
1463
+ expect(result).toHaveLength(1)
1464
+ expect(result[0].content).toHaveLength(2)
1465
+ expect(result[0].content[0]).toEqual({ type: "text", text: "What is in this image?" })
1466
+ expect(result[0].content[1]).toEqual({
1467
+ type: "text",
1468
+ text: "ERROR: Image file is empty or corrupted. Please provide a valid image.",
1469
+ })
1470
+ })
1471
+
1472
+ test("should keep valid base64 images unchanged", () => {
1473
+ const validBase64 =
1474
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
1475
+ const msgs = [
1476
+ {
1477
+ role: "user",
1478
+ content: [
1479
+ { type: "text", text: "What is in this image?" },
1480
+ { type: "image", image: `data:image/png;base64,${validBase64}` },
1481
+ ],
1482
+ },
1483
+ ] as any[]
1484
+
1485
+ const result = ProviderTransform.message(msgs, mockModel, {})
1486
+
1487
+ expect(result).toHaveLength(1)
1488
+ expect(result[0].content).toHaveLength(2)
1489
+ expect(result[0].content[0]).toEqual({ type: "text", text: "What is in this image?" })
1490
+ expect(result[0].content[1]).toEqual({ type: "image", image: `data:image/png;base64,${validBase64}` })
1491
+ })
1492
+
1493
+ test("should handle mixed valid and empty images", () => {
1494
+ const validBase64 =
1495
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
1496
+ const msgs = [
1497
+ {
1498
+ role: "user",
1499
+ content: [
1500
+ { type: "text", text: "Compare these images" },
1501
+ { type: "image", image: `data:image/png;base64,${validBase64}` },
1502
+ { type: "image", image: "data:image/jpeg;base64," },
1503
+ ],
1504
+ },
1505
+ ] as any[]
1506
+
1507
+ const result = ProviderTransform.message(msgs, mockModel, {})
1508
+
1509
+ expect(result).toHaveLength(1)
1510
+ expect(result[0].content).toHaveLength(3)
1511
+ expect(result[0].content[0]).toEqual({ type: "text", text: "Compare these images" })
1512
+ expect(result[0].content[1]).toEqual({ type: "image", image: `data:image/png;base64,${validBase64}` })
1513
+ expect(result[0].content[2]).toEqual({
1514
+ type: "text",
1515
+ text: "ERROR: Image file is empty or corrupted. Please provide a valid image.",
1516
+ })
1517
+ })
1518
+ })
1519
+
1520
+ describe("ProviderTransform.message - anthropic empty content filtering", () => {
1521
+ const anthropicModel = {
1522
+ id: "anthropic/claude-3-5-sonnet",
1523
+ providerID: "anthropic",
1524
+ api: {
1525
+ id: "claude-3-5-sonnet-20241022",
1526
+ url: "https://api.anthropic.com",
1527
+ npm: "@ai-sdk/anthropic",
1528
+ },
1529
+ name: "Claude 3.5 Sonnet",
1530
+ capabilities: {
1531
+ temperature: true,
1532
+ reasoning: false,
1533
+ attachment: true,
1534
+ toolcall: true,
1535
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
1536
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1537
+ interleaved: false,
1538
+ },
1539
+ cost: {
1540
+ input: 0.003,
1541
+ output: 0.015,
1542
+ cache: { read: 0.0003, write: 0.00375 },
1543
+ },
1544
+ limit: {
1545
+ context: 200000,
1546
+ output: 8192,
1547
+ },
1548
+ status: "active",
1549
+ options: {},
1550
+ headers: {},
1551
+ } as any
1552
+
1553
+ test("filters out messages with empty string content", () => {
1554
+ const msgs = [
1555
+ { role: "user", content: "Hello" },
1556
+ { role: "assistant", content: "" },
1557
+ { role: "user", content: "World" },
1558
+ ] as any[]
1559
+
1560
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1561
+
1562
+ expect(result).toHaveLength(2)
1563
+ expect(result[0].content).toBe("Hello")
1564
+ expect(result[1].content).toBe("World")
1565
+ })
1566
+
1567
+ test("filters out empty text parts from array content", () => {
1568
+ const msgs = [
1569
+ {
1570
+ role: "assistant",
1571
+ content: [
1572
+ { type: "text", text: "" },
1573
+ { type: "text", text: "Hello" },
1574
+ { type: "text", text: "" },
1575
+ ],
1576
+ },
1577
+ ] as any[]
1578
+
1579
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1580
+
1581
+ expect(result).toHaveLength(1)
1582
+ expect(result[0].content).toHaveLength(1)
1583
+ expect(result[0].content[0]).toEqual({ type: "text", text: "Hello" })
1584
+ })
1585
+
1586
+ test("filters out empty reasoning parts from array content", () => {
1587
+ const msgs = [
1588
+ {
1589
+ role: "assistant",
1590
+ content: [
1591
+ { type: "reasoning", text: "" },
1592
+ { type: "text", text: "Answer" },
1593
+ { type: "reasoning", text: "" },
1594
+ ],
1595
+ },
1596
+ ] as any[]
1597
+
1598
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1599
+
1600
+ expect(result).toHaveLength(1)
1601
+ expect(result[0].content).toHaveLength(1)
1602
+ expect(result[0].content[0]).toEqual({ type: "text", text: "Answer" })
1603
+ })
1604
+
1605
+ test("removes entire message when all parts are empty", () => {
1606
+ const msgs = [
1607
+ { role: "user", content: "Hello" },
1608
+ {
1609
+ role: "assistant",
1610
+ content: [
1611
+ { type: "text", text: "" },
1612
+ { type: "reasoning", text: "" },
1613
+ ],
1614
+ },
1615
+ { role: "user", content: "World" },
1616
+ ] as any[]
1617
+
1618
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1619
+
1620
+ expect(result).toHaveLength(2)
1621
+ expect(result[0].content).toBe("Hello")
1622
+ expect(result[1].content).toBe("World")
1623
+ })
1624
+
1625
+ test("keeps non-text/reasoning parts even if text parts are empty", () => {
1626
+ const msgs = [
1627
+ {
1628
+ role: "assistant",
1629
+ content: [
1630
+ { type: "text", text: "" },
1631
+ { type: "tool-call", toolCallId: "123", toolName: "bash", input: { command: "ls" } },
1632
+ ],
1633
+ },
1634
+ ] as any[]
1635
+
1636
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1637
+
1638
+ expect(result).toHaveLength(1)
1639
+ expect(result[0].content).toHaveLength(1)
1640
+ expect(result[0].content[0]).toEqual({
1641
+ type: "tool-call",
1642
+ toolCallId: "123",
1643
+ toolName: "bash",
1644
+ input: { command: "ls" },
1645
+ })
1646
+ })
1647
+
1648
+ test("keeps messages with valid text alongside empty parts", () => {
1649
+ const msgs = [
1650
+ {
1651
+ role: "assistant",
1652
+ content: [
1653
+ { type: "reasoning", text: "Thinking..." },
1654
+ { type: "text", text: "" },
1655
+ { type: "text", text: "Result" },
1656
+ ],
1657
+ },
1658
+ ] as any[]
1659
+
1660
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1661
+
1662
+ expect(result).toHaveLength(1)
1663
+ expect(result[0].content).toHaveLength(2)
1664
+ expect(result[0].content[0]).toEqual({ type: "reasoning", text: "Thinking..." })
1665
+ expect(result[0].content[1]).toEqual({ type: "text", text: "Result" })
1666
+ })
1667
+
1668
+ test("filters empty content for bedrock provider", () => {
1669
+ const bedrockModel = {
1670
+ ...anthropicModel,
1671
+ id: "amazon-bedrock/anthropic.claude-opus-4-6",
1672
+ providerID: "amazon-bedrock",
1673
+ api: {
1674
+ id: "anthropic.claude-opus-4-6",
1675
+ url: "https://bedrock-runtime.us-east-1.amazonaws.com",
1676
+ npm: "@ai-sdk/amazon-bedrock",
1677
+ },
1678
+ }
1679
+
1680
+ const msgs = [
1681
+ { role: "user", content: "Hello" },
1682
+ { role: "assistant", content: "" },
1683
+ {
1684
+ role: "assistant",
1685
+ content: [
1686
+ { type: "text", text: "" },
1687
+ { type: "text", text: "Answer" },
1688
+ ],
1689
+ },
1690
+ ] as any[]
1691
+
1692
+ const result = ProviderTransform.message(msgs, bedrockModel, {})
1693
+
1694
+ expect(result).toHaveLength(2)
1695
+ expect(result[0].content).toBe("Hello")
1696
+ expect(result[1].content).toHaveLength(1)
1697
+ expect(result[1].content[0]).toEqual({ type: "text", text: "Answer" })
1698
+ })
1699
+
1700
+ test("does not filter for non-anthropic providers", () => {
1701
+ const openaiModel = {
1702
+ ...anthropicModel,
1703
+ providerID: "openai",
1704
+ api: {
1705
+ id: "gpt-4",
1706
+ url: "https://api.openai.com",
1707
+ npm: "@ai-sdk/openai",
1708
+ },
1709
+ }
1710
+
1711
+ const msgs = [
1712
+ { role: "assistant", content: "" },
1713
+ {
1714
+ role: "assistant",
1715
+ content: [{ type: "text", text: "" }],
1716
+ },
1717
+ ] as any[]
1718
+
1719
+ const result = ProviderTransform.message(msgs, openaiModel, {})
1720
+
1721
+ expect(result).toHaveLength(2)
1722
+ expect(result[0].content).toBe("")
1723
+ expect(result[1].content).toHaveLength(1)
1724
+ })
1725
+
1726
+ test("leaves valid anthropic assistant tool ordering unchanged", () => {
1727
+ const msgs = [
1728
+ {
1729
+ role: "assistant",
1730
+ content: [
1731
+ { type: "text", text: "I checked your home directory and looked for PDF files." },
1732
+ { type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
1733
+ { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } },
1734
+ ],
1735
+ },
1736
+ ] as any[]
1737
+
1738
+ const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
1739
+
1740
+ expect(result).toHaveLength(1)
1741
+ expect(result[0].content).toMatchObject([
1742
+ { type: "text", text: "I checked your home directory and looked for PDF files." },
1743
+ { type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
1744
+ { type: "tool-call", toolCallId: "toolu_2", toolName: "glob", input: { pattern: "**/*.pdf" } },
1745
+ ])
1746
+ })
1747
+ })
1748
+
1749
+ describe("ProviderTransform.message - strip openai metadata when store=false", () => {
1750
+ const openaiModel = {
1751
+ id: "openai/gpt-5",
1752
+ providerID: "openai",
1753
+ api: {
1754
+ id: "gpt-5",
1755
+ url: "https://api.openai.com",
1756
+ npm: "@ai-sdk/openai",
1757
+ },
1758
+ name: "GPT-5",
1759
+ capabilities: {
1760
+ temperature: true,
1761
+ reasoning: true,
1762
+ attachment: true,
1763
+ toolcall: true,
1764
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
1765
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1766
+ interleaved: false,
1767
+ },
1768
+ cost: { input: 0.03, output: 0.06, cache: { read: 0.001, write: 0.002 } },
1769
+ limit: { context: 128000, output: 4096 },
1770
+ status: "active",
1771
+ options: {},
1772
+ headers: {},
1773
+ } as any
1774
+
1775
+ test("preserves itemId and reasoningEncryptedContent when store=false", () => {
1776
+ const msgs = [
1777
+ {
1778
+ role: "assistant",
1779
+ content: [
1780
+ {
1781
+ type: "reasoning",
1782
+ text: "thinking...",
1783
+ providerOptions: {
1784
+ openai: {
1785
+ itemId: "rs_123",
1786
+ reasoningEncryptedContent: "encrypted",
1787
+ },
1788
+ },
1789
+ },
1790
+ {
1791
+ type: "text",
1792
+ text: "Hello",
1793
+ providerOptions: {
1794
+ openai: {
1795
+ itemId: "msg_456",
1796
+ },
1797
+ },
1798
+ },
1799
+ ],
1800
+ },
1801
+ ] as any[]
1802
+
1803
+ const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
1804
+
1805
+ expect(result).toHaveLength(1)
1806
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
1807
+ expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
1808
+ })
1809
+
1810
+ test("preserves itemId and reasoningEncryptedContent when store=false even when not openai", () => {
1811
+ const zenModel = {
1812
+ ...openaiModel,
1813
+ providerID: "zen",
1814
+ }
1815
+ const msgs = [
1816
+ {
1817
+ role: "assistant",
1818
+ content: [
1819
+ {
1820
+ type: "reasoning",
1821
+ text: "thinking...",
1822
+ providerOptions: {
1823
+ openai: {
1824
+ itemId: "rs_123",
1825
+ reasoningEncryptedContent: "encrypted",
1826
+ },
1827
+ },
1828
+ },
1829
+ {
1830
+ type: "text",
1831
+ text: "Hello",
1832
+ providerOptions: {
1833
+ openai: {
1834
+ itemId: "msg_456",
1835
+ },
1836
+ },
1837
+ },
1838
+ ],
1839
+ },
1840
+ ] as any[]
1841
+
1842
+ const result = ProviderTransform.message(msgs, zenModel, { store: false }) as any[]
1843
+
1844
+ expect(result).toHaveLength(1)
1845
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
1846
+ expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
1847
+ })
1848
+
1849
+ test("preserves other openai options including itemId", () => {
1850
+ const msgs = [
1851
+ {
1852
+ role: "assistant",
1853
+ content: [
1854
+ {
1855
+ type: "text",
1856
+ text: "Hello",
1857
+ providerOptions: {
1858
+ openai: {
1859
+ itemId: "msg_123",
1860
+ otherOption: "value",
1861
+ },
1862
+ },
1863
+ },
1864
+ ],
1865
+ },
1866
+ ] as any[]
1867
+
1868
+ const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
1869
+
1870
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1871
+ expect(result[0].content[0].providerOptions?.openai?.otherOption).toBe("value")
1872
+ })
1873
+
1874
+ test("preserves metadata for openai package when store is true", () => {
1875
+ const msgs = [
1876
+ {
1877
+ role: "assistant",
1878
+ content: [
1879
+ {
1880
+ type: "text",
1881
+ text: "Hello",
1882
+ providerOptions: {
1883
+ openai: {
1884
+ itemId: "msg_123",
1885
+ },
1886
+ },
1887
+ },
1888
+ ],
1889
+ },
1890
+ ] as any[]
1891
+
1892
+ // openai package preserves itemId regardless of store value
1893
+ const result = ProviderTransform.message(msgs, openaiModel, { store: true }) as any[]
1894
+
1895
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1896
+ })
1897
+
1898
+ test("preserves metadata for non-openai packages when store is false", () => {
1899
+ const anthropicModel = {
1900
+ ...openaiModel,
1901
+ providerID: "anthropic",
1902
+ api: {
1903
+ id: "claude-3",
1904
+ url: "https://api.anthropic.com",
1905
+ npm: "@ai-sdk/anthropic",
1906
+ },
1907
+ }
1908
+ const msgs = [
1909
+ {
1910
+ role: "assistant",
1911
+ content: [
1912
+ {
1913
+ type: "text",
1914
+ text: "Hello",
1915
+ providerOptions: {
1916
+ openai: {
1917
+ itemId: "msg_123",
1918
+ },
1919
+ },
1920
+ },
1921
+ ],
1922
+ },
1923
+ ] as any[]
1924
+
1925
+ // store=false preserves metadata for non-openai packages
1926
+ const result = ProviderTransform.message(msgs, anthropicModel, { store: false }) as any[]
1927
+
1928
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1929
+ })
1930
+
1931
+ test("preserves metadata using providerID key when store is false", () => {
1932
+ const opencodeModel = {
1933
+ ...openaiModel,
1934
+ providerID: "opencode",
1935
+ api: {
1936
+ id: "opencode-test",
1937
+ url: "https://api.opencode.ai",
1938
+ npm: "@ai-sdk/openai-compatible",
1939
+ },
1940
+ }
1941
+ const msgs = [
1942
+ {
1943
+ role: "assistant",
1944
+ content: [
1945
+ {
1946
+ type: "text",
1947
+ text: "Hello",
1948
+ providerOptions: {
1949
+ opencode: {
1950
+ itemId: "msg_123",
1951
+ otherOption: "value",
1952
+ },
1953
+ },
1954
+ },
1955
+ ],
1956
+ },
1957
+ ] as any[]
1958
+
1959
+ const result = ProviderTransform.message(msgs, opencodeModel, { store: false }) as any[]
1960
+
1961
+ expect(result[0].content[0].providerOptions?.opencode?.itemId).toBe("msg_123")
1962
+ expect(result[0].content[0].providerOptions?.opencode?.otherOption).toBe("value")
1963
+ })
1964
+
1965
+ test("preserves itemId across all providerOptions keys", () => {
1966
+ const opencodeModel = {
1967
+ ...openaiModel,
1968
+ providerID: "opencode",
1969
+ api: {
1970
+ id: "opencode-test",
1971
+ url: "https://api.opencode.ai",
1972
+ npm: "@ai-sdk/openai-compatible",
1973
+ },
1974
+ }
1975
+ const msgs = [
1976
+ {
1977
+ role: "assistant",
1978
+ providerOptions: {
1979
+ openai: { itemId: "msg_root" },
1980
+ opencode: { itemId: "msg_opencode" },
1981
+ extra: { itemId: "msg_extra" },
1982
+ },
1983
+ content: [
1984
+ {
1985
+ type: "text",
1986
+ text: "Hello",
1987
+ providerOptions: {
1988
+ openai: { itemId: "msg_openai_part" },
1989
+ opencode: { itemId: "msg_opencode_part" },
1990
+ extra: { itemId: "msg_extra_part" },
1991
+ },
1992
+ },
1993
+ ],
1994
+ },
1995
+ ] as any[]
1996
+
1997
+ const result = ProviderTransform.message(msgs, opencodeModel, { store: false }) as any[]
1998
+
1999
+ expect(result[0].providerOptions?.openai?.itemId).toBe("msg_root")
2000
+ expect(result[0].providerOptions?.opencode?.itemId).toBe("msg_opencode")
2001
+ expect(result[0].providerOptions?.extra?.itemId).toBe("msg_extra")
2002
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_openai_part")
2003
+ expect(result[0].content[0].providerOptions?.opencode?.itemId).toBe("msg_opencode_part")
2004
+ expect(result[0].content[0].providerOptions?.extra?.itemId).toBe("msg_extra_part")
2005
+ })
2006
+
2007
+ test("does not strip metadata for non-openai packages when store is not false", () => {
2008
+ const anthropicModel = {
2009
+ ...openaiModel,
2010
+ providerID: "anthropic",
2011
+ api: {
2012
+ id: "claude-3",
2013
+ url: "https://api.anthropic.com",
2014
+ npm: "@ai-sdk/anthropic",
2015
+ },
2016
+ }
2017
+ const msgs = [
2018
+ {
2019
+ role: "assistant",
2020
+ content: [
2021
+ {
2022
+ type: "text",
2023
+ text: "Hello",
2024
+ providerOptions: {
2025
+ openai: {
2026
+ itemId: "msg_123",
2027
+ },
2028
+ },
2029
+ },
2030
+ ],
2031
+ },
2032
+ ] as any[]
2033
+
2034
+ const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
2035
+
2036
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
2037
+ })
2038
+ })
2039
+
2040
+ describe("ProviderTransform.message - providerOptions key remapping", () => {
2041
+ const createModel = (providerID: string, npm: string) =>
2042
+ ({
2043
+ id: `${providerID}/test-model`,
2044
+ providerID,
2045
+ api: {
2046
+ id: "test-model",
2047
+ url: "https://api.test.com",
2048
+ npm,
2049
+ },
2050
+ name: "Test Model",
2051
+ capabilities: {
2052
+ temperature: true,
2053
+ reasoning: false,
2054
+ attachment: true,
2055
+ toolcall: true,
2056
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
2057
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
2058
+ interleaved: false,
2059
+ },
2060
+ cost: { input: 0.001, output: 0.002, cache: { read: 0.0001, write: 0.0002 } },
2061
+ limit: { context: 128000, output: 8192 },
2062
+ status: "active",
2063
+ options: {},
2064
+ headers: {},
2065
+ }) as any
2066
+
2067
+ test("azure keeps 'azure' key and does not remap to 'openai'", () => {
2068
+ const model = createModel("azure", "@ai-sdk/azure")
2069
+ const msgs = [
2070
+ {
2071
+ role: "user",
2072
+ content: "Hello",
2073
+ providerOptions: {
2074
+ azure: { someOption: "value" },
2075
+ },
2076
+ },
2077
+ ] as any[]
2078
+
2079
+ const result = ProviderTransform.message(msgs, model, {})
2080
+
2081
+ expect(result[0].providerOptions?.azure).toEqual({ someOption: "value" })
2082
+ expect(result[0].providerOptions?.openai).toBeUndefined()
2083
+ })
2084
+
2085
+ test("azure cognitive services remaps providerID to 'azure' key", () => {
2086
+ const model = createModel("azure-cognitive-services", "@ai-sdk/azure")
2087
+ const msgs = [
2088
+ {
2089
+ role: "user",
2090
+ content: [
2091
+ {
2092
+ type: "text",
2093
+ text: "Hello",
2094
+ providerOptions: {
2095
+ "azure-cognitive-services": { part: true },
2096
+ },
2097
+ },
2098
+ ],
2099
+ providerOptions: {
2100
+ "azure-cognitive-services": { someOption: "value" },
2101
+ },
2102
+ },
2103
+ ] as any[]
2104
+
2105
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
2106
+ const part = result[0].content[0] as any
2107
+
2108
+ expect(result[0].providerOptions?.azure).toEqual({ someOption: "value" })
2109
+ expect(result[0].providerOptions?.["azure-cognitive-services"]).toBeUndefined()
2110
+ expect(part.providerOptions?.azure).toEqual({ part: true })
2111
+ expect(part.providerOptions?.["azure-cognitive-services"]).toBeUndefined()
2112
+ })
2113
+
2114
+ test("copilot remaps providerID to 'copilot' key", () => {
2115
+ const model = createModel("github-copilot", "@ai-sdk/github-copilot")
2116
+ const msgs = [
2117
+ {
2118
+ role: "user",
2119
+ content: "Hello",
2120
+ providerOptions: {
2121
+ copilot: { someOption: "value" },
2122
+ },
2123
+ },
2124
+ ] as any[]
2125
+
2126
+ const result = ProviderTransform.message(msgs, model, {})
2127
+
2128
+ expect(result[0].providerOptions?.copilot).toEqual({ someOption: "value" })
2129
+ expect(result[0].providerOptions?.["github-copilot"]).toBeUndefined()
2130
+ })
2131
+
2132
+ test("bedrock remaps providerID to 'bedrock' key", () => {
2133
+ const model = createModel("my-bedrock", "@ai-sdk/amazon-bedrock")
2134
+ const msgs = [
2135
+ {
2136
+ role: "user",
2137
+ content: "Hello",
2138
+ providerOptions: {
2139
+ "my-bedrock": { someOption: "value" },
2140
+ },
2141
+ },
2142
+ ] as any[]
2143
+
2144
+ const result = ProviderTransform.message(msgs, model, {})
2145
+
2146
+ expect(result[0].providerOptions?.bedrock).toEqual({ someOption: "value" })
2147
+ expect(result[0].providerOptions?.["my-bedrock"]).toBeUndefined()
2148
+ })
2149
+ })
2150
+
2151
+ describe("ProviderTransform.message - claude w/bedrock custom inference profile", () => {
2152
+ test("adds cachePoint", () => {
2153
+ const model = {
2154
+ id: "amazon-bedrock/custom-claude-sonnet-4.5",
2155
+ providerID: "amazon-bedrock",
2156
+ api: {
2157
+ id: "arn:aws:bedrock:xxx:yyy:application-inference-profile/zzz",
2158
+ url: "https://api.test.com",
2159
+ npm: "@ai-sdk/amazon-bedrock",
2160
+ },
2161
+ name: "Custom inference profile",
2162
+ capabilities: {},
2163
+ options: {},
2164
+ headers: {},
2165
+ } as any
2166
+
2167
+ const msgs = [
2168
+ {
2169
+ role: "user",
2170
+ content: "Hello",
2171
+ },
2172
+ ] as any[]
2173
+
2174
+ const result = ProviderTransform.message(msgs, model, {})
2175
+
2176
+ expect(result[0].providerOptions?.bedrock).toEqual(
2177
+ expect.objectContaining({
2178
+ cachePoint: {
2179
+ type: "default",
2180
+ },
2181
+ }),
2182
+ )
2183
+ })
2184
+ })
2185
+
2186
+ describe("ProviderTransform.message - bedrock caching with non-bedrock providerID", () => {
2187
+ test("applies cache options at message level when npm package is amazon-bedrock", () => {
2188
+ const model = {
2189
+ id: "aws/us.anthropic.claude-opus-4-6-v1",
2190
+ providerID: "aws",
2191
+ api: {
2192
+ id: "us.anthropic.claude-opus-4-6-v1",
2193
+ url: "https://bedrock-runtime.us-east-1.amazonaws.com",
2194
+ npm: "@ai-sdk/amazon-bedrock",
2195
+ },
2196
+ name: "Claude Opus 4.6",
2197
+ capabilities: {},
2198
+ options: {},
2199
+ headers: {},
2200
+ } as any
2201
+
2202
+ const msgs = [
2203
+ {
2204
+ role: "system",
2205
+ content: "You are a helpful assistant",
2206
+ },
2207
+ {
2208
+ role: "user",
2209
+ content: [{ type: "text", text: "Hello" }],
2210
+ },
2211
+ ] as any[]
2212
+
2213
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
2214
+
2215
+ // Cache should be at the message level and not the content-part level
2216
+ expect(result[0].providerOptions?.bedrock).toEqual({
2217
+ cachePoint: { type: "default" },
2218
+ })
2219
+ expect(result[0].content).toBe("You are a helpful assistant")
2220
+ })
2221
+ })
2222
+
2223
+ describe("ProviderTransform.message - cache control on gateway", () => {
2224
+ const createModel = (overrides: Partial<any> = {}) =>
2225
+ ({
2226
+ id: "anthropic/claude-sonnet-4",
2227
+ providerID: "vercel",
2228
+ api: {
2229
+ id: "anthropic/claude-sonnet-4",
2230
+ url: "https://ai-gateway.vercel.sh/v3/ai",
2231
+ npm: "@ai-sdk/gateway",
2232
+ },
2233
+ name: "Claude Sonnet 4",
2234
+ capabilities: {
2235
+ temperature: true,
2236
+ reasoning: true,
2237
+ attachment: true,
2238
+ toolcall: true,
2239
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
2240
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
2241
+ interleaved: false,
2242
+ },
2243
+ cost: { input: 0.001, output: 0.002, cache: { read: 0.0001, write: 0.0002 } },
2244
+ limit: { context: 200_000, output: 8192 },
2245
+ status: "active",
2246
+ options: {},
2247
+ headers: {},
2248
+ ...overrides,
2249
+ }) as any
2250
+
2251
+ test("gateway does not set cache control for anthropic models", () => {
2252
+ const model = createModel()
2253
+ const msgs = [
2254
+ {
2255
+ role: "system",
2256
+ content: "You are a helpful assistant",
2257
+ },
2258
+ {
2259
+ role: "user",
2260
+ content: "Hello",
2261
+ },
2262
+ ] as any[]
2263
+
2264
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
2265
+
2266
+ expect(result[0].content).toBe("You are a helpful assistant")
2267
+ expect(result[0].providerOptions).toBeUndefined()
2268
+ })
2269
+
2270
+ test("non-gateway anthropic keeps existing cache control behavior", () => {
2271
+ const model = createModel({
2272
+ providerID: "anthropic",
2273
+ api: {
2274
+ id: "claude-sonnet-4",
2275
+ url: "https://api.anthropic.com",
2276
+ npm: "@ai-sdk/anthropic",
2277
+ },
2278
+ })
2279
+ const msgs = [
2280
+ {
2281
+ role: "system",
2282
+ content: "You are a helpful assistant",
2283
+ },
2284
+ {
2285
+ role: "user",
2286
+ content: "Hello",
2287
+ },
2288
+ ] as any[]
2289
+
2290
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
2291
+
2292
+ expect(result[0].providerOptions).toEqual({
2293
+ anthropic: {
2294
+ cacheControl: {
2295
+ type: "ephemeral",
2296
+ },
2297
+ },
2298
+ openrouter: {
2299
+ cacheControl: {
2300
+ type: "ephemeral",
2301
+ },
2302
+ },
2303
+ bedrock: {
2304
+ cachePoint: {
2305
+ type: "default",
2306
+ },
2307
+ },
2308
+ openaiCompatible: {
2309
+ cache_control: {
2310
+ type: "ephemeral",
2311
+ },
2312
+ },
2313
+ copilot: {
2314
+ copilot_cache_control: {
2315
+ type: "ephemeral",
2316
+ },
2317
+ },
2318
+ alibaba: {
2319
+ cacheControl: {
2320
+ type: "ephemeral",
2321
+ },
2322
+ },
2323
+ })
2324
+ })
2325
+
2326
+ test("google-vertex-anthropic applies cache control", () => {
2327
+ const model = createModel({
2328
+ providerID: "google-vertex-anthropic",
2329
+ api: {
2330
+ id: "google-vertex-anthropic",
2331
+ url: "https://us-central1-aiplatform.googleapis.com",
2332
+ npm: "@ai-sdk/google-vertex/anthropic",
2333
+ },
2334
+ id: "claude-sonnet-4@20250514",
2335
+ })
2336
+ const msgs = [
2337
+ {
2338
+ role: "system",
2339
+ content: "You are a helpful assistant",
2340
+ },
2341
+ {
2342
+ role: "user",
2343
+ content: "Hello",
2344
+ },
2345
+ ] as any[]
2346
+
2347
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
2348
+
2349
+ expect(result[0].providerOptions).toEqual({
2350
+ anthropic: {
2351
+ cacheControl: {
2352
+ type: "ephemeral",
2353
+ },
2354
+ },
2355
+ openrouter: {
2356
+ cacheControl: {
2357
+ type: "ephemeral",
2358
+ },
2359
+ },
2360
+ bedrock: {
2361
+ cachePoint: {
2362
+ type: "default",
2363
+ },
2364
+ },
2365
+ openaiCompatible: {
2366
+ cache_control: {
2367
+ type: "ephemeral",
2368
+ },
2369
+ },
2370
+ copilot: {
2371
+ copilot_cache_control: {
2372
+ type: "ephemeral",
2373
+ },
2374
+ },
2375
+ alibaba: {
2376
+ cacheControl: {
2377
+ type: "ephemeral",
2378
+ },
2379
+ },
2380
+ })
2381
+ })
2382
+ })
2383
+
2384
+ describe("ProviderTransform.variants", () => {
2385
+ const createMockModel = (overrides: Partial<any> = {}): any => ({
2386
+ id: "test/test-model",
2387
+ providerID: "test",
2388
+ api: {
2389
+ id: "test-model",
2390
+ url: "https://api.test.com",
2391
+ npm: "@ai-sdk/openai",
2392
+ },
2393
+ name: "Test Model",
2394
+ capabilities: {
2395
+ temperature: true,
2396
+ reasoning: true,
2397
+ attachment: true,
2398
+ toolcall: true,
2399
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
2400
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
2401
+ interleaved: false,
2402
+ },
2403
+ cost: {
2404
+ input: 0.001,
2405
+ output: 0.002,
2406
+ cache: { read: 0.0001, write: 0.0002 },
2407
+ },
2408
+ limit: {
2409
+ context: 200_000,
2410
+ output: 64_000,
2411
+ },
2412
+ status: "active",
2413
+ options: {},
2414
+ headers: {},
2415
+ release_date: "2024-01-01",
2416
+ ...overrides,
2417
+ })
2418
+
2419
+ test("returns empty object when model has no reasoning capabilities", () => {
2420
+ const model = createMockModel({
2421
+ capabilities: { reasoning: false },
2422
+ })
2423
+ const result = ProviderTransform.variants(model)
2424
+ expect(result).toEqual({})
2425
+ })
2426
+
2427
+ test("deepseek returns empty object", () => {
2428
+ const model = createMockModel({
2429
+ id: "deepseek/deepseek-chat",
2430
+ providerID: "deepseek",
2431
+ api: {
2432
+ id: "deepseek-chat",
2433
+ url: "https://api.deepseek.com",
2434
+ npm: "@ai-sdk/openai-compatible",
2435
+ },
2436
+ })
2437
+ const result = ProviderTransform.variants(model)
2438
+ expect(result).toEqual({})
2439
+ })
2440
+
2441
+ test("minimax returns empty object", () => {
2442
+ const model = createMockModel({
2443
+ id: "minimax/minimax-model",
2444
+ providerID: "minimax",
2445
+ api: {
2446
+ id: "minimax-model",
2447
+ url: "https://api.minimax.com",
2448
+ npm: "@ai-sdk/openai-compatible",
2449
+ },
2450
+ })
2451
+ const result = ProviderTransform.variants(model)
2452
+ expect(result).toEqual({})
2453
+ })
2454
+
2455
+ test("glm returns empty object", () => {
2456
+ const model = createMockModel({
2457
+ id: "glm/glm-4",
2458
+ providerID: "glm",
2459
+ api: {
2460
+ id: "glm-4",
2461
+ url: "https://api.glm.com",
2462
+ npm: "@ai-sdk/openai-compatible",
2463
+ },
2464
+ })
2465
+ const result = ProviderTransform.variants(model)
2466
+ expect(result).toEqual({})
2467
+ })
2468
+
2469
+ test("mistral models with reasoning support return variants", () => {
2470
+ const model = createMockModel({
2471
+ id: "mistral/mistral-small-latest",
2472
+ providerID: "mistral",
2473
+ api: {
2474
+ id: "mistral-small-latest",
2475
+ url: "https://api.mistral.com",
2476
+ npm: "@ai-sdk/mistral",
2477
+ },
2478
+ capabilities: { reasoning: true },
2479
+ })
2480
+ const result = ProviderTransform.variants(model)
2481
+ expect(result).toEqual({
2482
+ high: { reasoningEffort: "high" },
2483
+ })
2484
+ })
2485
+
2486
+ test("mistral-medium-3.5 with reasoning returns variants", () => {
2487
+ const model = createMockModel({
2488
+ id: "mistral/mistral-medium-3.5",
2489
+ providerID: "mistral",
2490
+ api: {
2491
+ id: "mistral-medium-3.5",
2492
+ url: "https://api.mistral.com",
2493
+ npm: "@ai-sdk/mistral",
2494
+ },
2495
+ capabilities: { reasoning: true },
2496
+ })
2497
+ const result = ProviderTransform.variants(model)
2498
+ expect(result).toEqual({
2499
+ high: { reasoningEffort: "high" },
2500
+ })
2501
+ })
2502
+
2503
+ test("mistral without reasoning returns empty object", () => {
2504
+ const model = createMockModel({
2505
+ id: "mistral/mistral-large",
2506
+ providerID: "mistral",
2507
+ api: {
2508
+ id: "mistral-large-latest",
2509
+ url: "https://api.mistral.com",
2510
+ npm: "@ai-sdk/mistral",
2511
+ },
2512
+ capabilities: { reasoning: false },
2513
+ })
2514
+ const result = ProviderTransform.variants(model)
2515
+ expect(result).toEqual({})
2516
+ })
2517
+
2518
+ test("mistral large with reasoning returns empty object (only small supports reasoning)", () => {
2519
+ const model = createMockModel({
2520
+ id: "mistral/mistral-large",
2521
+ providerID: "mistral",
2522
+ api: {
2523
+ id: "mistral-large-latest",
2524
+ url: "https://api.mistral.com",
2525
+ npm: "@ai-sdk/mistral",
2526
+ },
2527
+ capabilities: { reasoning: true },
2528
+ })
2529
+ const result = ProviderTransform.variants(model)
2530
+ expect(result).toEqual({})
2531
+ })
2532
+
2533
+ describe("@openrouter/ai-sdk-provider", () => {
2534
+ test("returns empty object for non-qualifying models", () => {
2535
+ const model = createMockModel({
2536
+ id: "openrouter/test-model",
2537
+ providerID: "openrouter",
2538
+ api: {
2539
+ id: "test-model",
2540
+ url: "https://openrouter.ai",
2541
+ npm: "@openrouter/ai-sdk-provider",
2542
+ },
2543
+ })
2544
+ const result = ProviderTransform.variants(model)
2545
+ expect(result).toEqual({})
2546
+ })
2547
+
2548
+ test("gpt models return OPENAI_EFFORTS with reasoning", () => {
2549
+ const model = createMockModel({
2550
+ id: "openrouter/gpt-4",
2551
+ providerID: "openrouter",
2552
+ api: {
2553
+ id: "gpt-4",
2554
+ url: "https://openrouter.ai",
2555
+ npm: "@openrouter/ai-sdk-provider",
2556
+ },
2557
+ })
2558
+ const result = ProviderTransform.variants(model)
2559
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2560
+ expect(result.low).toEqual({ reasoning: { effort: "low" } })
2561
+ expect(result.high).toEqual({ reasoning: { effort: "high" } })
2562
+ })
2563
+
2564
+ for (const testCase of [
2565
+ { id: "openai/gpt-5.4", efforts: ["none", "low", "medium", "high", "xhigh"] },
2566
+ { id: "openai/gpt-5-pro", efforts: ["high"] },
2567
+ { id: "openai/gpt-5.5-pro", efforts: ["medium", "high", "xhigh"] },
2568
+ { id: "openai/gpt-5.2-codex", efforts: ["low", "medium", "high", "xhigh"] },
2569
+ { id: "openai/gpt-5.3-codex", efforts: ["none", "low", "medium", "high", "xhigh"] },
2570
+ { id: "openai/gpt-5.3-codex-max", efforts: ["none", "low", "medium", "high", "xhigh"] },
2571
+ { id: "openai/gpt-5-chat-latest", efforts: [] },
2572
+ { id: "openai/gpt-5.2-chat-latest", efforts: ["medium"] },
2573
+ ]) {
2574
+ test(`${testCase.id} returns supported OpenAI reasoning efforts`, () => {
2575
+ const result = ProviderTransform.variants(
2576
+ createMockModel({
2577
+ id: testCase.id,
2578
+ providerID: "openrouter",
2579
+ api: {
2580
+ id: testCase.id,
2581
+ url: "https://openrouter.ai",
2582
+ npm: "@openrouter/ai-sdk-provider",
2583
+ },
2584
+ }),
2585
+ )
2586
+ expect(Object.keys(result)).toEqual(testCase.efforts)
2587
+ })
2588
+ }
2589
+
2590
+ test("gemini-3 returns OPENAI_EFFORTS with reasoning", () => {
2591
+ const model = createMockModel({
2592
+ id: "openrouter/gemini-3-5-pro",
2593
+ providerID: "openrouter",
2594
+ api: {
2595
+ id: "gemini-3-5-pro",
2596
+ url: "https://openrouter.ai",
2597
+ npm: "@openrouter/ai-sdk-provider",
2598
+ },
2599
+ })
2600
+ const result = ProviderTransform.variants(model)
2601
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2602
+ })
2603
+
2604
+ test("grok-4 returns empty object", () => {
2605
+ const model = createMockModel({
2606
+ id: "openrouter/grok-4",
2607
+ providerID: "openrouter",
2608
+ api: {
2609
+ id: "grok-4",
2610
+ url: "https://openrouter.ai",
2611
+ npm: "@openrouter/ai-sdk-provider",
2612
+ },
2613
+ })
2614
+ const result = ProviderTransform.variants(model)
2615
+ expect(result).toEqual({})
2616
+ })
2617
+
2618
+ test("grok-3-mini returns low and high with reasoning", () => {
2619
+ const model = createMockModel({
2620
+ id: "openrouter/grok-3-mini",
2621
+ providerID: "openrouter",
2622
+ api: {
2623
+ id: "grok-3-mini",
2624
+ url: "https://openrouter.ai",
2625
+ npm: "@openrouter/ai-sdk-provider",
2626
+ },
2627
+ })
2628
+ const result = ProviderTransform.variants(model)
2629
+ expect(Object.keys(result)).toEqual(["low", "high"])
2630
+ expect(result.low).toEqual({ reasoning: { effort: "low" } })
2631
+ expect(result.high).toEqual({ reasoning: { effort: "high" } })
2632
+ })
2633
+ })
2634
+
2635
+ describe("@ai-sdk/gateway", () => {
2636
+ test("anthropic sonnet 4.6 models return adaptive thinking options", () => {
2637
+ const model = createMockModel({
2638
+ id: "anthropic/claude-sonnet-4-6",
2639
+ providerID: "gateway",
2640
+ api: {
2641
+ id: "anthropic/claude-sonnet-4-6",
2642
+ url: "https://gateway.ai",
2643
+ npm: "@ai-sdk/gateway",
2644
+ },
2645
+ })
2646
+ const result = ProviderTransform.variants(model)
2647
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2648
+ expect(result.medium).toEqual({
2649
+ thinking: {
2650
+ type: "adaptive",
2651
+ },
2652
+ effort: "medium",
2653
+ })
2654
+ })
2655
+
2656
+ test("anthropic sonnet 4.6 dot-format models return adaptive thinking options", () => {
2657
+ const model = createMockModel({
2658
+ id: "anthropic/claude-sonnet-4-6",
2659
+ providerID: "gateway",
2660
+ api: {
2661
+ id: "anthropic/claude-sonnet-4.6",
2662
+ url: "https://gateway.ai",
2663
+ npm: "@ai-sdk/gateway",
2664
+ },
2665
+ })
2666
+ const result = ProviderTransform.variants(model)
2667
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2668
+ expect(result.medium).toEqual({
2669
+ thinking: {
2670
+ type: "adaptive",
2671
+ },
2672
+ effort: "medium",
2673
+ })
2674
+ })
2675
+
2676
+ test("anthropic opus 4.6 dot-format models return adaptive thinking options", () => {
2677
+ const model = createMockModel({
2678
+ id: "anthropic/claude-opus-4-6",
2679
+ providerID: "gateway",
2680
+ api: {
2681
+ id: "anthropic/claude-opus-4.6",
2682
+ url: "https://gateway.ai",
2683
+ npm: "@ai-sdk/gateway",
2684
+ },
2685
+ })
2686
+ const result = ProviderTransform.variants(model)
2687
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2688
+ expect(result.high).toEqual({
2689
+ thinking: {
2690
+ type: "adaptive",
2691
+ },
2692
+ effort: "high",
2693
+ })
2694
+ })
2695
+
2696
+ test("anthropic opus 4.7 models return adaptive thinking options with xhigh", () => {
2697
+ const model = createMockModel({
2698
+ id: "anthropic/claude-opus-4-7",
2699
+ providerID: "gateway",
2700
+ api: {
2701
+ id: "anthropic/claude-opus-4-7",
2702
+ url: "https://gateway.ai",
2703
+ npm: "@ai-sdk/gateway",
2704
+ },
2705
+ })
2706
+ const result = ProviderTransform.variants(model)
2707
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
2708
+ expect(result.xhigh).toEqual({
2709
+ thinking: {
2710
+ type: "adaptive",
2711
+ display: "summarized",
2712
+ },
2713
+ effort: "xhigh",
2714
+ })
2715
+ expect(result.max).toEqual({
2716
+ thinking: {
2717
+ type: "adaptive",
2718
+ display: "summarized",
2719
+ },
2720
+ effort: "max",
2721
+ })
2722
+ })
2723
+
2724
+ test("anthropic opus 4.7 dot-format models return adaptive thinking options with xhigh", () => {
2725
+ const model = createMockModel({
2726
+ id: "anthropic/claude-opus-4-7",
2727
+ providerID: "gateway",
2728
+ api: {
2729
+ id: "anthropic/claude-opus-4.7",
2730
+ url: "https://gateway.ai",
2731
+ npm: "@ai-sdk/gateway",
2732
+ },
2733
+ })
2734
+ const result = ProviderTransform.variants(model)
2735
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
2736
+ })
2737
+
2738
+ test("anthropic opus 4.8 forces display summarized for adaptive reasoning", () => {
2739
+ const model = createMockModel({
2740
+ id: "anthropic/claude-opus-4-8",
2741
+ providerID: "gateway",
2742
+ api: {
2743
+ id: "anthropic/claude-opus-4-8",
2744
+ url: "https://gateway.ai",
2745
+ npm: "@ai-sdk/gateway",
2746
+ },
2747
+ })
2748
+ const result = ProviderTransform.variants(model)
2749
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
2750
+ expect(result.high).toEqual({
2751
+ thinking: {
2752
+ type: "adaptive",
2753
+ display: "summarized",
2754
+ },
2755
+ effort: "high",
2756
+ })
2757
+ })
2758
+
2759
+ test("anthropic opus 4.6 omits display so it keeps the summarized default", () => {
2760
+ const model = createMockModel({
2761
+ id: "anthropic/claude-opus-4-6",
2762
+ providerID: "gateway",
2763
+ api: {
2764
+ id: "anthropic/claude-opus-4-6",
2765
+ url: "https://gateway.ai",
2766
+ npm: "@ai-sdk/gateway",
2767
+ },
2768
+ })
2769
+ const result = ProviderTransform.variants(model)
2770
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2771
+ expect(result.high).toEqual({
2772
+ thinking: {
2773
+ type: "adaptive",
2774
+ },
2775
+ effort: "high",
2776
+ })
2777
+ })
2778
+
2779
+ test("anthropic models return anthropic thinking options", () => {
2780
+ const model = createMockModel({
2781
+ id: "anthropic/claude-sonnet-4",
2782
+ providerID: "gateway",
2783
+ api: {
2784
+ id: "anthropic/claude-sonnet-4",
2785
+ url: "https://gateway.ai",
2786
+ npm: "@ai-sdk/gateway",
2787
+ },
2788
+ })
2789
+ const result = ProviderTransform.variants(model)
2790
+ expect(Object.keys(result)).toEqual(["high", "max"])
2791
+ expect(result.high).toEqual({
2792
+ thinking: {
2793
+ type: "enabled",
2794
+ budgetTokens: 16000,
2795
+ },
2796
+ })
2797
+ expect(result.max).toEqual({
2798
+ thinking: {
2799
+ type: "enabled",
2800
+ budgetTokens: 31999,
2801
+ },
2802
+ })
2803
+ })
2804
+
2805
+ test("returns OPENAI_EFFORTS with reasoningEffort", () => {
2806
+ const model = createMockModel({
2807
+ id: "gateway/gateway-model",
2808
+ providerID: "gateway",
2809
+ api: {
2810
+ id: "gateway-model",
2811
+ url: "https://gateway.ai",
2812
+ npm: "@ai-sdk/gateway",
2813
+ },
2814
+ })
2815
+ const result = ProviderTransform.variants(model)
2816
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2817
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2818
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2819
+ })
2820
+
2821
+ for (const testCase of [
2822
+ { id: "openai/gpt-5-5", efforts: ["none", "low", "medium", "high", "xhigh"] },
2823
+ { id: "openai/gpt-5-pro", efforts: ["high"] },
2824
+ { id: "openai/gpt-5-5-pro", efforts: ["medium", "high", "xhigh"] },
2825
+ { id: "openai/gpt-5-2-codex", efforts: ["low", "medium", "high", "xhigh"] },
2826
+ { id: "openai/gpt-5-3-codex", efforts: ["none", "low", "medium", "high", "xhigh"] },
2827
+ { id: "openai/gpt-5-3-codex-max", efforts: ["none", "low", "medium", "high", "xhigh"] },
2828
+ { id: "openai/gpt-5-chat-latest", efforts: [] },
2829
+ { id: "openai/gpt-5-2-chat-latest", efforts: ["medium"] },
2830
+ ]) {
2831
+ test(`${testCase.id} returns supported OpenAI reasoning efforts`, () => {
2832
+ const result = ProviderTransform.variants(
2833
+ createMockModel({
2834
+ id: testCase.id,
2835
+ providerID: "gateway",
2836
+ api: {
2837
+ id: testCase.id,
2838
+ url: "https://gateway.ai",
2839
+ npm: "@ai-sdk/gateway",
2840
+ },
2841
+ }),
2842
+ )
2843
+ expect(Object.keys(result)).toEqual(testCase.efforts)
2844
+ })
2845
+ }
2846
+ })
2847
+
2848
+ describe("@ai-sdk/github-copilot", () => {
2849
+ test("standard models return low, medium, high", () => {
2850
+ const model = createMockModel({
2851
+ id: "gpt-4.5",
2852
+ providerID: "github-copilot",
2853
+ api: {
2854
+ id: "gpt-4.5",
2855
+ url: "https://api.githubcopilot.com",
2856
+ npm: "@ai-sdk/github-copilot",
2857
+ },
2858
+ })
2859
+ const result = ProviderTransform.variants(model)
2860
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2861
+ expect(result.low).toEqual({
2862
+ reasoningEffort: "low",
2863
+ reasoningSummary: "auto",
2864
+ include: ["reasoning.encrypted_content"],
2865
+ })
2866
+ })
2867
+
2868
+ test("gpt-5.1-codex-max includes xhigh", () => {
2869
+ const model = createMockModel({
2870
+ id: "gpt-5.1-codex-max",
2871
+ providerID: "github-copilot",
2872
+ api: {
2873
+ id: "gpt-5.1-codex-max",
2874
+ url: "https://api.githubcopilot.com",
2875
+ npm: "@ai-sdk/github-copilot",
2876
+ },
2877
+ })
2878
+ const result = ProviderTransform.variants(model)
2879
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2880
+ })
2881
+
2882
+ test("gpt-5.1-codex-mini does not include xhigh", () => {
2883
+ const model = createMockModel({
2884
+ id: "gpt-5.1-codex-mini",
2885
+ providerID: "github-copilot",
2886
+ api: {
2887
+ id: "gpt-5.1-codex-mini",
2888
+ url: "https://api.githubcopilot.com",
2889
+ npm: "@ai-sdk/github-copilot",
2890
+ },
2891
+ })
2892
+ const result = ProviderTransform.variants(model)
2893
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2894
+ })
2895
+
2896
+ test("gpt-5.1-codex does not include xhigh", () => {
2897
+ const model = createMockModel({
2898
+ id: "gpt-5.1-codex",
2899
+ providerID: "github-copilot",
2900
+ api: {
2901
+ id: "gpt-5.1-codex",
2902
+ url: "https://api.githubcopilot.com",
2903
+ npm: "@ai-sdk/github-copilot",
2904
+ },
2905
+ })
2906
+ const result = ProviderTransform.variants(model)
2907
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2908
+ })
2909
+
2910
+ test("gpt-5.2 includes xhigh", () => {
2911
+ const model = createMockModel({
2912
+ id: "gpt-5.2",
2913
+ providerID: "github-copilot",
2914
+ api: {
2915
+ id: "gpt-5.2",
2916
+ url: "https://api.githubcopilot.com",
2917
+ npm: "@ai-sdk/github-copilot",
2918
+ },
2919
+ })
2920
+ const result = ProviderTransform.variants(model)
2921
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2922
+ expect(result.xhigh).toEqual({
2923
+ reasoningEffort: "xhigh",
2924
+ reasoningSummary: "auto",
2925
+ include: ["reasoning.encrypted_content"],
2926
+ })
2927
+ })
2928
+
2929
+ test("gpt-5.2-codex includes xhigh", () => {
2930
+ const model = createMockModel({
2931
+ id: "gpt-5.2-codex",
2932
+ providerID: "github-copilot",
2933
+ api: {
2934
+ id: "gpt-5.2-codex",
2935
+ url: "https://api.githubcopilot.com",
2936
+ npm: "@ai-sdk/github-copilot",
2937
+ },
2938
+ })
2939
+ const result = ProviderTransform.variants(model)
2940
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2941
+ })
2942
+
2943
+ test("gpt-5.3-codex includes xhigh", () => {
2944
+ const model = createMockModel({
2945
+ id: "gpt-5.3-codex",
2946
+ providerID: "github-copilot",
2947
+ api: {
2948
+ id: "gpt-5.3-codex",
2949
+ url: "https://api.githubcopilot.com",
2950
+ npm: "@ai-sdk/github-copilot",
2951
+ },
2952
+ })
2953
+ const result = ProviderTransform.variants(model)
2954
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2955
+ })
2956
+
2957
+ test("gpt-5.4 includes xhigh", () => {
2958
+ const model = createMockModel({
2959
+ id: "gpt-5.4",
2960
+ release_date: "2026-03-05",
2961
+ providerID: "github-copilot",
2962
+ api: {
2963
+ id: "gpt-5.4",
2964
+ url: "https://api.githubcopilot.com",
2965
+ npm: "@ai-sdk/github-copilot",
2966
+ },
2967
+ })
2968
+ const result = ProviderTransform.variants(model)
2969
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2970
+ })
2971
+ })
2972
+
2973
+ describe("@ai-sdk/cerebras", () => {
2974
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
2975
+ const model = createMockModel({
2976
+ id: "cerebras/llama-4",
2977
+ providerID: "cerebras",
2978
+ api: {
2979
+ id: "llama-4-sc",
2980
+ url: "https://api.cerebras.ai",
2981
+ npm: "@ai-sdk/cerebras",
2982
+ },
2983
+ })
2984
+ const result = ProviderTransform.variants(model)
2985
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2986
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2987
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2988
+ })
2989
+ })
2990
+
2991
+ describe("@ai-sdk/togetherai", () => {
2992
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
2993
+ const model = createMockModel({
2994
+ id: "togetherai/llama-4",
2995
+ providerID: "togetherai",
2996
+ api: {
2997
+ id: "llama-4-sc",
2998
+ url: "https://api.togetherai.com",
2999
+ npm: "@ai-sdk/togetherai",
3000
+ },
3001
+ })
3002
+ const result = ProviderTransform.variants(model)
3003
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3004
+ expect(result.low).toEqual({ reasoningEffort: "low" })
3005
+ expect(result.high).toEqual({ reasoningEffort: "high" })
3006
+ })
3007
+ })
3008
+
3009
+ describe("@ai-sdk/xai", () => {
3010
+ test("grok-3 returns empty object", () => {
3011
+ const model = createMockModel({
3012
+ id: "xai/grok-3",
3013
+ providerID: "xai",
3014
+ api: {
3015
+ id: "grok-3",
3016
+ url: "https://api.x.ai",
3017
+ npm: "@ai-sdk/xai",
3018
+ },
3019
+ })
3020
+ const result = ProviderTransform.variants(model)
3021
+ expect(result).toEqual({})
3022
+ })
3023
+
3024
+ test("grok-3-mini returns low and high with reasoningEffort", () => {
3025
+ const model = createMockModel({
3026
+ id: "xai/grok-3-mini",
3027
+ providerID: "xai",
3028
+ api: {
3029
+ id: "grok-3-mini",
3030
+ url: "https://api.x.ai",
3031
+ npm: "@ai-sdk/xai",
3032
+ },
3033
+ })
3034
+ const result = ProviderTransform.variants(model)
3035
+ expect(Object.keys(result)).toEqual(["low", "high"])
3036
+ expect(result.low).toEqual({ reasoningEffort: "low" })
3037
+ expect(result.high).toEqual({ reasoningEffort: "high" })
3038
+ })
3039
+ })
3040
+
3041
+ describe("@ai-sdk/deepinfra", () => {
3042
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
3043
+ const model = createMockModel({
3044
+ id: "deepinfra/llama-4",
3045
+ providerID: "deepinfra",
3046
+ api: {
3047
+ id: "llama-4-sc",
3048
+ url: "https://api.deepinfra.com",
3049
+ npm: "@ai-sdk/deepinfra",
3050
+ },
3051
+ })
3052
+ const result = ProviderTransform.variants(model)
3053
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3054
+ expect(result.low).toEqual({ reasoningEffort: "low" })
3055
+ expect(result.high).toEqual({ reasoningEffort: "high" })
3056
+ })
3057
+ })
3058
+
3059
+ describe("@ai-sdk/openai-compatible", () => {
3060
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
3061
+ const model = createMockModel({
3062
+ id: "custom-provider/custom-model",
3063
+ providerID: "custom-provider",
3064
+ api: {
3065
+ id: "custom-model",
3066
+ url: "https://api.custom.com",
3067
+ npm: "@ai-sdk/openai-compatible",
3068
+ },
3069
+ })
3070
+ const result = ProviderTransform.variants(model)
3071
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3072
+ expect(result.low).toEqual({ reasoningEffort: "low" })
3073
+ expect(result.high).toEqual({ reasoningEffort: "high" })
3074
+ })
3075
+ })
3076
+
3077
+ describe("@ai-sdk/azure", () => {
3078
+ test("o1-mini returns empty object", () => {
3079
+ const model = createMockModel({
3080
+ id: "o1-mini",
3081
+ providerID: "azure",
3082
+ api: {
3083
+ id: "o1-mini",
3084
+ url: "https://azure.com",
3085
+ npm: "@ai-sdk/azure",
3086
+ },
3087
+ })
3088
+ const result = ProviderTransform.variants(model)
3089
+ expect(result).toEqual({})
3090
+ })
3091
+
3092
+ test("standard azure models return custom efforts with reasoningSummary", () => {
3093
+ const model = createMockModel({
3094
+ id: "o1",
3095
+ providerID: "azure",
3096
+ api: {
3097
+ id: "o1",
3098
+ url: "https://azure.com",
3099
+ npm: "@ai-sdk/azure",
3100
+ },
3101
+ })
3102
+ const result = ProviderTransform.variants(model)
3103
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3104
+ expect(result.low).toEqual({
3105
+ reasoningEffort: "low",
3106
+ reasoningSummary: "auto",
3107
+ include: ["reasoning.encrypted_content"],
3108
+ })
3109
+ })
3110
+
3111
+ test("gpt-5 adds minimal effort", () => {
3112
+ const model = createMockModel({
3113
+ id: "gpt-5",
3114
+ providerID: "azure",
3115
+ api: {
3116
+ id: "gpt-5",
3117
+ url: "https://azure.com",
3118
+ npm: "@ai-sdk/azure",
3119
+ },
3120
+ })
3121
+ const result = ProviderTransform.variants(model)
3122
+ expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
3123
+ })
3124
+
3125
+ for (const testCase of [
3126
+ { id: "gpt-5-1", efforts: ["none", "low", "medium", "high"] },
3127
+ { id: "gpt-5-4", efforts: ["none", "low", "medium", "high", "xhigh"] },
3128
+ { id: "gpt-5.4", efforts: ["none", "low", "medium", "high", "xhigh"] },
3129
+ { id: "gpt-5-5", efforts: ["none", "low", "medium", "high", "xhigh"] },
3130
+ ]) {
3131
+ test(`${testCase.id} returns supported Azure reasoning efforts`, () => {
3132
+ const result = ProviderTransform.variants(
3133
+ createMockModel({
3134
+ id: testCase.id,
3135
+ providerID: "azure",
3136
+ api: {
3137
+ id: testCase.id,
3138
+ url: "https://azure.com",
3139
+ npm: "@ai-sdk/azure",
3140
+ },
3141
+ }),
3142
+ )
3143
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3144
+ })
3145
+ }
3146
+ })
3147
+
3148
+ describe("@ai-sdk/openai", () => {
3149
+ test("gpt-5-pro returns only high effort", () => {
3150
+ const model = createMockModel({
3151
+ id: "gpt-5-pro",
3152
+ providerID: "openai",
3153
+ api: {
3154
+ id: "gpt-5-pro",
3155
+ url: "https://api.openai.com",
3156
+ npm: "@ai-sdk/openai",
3157
+ },
3158
+ })
3159
+ const result = ProviderTransform.variants(model)
3160
+ expect(Object.keys(result)).toEqual(["high"])
3161
+ })
3162
+
3163
+ test("standard openai models return custom efforts with reasoningSummary", () => {
3164
+ const model = createMockModel({
3165
+ id: "gpt-5",
3166
+ providerID: "openai",
3167
+ api: {
3168
+ id: "gpt-5",
3169
+ url: "https://api.openai.com",
3170
+ npm: "@ai-sdk/openai",
3171
+ },
3172
+ release_date: "2024-06-01",
3173
+ })
3174
+ const result = ProviderTransform.variants(model)
3175
+ expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
3176
+ expect(result.low).toEqual({
3177
+ reasoningEffort: "low",
3178
+ reasoningSummary: "auto",
3179
+ include: ["reasoning.encrypted_content"],
3180
+ })
3181
+ })
3182
+
3183
+ test("models after 2025-11-13 include 'none' effort", () => {
3184
+ const model = createMockModel({
3185
+ id: "gpt-5-nano",
3186
+ providerID: "openai",
3187
+ api: {
3188
+ id: "gpt-5-nano",
3189
+ url: "https://api.openai.com",
3190
+ npm: "@ai-sdk/openai",
3191
+ },
3192
+ release_date: "2025-11-14",
3193
+ })
3194
+ const result = ProviderTransform.variants(model)
3195
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high"])
3196
+ })
3197
+
3198
+ test("models after 2025-12-04 include 'xhigh' effort", () => {
3199
+ const model = createMockModel({
3200
+ id: "openai/gpt-5-reasoning",
3201
+ providerID: "openai",
3202
+ api: {
3203
+ id: "gpt-5-reasoning",
3204
+ url: "https://api.openai.com",
3205
+ npm: "@ai-sdk/openai",
3206
+ },
3207
+ release_date: "2025-12-05",
3208
+ })
3209
+ const result = ProviderTransform.variants(model)
3210
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
3211
+ })
3212
+
3213
+ for (const testCase of [
3214
+ { id: "o1", releaseDate: "2024-12-17", efforts: ["low", "medium", "high"] },
3215
+ { id: "o1-pro", releaseDate: "2025-03-19", efforts: ["low", "medium", "high"] },
3216
+ { id: "o3", releaseDate: "2025-04-16", efforts: ["low", "medium", "high"] },
3217
+ { id: "o3-mini", releaseDate: "2025-01-31", efforts: ["low", "medium", "high"] },
3218
+ { id: "o3-pro", releaseDate: "2025-06-10", efforts: ["low", "medium", "high"] },
3219
+ { id: "o4-mini", releaseDate: "2025-04-16", efforts: ["low", "medium", "high"] },
3220
+ { id: "o3-deep-research", releaseDate: "2025-06-26", efforts: ["medium"] },
3221
+ { id: "o4-mini-deep-research", releaseDate: "2025-06-26", efforts: ["medium"] },
3222
+ { id: "gpt-5.1", releaseDate: "2025-11-13", efforts: ["none", "low", "medium", "high"] },
3223
+ { id: "gpt-5.4", releaseDate: "2026-03-05", efforts: ["none", "low", "medium", "high", "xhigh"] },
3224
+ {
3225
+ id: "gpt-5.5",
3226
+ modelID: "gpt-5-5",
3227
+ releaseDate: "2026-04-23",
3228
+ efforts: ["none", "low", "medium", "high", "xhigh"],
3229
+ },
3230
+ { id: "gpt-5.4-pro", releaseDate: "2026-03-05", efforts: ["medium", "high", "xhigh"] },
3231
+ { id: "gpt-5.5-pro", releaseDate: "2026-04-23", efforts: ["medium", "high", "xhigh"] },
3232
+ { id: "gpt-5-codex", releaseDate: "2025-09-23", efforts: ["low", "medium", "high"] },
3233
+ { id: "gpt-5.1-codex", releaseDate: "2025-11-13", efforts: ["low", "medium", "high"] },
3234
+ { id: "gpt-5.1-codex-max", releaseDate: "2025-11-13", efforts: ["low", "medium", "high", "xhigh"] },
3235
+ { id: "gpt-5.2-codex", releaseDate: "2025-12-11", efforts: ["low", "medium", "high", "xhigh"] },
3236
+ { id: "gpt-5.3-codex", releaseDate: "2026-01-22", efforts: ["none", "low", "medium", "high", "xhigh"] },
3237
+ { id: "gpt-5.3-codex-max", releaseDate: "2026-01-22", efforts: ["none", "low", "medium", "high", "xhigh"] },
3238
+ { id: "gpt-5-chat-latest", releaseDate: "2025-08-07", efforts: [] },
3239
+ { id: "gpt-5.1-chat-latest", releaseDate: "2025-11-13", efforts: ["medium"] },
3240
+ { id: "gpt-5.2-chat-latest", releaseDate: "2025-12-11", efforts: ["medium"] },
3241
+ ]) {
3242
+ test(`${testCase.id} returns supported reasoning efforts`, () => {
3243
+ const result = ProviderTransform.variants(
3244
+ createMockModel({
3245
+ id: testCase.modelID ?? testCase.id,
3246
+ providerID: "openai",
3247
+ api: {
3248
+ id: testCase.id,
3249
+ url: "https://api.openai.com",
3250
+ npm: "@ai-sdk/openai",
3251
+ },
3252
+ release_date: testCase.releaseDate,
3253
+ }),
3254
+ )
3255
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3256
+ })
3257
+ }
3258
+
3259
+ test("gpt-50 (lookalike) does not get gpt-5 family treatment", () => {
3260
+ const model = createMockModel({
3261
+ id: "gpt-50",
3262
+ providerID: "openai",
3263
+ api: {
3264
+ id: "gpt-50",
3265
+ url: "https://api.openai.com",
3266
+ npm: "@ai-sdk/openai",
3267
+ },
3268
+ release_date: "2024-01-01",
3269
+ })
3270
+ const result = ProviderTransform.variants(model)
3271
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3272
+ })
3273
+ })
3274
+
3275
+ describe("@ai-sdk/amazon-bedrock/mantle", () => {
3276
+ test("gpt-5.5 returns OpenAI-style reasoning variants", () => {
3277
+ const model = createMockModel({
3278
+ id: "openai.gpt-5.5",
3279
+ providerID: "amazon-bedrock",
3280
+ api: {
3281
+ id: "openai.gpt-5.5",
3282
+ url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
3283
+ npm: "@ai-sdk/amazon-bedrock/mantle",
3284
+ },
3285
+ release_date: "2026-04-23",
3286
+ })
3287
+ const result = ProviderTransform.variants(model)
3288
+ expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"])
3289
+ expect(result.medium).toEqual({
3290
+ reasoningEffort: "medium",
3291
+ reasoningSummary: "auto",
3292
+ include: ["reasoning.encrypted_content"],
3293
+ })
3294
+ })
3295
+ })
3296
+
3297
+ describe("@ai-sdk/anthropic", () => {
3298
+ for (const testCase of [
3299
+ {
3300
+ name: "opus 4.5",
3301
+ apiIds: ["claude-opus-4-5-20251101", "claude-opus-4.5-20251101"],
3302
+ efforts: ["low", "medium", "high"],
3303
+ expectedHigh: { effort: "high" },
3304
+ },
3305
+ {
3306
+ name: "sonnet 4.6",
3307
+ apiIds: ["claude-sonnet-4-6", "claude-sonnet-4.6"],
3308
+ efforts: ["low", "medium", "high", "max"],
3309
+ expectedHigh: { thinking: { type: "adaptive" }, effort: "high" },
3310
+ },
3311
+ {
3312
+ name: "opus 4.6",
3313
+ apiIds: ["claude-opus-4-6", "claude-opus-4.6"],
3314
+ efforts: ["low", "medium", "high", "max"],
3315
+ expectedHigh: { thinking: { type: "adaptive" }, effort: "high" },
3316
+ },
3317
+ {
3318
+ name: "opus 4.7",
3319
+ apiIds: ["claude-opus-4-7", "claude-opus-4.7"],
3320
+ efforts: ["low", "medium", "high", "xhigh", "max"],
3321
+ expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
3322
+ },
3323
+ {
3324
+ name: "opus 4.8",
3325
+ apiIds: ["claude-opus-4-8", "claude-opus-4.8"],
3326
+ efforts: ["low", "medium", "high", "xhigh", "max"],
3327
+ expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
3328
+ },
3329
+ ]) {
3330
+ for (const apiId of testCase.apiIds) {
3331
+ test(`${testCase.name} ${apiId} returns supported reasoning efforts`, () => {
3332
+ const result = ProviderTransform.variants(
3333
+ createMockModel({
3334
+ id: `anthropic/${apiId}`,
3335
+ providerID: "anthropic",
3336
+ api: {
3337
+ id: apiId,
3338
+ url: "https://api.anthropic.com",
3339
+ npm: "@ai-sdk/anthropic",
3340
+ },
3341
+ }),
3342
+ )
3343
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3344
+ expect(result.high).toEqual(testCase.expectedHigh)
3345
+ })
3346
+ }
3347
+ }
3348
+
3349
+ test("github copilot opus 4.7 returns only medium reasoning effort", () => {
3350
+ const model = createMockModel({
3351
+ id: "claude-opus-4.7",
3352
+ providerID: "github-copilot",
3353
+ api: {
3354
+ id: "claude-opus-4.7",
3355
+ url: "https://api.githubcopilot.com/v1",
3356
+ npm: "@ai-sdk/anthropic",
3357
+ },
3358
+ })
3359
+ const result = ProviderTransform.variants(model)
3360
+ expect(result).toEqual({
3361
+ medium: {
3362
+ thinking: {
3363
+ type: "adaptive",
3364
+ display: "summarized",
3365
+ },
3366
+ effort: "medium",
3367
+ },
3368
+ })
3369
+ })
3370
+
3371
+ test("returns high and max with thinking config", () => {
3372
+ const model = createMockModel({
3373
+ id: "anthropic/claude-4",
3374
+ providerID: "anthropic",
3375
+ api: {
3376
+ id: "claude-4",
3377
+ url: "https://api.anthropic.com",
3378
+ npm: "@ai-sdk/anthropic",
3379
+ },
3380
+ })
3381
+ const result = ProviderTransform.variants(model)
3382
+ expect(Object.keys(result)).toEqual(["high", "max"])
3383
+ expect(result.high).toEqual({
3384
+ thinking: {
3385
+ type: "enabled",
3386
+ budgetTokens: 16000,
3387
+ },
3388
+ })
3389
+ expect(result.max).toEqual({
3390
+ thinking: {
3391
+ type: "enabled",
3392
+ budgetTokens: 31999,
3393
+ },
3394
+ })
3395
+ })
3396
+ })
3397
+
3398
+ describe("@ai-sdk/google-vertex/anthropic", () => {
3399
+ test("opus 4.8 uses adaptive reasoning for Vertex model IDs", () => {
3400
+ const result = ProviderTransform.variants(
3401
+ createMockModel({
3402
+ id: "google-vertex-anthropic/claude-opus-4-8@default",
3403
+ providerID: "google-vertex-anthropic",
3404
+ api: {
3405
+ id: "claude-opus-4-8@default",
3406
+ url: "https://us-central1-aiplatform.googleapis.com",
3407
+ npm: "@ai-sdk/google-vertex/anthropic",
3408
+ },
3409
+ }),
3410
+ )
3411
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
3412
+ expect(result.high).toEqual({
3413
+ thinking: {
3414
+ type: "adaptive",
3415
+ display: "summarized",
3416
+ },
3417
+ effort: "high",
3418
+ })
3419
+ })
3420
+ })
3421
+
3422
+ describe("@ai-sdk/amazon-bedrock", () => {
3423
+ test("anthropic sonnet 4.6 returns adaptive reasoning options", () => {
3424
+ const model = createMockModel({
3425
+ id: "bedrock/anthropic-claude-sonnet-4-6",
3426
+ providerID: "bedrock",
3427
+ api: {
3428
+ id: "anthropic.claude-sonnet-4-6",
3429
+ url: "https://bedrock.amazonaws.com",
3430
+ npm: "@ai-sdk/amazon-bedrock",
3431
+ },
3432
+ })
3433
+ const result = ProviderTransform.variants(model)
3434
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
3435
+ expect(result.max).toEqual({
3436
+ reasoningConfig: {
3437
+ type: "adaptive",
3438
+ maxReasoningEffort: "max",
3439
+ },
3440
+ })
3441
+ })
3442
+
3443
+ test("anthropic opus 4.7 returns adaptive reasoning options with xhigh", () => {
3444
+ const model = createMockModel({
3445
+ id: "bedrock/anthropic-claude-opus-4-7",
3446
+ providerID: "bedrock",
3447
+ api: {
3448
+ id: "anthropic.claude-opus-4-7",
3449
+ url: "https://bedrock.amazonaws.com",
3450
+ npm: "@ai-sdk/amazon-bedrock",
3451
+ },
3452
+ })
3453
+ const result = ProviderTransform.variants(model)
3454
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
3455
+ expect(result.xhigh).toEqual({
3456
+ reasoningConfig: {
3457
+ type: "adaptive",
3458
+ maxReasoningEffort: "xhigh",
3459
+ display: "summarized",
3460
+ },
3461
+ })
3462
+ expect(result.max).toEqual({
3463
+ reasoningConfig: {
3464
+ type: "adaptive",
3465
+ maxReasoningEffort: "max",
3466
+ display: "summarized",
3467
+ },
3468
+ })
3469
+ })
3470
+
3471
+ test("anthropic opus 4.8 returns adaptive reasoning options with xhigh", () => {
3472
+ const result = ProviderTransform.variants(
3473
+ createMockModel({
3474
+ id: "bedrock/anthropic-claude-opus-4.8",
3475
+ providerID: "bedrock",
3476
+ api: {
3477
+ id: "anthropic.claude-opus-4.8",
3478
+ url: "https://bedrock.amazonaws.com",
3479
+ npm: "@ai-sdk/amazon-bedrock",
3480
+ },
3481
+ }),
3482
+ )
3483
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
3484
+ expect(result.high).toEqual({
3485
+ reasoningConfig: {
3486
+ type: "adaptive",
3487
+ maxReasoningEffort: "high",
3488
+ display: "summarized",
3489
+ },
3490
+ })
3491
+ })
3492
+
3493
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningConfig", () => {
3494
+ const model = createMockModel({
3495
+ id: "bedrock/llama-4",
3496
+ providerID: "bedrock",
3497
+ api: {
3498
+ id: "llama-4-sc",
3499
+ url: "https://bedrock.amazonaws.com",
3500
+ npm: "@ai-sdk/amazon-bedrock",
3501
+ },
3502
+ })
3503
+ const result = ProviderTransform.variants(model)
3504
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3505
+ expect(result.low).toEqual({
3506
+ reasoningConfig: {
3507
+ type: "enabled",
3508
+ maxReasoningEffort: "low",
3509
+ },
3510
+ })
3511
+ })
3512
+ })
3513
+
3514
+ for (const provider of [
3515
+ { name: "@ai-sdk/google", providerID: "google", url: "https://generativelanguage.googleapis.com" },
3516
+ { name: "@ai-sdk/google-vertex", providerID: "google-vertex", url: "https://vertexai.googleapis.com" },
3517
+ ]) {
3518
+ describe(provider.name, () => {
3519
+ for (const testCase of [
3520
+ {
3521
+ apiId: "gemini-2.5-pro",
3522
+ efforts: ["high", "max"],
3523
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16_000 } },
3524
+ expectedMax: { thinkingConfig: { includeThoughts: true, thinkingBudget: 32_768 } },
3525
+ },
3526
+ {
3527
+ apiId: "gemini-2.5-flash",
3528
+ efforts: ["high", "max"],
3529
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16_000 } },
3530
+ expectedMax: { thinkingConfig: { includeThoughts: true, thinkingBudget: 24_576 } },
3531
+ },
3532
+ {
3533
+ apiId: "gemini-3-pro-preview",
3534
+ efforts: ["low", "medium", "high"],
3535
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3536
+ },
3537
+ {
3538
+ apiId: "gemini-3.1-pro-preview",
3539
+ efforts: ["low", "medium", "high"],
3540
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3541
+ },
3542
+ {
3543
+ apiId: "gemini-3-flash-preview",
3544
+ efforts: ["minimal", "low", "medium", "high"],
3545
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3546
+ },
3547
+ {
3548
+ apiId: "gemini-3.1-flash-lite",
3549
+ efforts: ["minimal", "low", "medium", "high"],
3550
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3551
+ },
3552
+ {
3553
+ apiId: "gemini-3.1-flash-image-preview",
3554
+ efforts: ["minimal", "high"],
3555
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3556
+ },
3557
+ {
3558
+ apiId: "gemini-3-pro-image-preview",
3559
+ efforts: ["high"],
3560
+ expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3561
+ },
3562
+ ]) {
3563
+ test(`${testCase.apiId} returns supported thinking controls`, () => {
3564
+ const result = ProviderTransform.variants(
3565
+ createMockModel({
3566
+ id: `${provider.providerID}/${testCase.apiId}`,
3567
+ providerID: provider.providerID,
3568
+ api: {
3569
+ id: testCase.apiId,
3570
+ url: provider.url,
3571
+ npm: provider.name,
3572
+ },
3573
+ }),
3574
+ )
3575
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3576
+ expect(result.high).toEqual(testCase.expectedHigh)
3577
+ if (testCase.expectedMax) expect(result.max).toEqual(testCase.expectedMax)
3578
+ })
3579
+ }
3580
+ })
3581
+ }
3582
+
3583
+ describe("@ai-sdk/cohere", () => {
3584
+ test("returns empty object", () => {
3585
+ const model = createMockModel({
3586
+ id: "cohere/command-r",
3587
+ providerID: "cohere",
3588
+ api: {
3589
+ id: "command-r",
3590
+ url: "https://api.cohere.com",
3591
+ npm: "@ai-sdk/cohere",
3592
+ },
3593
+ })
3594
+ const result = ProviderTransform.variants(model)
3595
+ expect(result).toEqual({})
3596
+ })
3597
+ })
3598
+
3599
+ describe("@ai-sdk/groq", () => {
3600
+ test("returns none and WIDELY_SUPPORTED_EFFORTS with thinkingLevel", () => {
3601
+ const model = createMockModel({
3602
+ id: "groq/llama-4",
3603
+ providerID: "groq",
3604
+ api: {
3605
+ id: "llama-4-sc",
3606
+ url: "https://api.groq.com",
3607
+ npm: "@ai-sdk/groq",
3608
+ },
3609
+ })
3610
+ const result = ProviderTransform.variants(model)
3611
+ expect(Object.keys(result)).toEqual(["none", "low", "medium", "high"])
3612
+ expect(result.none).toEqual({
3613
+ reasoningEffort: "none",
3614
+ })
3615
+ expect(result.low).toEqual({
3616
+ reasoningEffort: "low",
3617
+ })
3618
+ })
3619
+ })
3620
+
3621
+ describe("@ai-sdk/perplexity", () => {
3622
+ test("returns empty object", () => {
3623
+ const model = createMockModel({
3624
+ id: "perplexity/sonar-plus",
3625
+ providerID: "perplexity",
3626
+ api: {
3627
+ id: "sonar-plus",
3628
+ url: "https://api.perplexity.ai",
3629
+ npm: "@ai-sdk/perplexity",
3630
+ },
3631
+ })
3632
+ const result = ProviderTransform.variants(model)
3633
+ expect(result).toEqual({})
3634
+ })
3635
+ })
3636
+
3637
+ describe("@jerome-benoit/sap-ai-provider-v2", () => {
3638
+ const sapModel = (apiId: string, releaseDate = "2024-01-01") =>
3639
+ createMockModel({
3640
+ id: `sap-ai-core/${apiId}`,
3641
+ providerID: "sap-ai-core",
3642
+ api: {
3643
+ id: apiId,
3644
+ url: "https://api.ai.sap",
3645
+ npm: "@jerome-benoit/sap-ai-provider-v2",
3646
+ },
3647
+ release_date: releaseDate,
3648
+ })
3649
+
3650
+ for (const testCase of [
3651
+ {
3652
+ name: "sonnet 4.6",
3653
+ apiIds: ["anthropic--claude-sonnet-4-6"],
3654
+ efforts: ["low", "medium", "high", "max"],
3655
+ thinking: { type: "adaptive" },
3656
+ },
3657
+ {
3658
+ name: "opus 4.6",
3659
+ apiIds: ["anthropic--claude-4.6-opus", "anthropic--claude-4-6-opus"],
3660
+ efforts: ["low", "medium", "high", "max"],
3661
+ thinking: { type: "adaptive" },
3662
+ },
3663
+ {
3664
+ name: "opus 4.7",
3665
+ apiIds: ["anthropic--claude-4.7-opus", "anthropic--claude-4-7-opus"],
3666
+ efforts: ["low", "medium", "high", "xhigh", "max"],
3667
+ thinking: { type: "adaptive", display: "summarized" },
3668
+ },
3669
+ {
3670
+ name: "opus 4.8",
3671
+ apiIds: ["anthropic--claude-4.8-opus", "anthropic--claude-4-8-opus"],
3672
+ efforts: ["low", "medium", "high", "xhigh", "max"],
3673
+ thinking: { type: "adaptive", display: "summarized" },
3674
+ },
3675
+ ]) {
3676
+ for (const apiId of testCase.apiIds) {
3677
+ test(`${testCase.name} ${apiId} returns adaptive thinking variants under modelParams`, () => {
3678
+ const result = ProviderTransform.variants(sapModel(apiId))
3679
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3680
+ for (const effort of testCase.efforts) {
3681
+ expect(result[effort]).toEqual({
3682
+ modelParams: {
3683
+ thinking: testCase.thinking,
3684
+ output_config: { effort },
3685
+ },
3686
+ })
3687
+ }
3688
+ })
3689
+ }
3690
+ }
3691
+
3692
+ for (const apiId of ["anthropic--claude-sonnet-4", "anthropic--claude-4.5-opus"]) {
3693
+ test(`${apiId} returns budget_tokens variants under modelParams`, () => {
3694
+ const result = ProviderTransform.variants(sapModel(apiId))
3695
+ expect(Object.keys(result)).toEqual(["high", "max"])
3696
+ expect(result.high).toEqual({
3697
+ modelParams: { thinking: { type: "enabled", budget_tokens: 16000 } },
3698
+ })
3699
+ expect(result.max).toEqual({
3700
+ modelParams: { thinking: { type: "enabled", budget_tokens: 31999 } },
3701
+ })
3702
+ })
3703
+ }
3704
+
3705
+ for (const testCase of [
3706
+ { apiId: "gemini-2.5-pro", maxBudget: 32768 },
3707
+ { apiId: "gemini-2.5-flash", maxBudget: 24576 },
3708
+ ]) {
3709
+ test(`${testCase.apiId} returns thinkingConfig variants under modelParams`, () => {
3710
+ const result = ProviderTransform.variants(sapModel(testCase.apiId))
3711
+ expect(Object.keys(result)).toEqual(["high", "max"])
3712
+ expect(result.high).toEqual({
3713
+ modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
3714
+ })
3715
+ expect(result.max).toEqual({
3716
+ modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: testCase.maxBudget } },
3717
+ })
3718
+ })
3719
+ }
3720
+
3721
+ for (const testCase of [
3722
+ { apiId: "gpt-5", releaseDate: "2025-08-07", efforts: ["minimal", "low", "medium", "high"] },
3723
+ { apiId: "gpt-5-mini", releaseDate: "2025-08-07", efforts: ["minimal", "low", "medium", "high"] },
3724
+ { apiId: "gpt-5-nano", releaseDate: "2025-08-07", efforts: ["minimal", "low", "medium", "high"] },
3725
+ { apiId: "gpt-5.4", releaseDate: "2026-01-15", efforts: ["none", "low", "medium", "high", "xhigh"] },
3726
+ { apiId: "azure-openai--o3-mini", releaseDate: "2024-01-01", efforts: ["low", "medium", "high"] },
3727
+ ]) {
3728
+ test(`${testCase.apiId} returns reasoning_effort variants under modelParams`, () => {
3729
+ const result = ProviderTransform.variants(sapModel(testCase.apiId, testCase.releaseDate))
3730
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3731
+ for (const effort of testCase.efforts) {
3732
+ expect(result[effort]).toEqual({ modelParams: { reasoning_effort: effort } })
3733
+ }
3734
+ })
3735
+ }
3736
+
3737
+ for (const apiId of [
3738
+ "gemini-3.1-flash-lite",
3739
+ "cohere--command-a-reasoning",
3740
+ "sonar-deep-research",
3741
+ "aws--llama-opus-4.7-fake",
3742
+ ]) {
3743
+ test(`${apiId} falls through to harmonized reasoning_effort fallback`, () => {
3744
+ const result = ProviderTransform.variants(sapModel(apiId))
3745
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
3746
+ for (const effort of ["low", "medium", "high"]) {
3747
+ expect(result[effort]).toEqual({ modelParams: { reasoning_effort: effort } })
3748
+ }
3749
+ })
3750
+ }
3751
+ })
3752
+
3753
+ describe("ai-gateway-provider (cloudflare-ai-gateway)", () => {
3754
+ const cfModel = (apiId: string, releaseDate = "2024-01-01") =>
3755
+ createMockModel({
3756
+ id: `cloudflare-ai-gateway/${apiId}`,
3757
+ providerID: "cloudflare-ai-gateway",
3758
+ api: {
3759
+ id: apiId,
3760
+ url: "https://gateway.ai.cloudflare.com/v1/compat",
3761
+ npm: "ai-gateway-provider",
3762
+ },
3763
+ release_date: releaseDate,
3764
+ })
3765
+
3766
+ for (const testCase of [
3767
+ { id: "openai/gpt-5.4", efforts: ["none", "low", "medium", "high", "xhigh"] },
3768
+ { id: "openai/gpt-5.2-codex", efforts: ["low", "medium", "high", "xhigh"] },
3769
+ { id: "openai/gpt-5.3-codex", efforts: ["none", "low", "medium", "high", "xhigh"] },
3770
+ { id: "openai/gpt-5-pro", efforts: ["high"] },
3771
+ { id: "openai/gpt-5.2-pro", efforts: ["medium", "high", "xhigh"] },
3772
+ { id: "openai/gpt-5-chat-latest", efforts: [] },
3773
+ { id: "openai/gpt-5.2-chat-latest", efforts: ["medium"] },
3774
+ ]) {
3775
+ test(`${testCase.id} returns supported reasoning efforts`, () => {
3776
+ const result = ProviderTransform.variants(cfModel(testCase.id, "2026-03-05"))
3777
+ expect(Object.keys(result)).toEqual(testCase.efforts)
3778
+ })
3779
+ }
3780
+
3781
+ test("openai gpt-4o (no reasoning) returns empty", () => {
3782
+ const model = cfModel("openai/gpt-4o")
3783
+ model.capabilities.reasoning = false
3784
+ const result = ProviderTransform.variants(model)
3785
+ expect(result).toEqual({})
3786
+ })
3787
+
3788
+ test("non-openai upstream falls back to widely-supported OAI efforts", () => {
3789
+ const result = ProviderTransform.variants(cfModel("anthropic/claude-sonnet-4-6"))
3790
+ expect(result).toEqual({
3791
+ low: { reasoningEffort: "low" },
3792
+ medium: { reasoningEffort: "medium" },
3793
+ high: { reasoningEffort: "high" },
3794
+ })
3795
+ })
3796
+ })
3797
+ })
3798
+
3799
+ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
3800
+ const createModel = (apiId: string) => {
3801
+ const model = {
3802
+ id: `openai/${apiId}`,
3803
+ providerID: "openai",
3804
+ api: {
3805
+ id: apiId,
3806
+ url: "https://api.openai.com",
3807
+ npm: "@ai-sdk/openai",
3808
+ },
3809
+ capabilities: { reasoning: true },
3810
+ limit: { output: 64_000 },
3811
+ release_date: "2026-01-01",
3812
+ } as any
3813
+ model.variants = ProviderTransform.variants(model)
3814
+ return model
3815
+ }
3816
+
3817
+ for (const testCase of [
3818
+ { id: "gpt-5-chat-latest", options: { store: false } },
3819
+ {
3820
+ id: "gpt-5.1-chat-latest",
3821
+ options: {
3822
+ store: false,
3823
+ reasoningEffort: "medium",
3824
+ reasoningSummary: "auto",
3825
+ include: ["reasoning.encrypted_content"],
3826
+ },
3827
+ },
3828
+ {
3829
+ id: "gpt-5.2-chat-latest",
3830
+ options: {
3831
+ store: false,
3832
+ reasoningEffort: "medium",
3833
+ reasoningSummary: "auto",
3834
+ include: ["reasoning.encrypted_content"],
3835
+ },
3836
+ },
3837
+ {
3838
+ id: "gpt-5-search-api",
3839
+ options: {
3840
+ store: false,
3841
+ reasoningEffort: "none",
3842
+ reasoningSummary: "auto",
3843
+ include: ["reasoning.encrypted_content"],
3844
+ },
3845
+ },
3846
+ ]) {
3847
+ test(`${testCase.id} returns only supported small options`, () => {
3848
+ expect(ProviderTransform.smallOptions(createModel(testCase.id))).toEqual(testCase.options)
3849
+ })
3850
+ }
3851
+ })
3852
+
3853
+ describe("ProviderTransform.smallOptions - google thinking controls", () => {
3854
+ const createGoogleModel = (apiId: string) => {
3855
+ const model = {
3856
+ id: `google/${apiId}`,
3857
+ providerID: "google",
3858
+ api: {
3859
+ id: apiId,
3860
+ url: "https://generativelanguage.googleapis.com",
3861
+ npm: "@ai-sdk/google",
3862
+ },
3863
+ capabilities: { reasoning: true },
3864
+ limit: { output: 64_000 },
3865
+ } as any
3866
+ model.variants = ProviderTransform.variants(model)
3867
+ return model
3868
+ }
3869
+
3870
+ for (const testCase of [
3871
+ { id: "gemini-3-pro-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
3872
+ { id: "gemini-3-flash-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } } },
3873
+ {
3874
+ id: "gemini-3.1-flash-image-preview",
3875
+ options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } },
3876
+ },
3877
+ { id: "gemini-3-pro-image-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } } },
3878
+ { id: "gemini-2.5-pro", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
3879
+ { id: "gemini-2.5-flash", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
3880
+ ]) {
3881
+ test(`${testCase.id} returns supported small thinking options`, () => {
3882
+ expect(ProviderTransform.smallOptions(createGoogleModel(testCase.id))).toEqual(testCase.options)
3883
+ })
3884
+ }
3885
+
3886
+ test("uses the first configured variant when available", () => {
3887
+ expect(
3888
+ ProviderTransform.smallOptions({
3889
+ ...createGoogleModel("gemini-2.5-pro"),
3890
+ variants: {
3891
+ high: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
3892
+ max: { thinkingConfig: { includeThoughts: true, thinkingBudget: 32768 } },
3893
+ },
3894
+ }),
3895
+ ).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } })
3896
+ })
3897
+
3898
+ test("does not synthesize thinking options when variants are empty", () => {
3899
+ expect(ProviderTransform.smallOptions({ ...createGoogleModel("gemini-2.5-pro"), variants: {} })).toEqual({})
3900
+ })
3901
+ })
3902
+
3903
+ describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {
3904
+ const createModel = (overrides: Partial<any> = {}) =>
3905
+ ({
3906
+ id: "cloudflare-ai-gateway/openai/gpt-5.4",
3907
+ providerID: "cloudflare-ai-gateway",
3908
+ api: {
3909
+ id: "openai/gpt-5.4",
3910
+ url: "https://gateway.ai.cloudflare.com/v1/compat",
3911
+ npm: "ai-gateway-provider",
3912
+ },
3913
+ capabilities: {
3914
+ temperature: false,
3915
+ reasoning: true,
3916
+ attachment: true,
3917
+ toolcall: true,
3918
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
3919
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
3920
+ interleaved: false,
3921
+ },
3922
+ cost: { input: 1, output: 1, cache: { read: 0, write: 0 } },
3923
+ limit: { context: 1_000_000, output: 128_000 },
3924
+ status: "active",
3925
+ options: {},
3926
+ headers: {},
3927
+ release_date: "2026-03-05",
3928
+ ...overrides,
3929
+ }) as any
3930
+
3931
+ test("routes options under openaiCompatible (the key @ai-sdk/openai-compatible reads)", () => {
3932
+ // Regression: previously fell back to providerID="cloudflare-ai-gateway",
3933
+ // which @ai-sdk/openai-compatible never reads, silently dropping reasoningEffort.
3934
+ const result = ProviderTransform.providerOptions(createModel(), { reasoningEffort: "high" })
3935
+ expect(result).toEqual({ openaiCompatible: { reasoningEffort: "high" } })
3936
+ })
3937
+ })