@epoch-ai/cli 2.2.4

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 (710) hide show
  1. package/.artifacts/unit/junit.xml +2823 -0
  2. package/.project-map/backups/20260530_223453/.project-map.json +90101 -0
  3. package/.project-map/backups/20260530_223507/.project-map.json +90101 -0
  4. package/.project-map/backups/20260530_223512/.project-map.json +90101 -0
  5. package/.project-map/backups/20260530_223512/map.toon +666 -0
  6. package/.project-map/backups/20260530_223516/.project-map.json +90101 -0
  7. package/.project-map/backups/20260530_223516/map.toon +666 -0
  8. package/.project-map/backups/20260530_223520/.project-map.json +90101 -0
  9. package/.project-map/backups/20260530_223520/map.toon +666 -0
  10. package/AGENTS.md +47 -0
  11. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  12. package/Dockerfile +18 -0
  13. package/README.md +15 -0
  14. package/bin/epochcli +179 -0
  15. package/bunfig.toml +7 -0
  16. package/drizzle.config.ts +10 -0
  17. package/git +0 -0
  18. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  19. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  20. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  21. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  22. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  23. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  24. package/migration/20260225215848_workspace/migration.sql +7 -0
  25. package/migration/20260225215848_workspace/snapshot.json +959 -0
  26. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  27. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  28. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  29. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  30. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  31. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  32. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  33. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  34. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  35. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  36. package/migration/20260323234822_events/migration.sql +13 -0
  37. package/migration/20260323234822_events/snapshot.json +1271 -0
  38. package/migration/20260418092949_add_yolo_to_session/migration.sql +2 -0
  39. package/migration/20260418092949_add_yolo_to_session/snapshot.json +1199 -0
  40. package/migration/20260419120000_add_intervention_to_session/migration.sql +2 -0
  41. package/package.json +186 -0
  42. package/parsers-config.ts +290 -0
  43. package/script/build-node.ts +71 -0
  44. package/script/build.ts +255 -0
  45. package/script/check-migrations.ts +16 -0
  46. package/script/fix-node-pty.ts +28 -0
  47. package/script/postinstall.mjs +131 -0
  48. package/script/publish.ts +184 -0
  49. package/script/schema.ts +63 -0
  50. package/script/seed-e2e.ts +60 -0
  51. package/script/upgrade-opentui.ts +64 -0
  52. package/specs/effect-migration.md +310 -0
  53. package/specs/tui-plugins.md +436 -0
  54. package/specs/v2.md +14 -0
  55. package/src/account/account.sql.ts +39 -0
  56. package/src/account/index.ts +465 -0
  57. package/src/account/repo.ts +163 -0
  58. package/src/account/schema.ts +91 -0
  59. package/src/acp/README.md +174 -0
  60. package/src/acp/agent.ts +1847 -0
  61. package/src/acp/session.ts +116 -0
  62. package/src/acp/types.ts +24 -0
  63. package/src/agent/agent.ts +445 -0
  64. package/src/agent/generate.txt +75 -0
  65. package/src/agent/prompt/compaction.txt +15 -0
  66. package/src/agent/prompt/explore.txt +9 -0
  67. package/src/agent/prompt/summary.txt +11 -0
  68. package/src/agent/prompt/title.txt +44 -0
  69. package/src/auth/index.ts +110 -0
  70. package/src/bus/bus-event.ts +40 -0
  71. package/src/bus/global.ts +10 -0
  72. package/src/bus/index.ts +232 -0
  73. package/src/cli/bootstrap.ts +17 -0
  74. package/src/cli/cmd/account.ts +257 -0
  75. package/src/cli/cmd/acp.ts +70 -0
  76. package/src/cli/cmd/agent.ts +245 -0
  77. package/src/cli/cmd/cmd.ts +7 -0
  78. package/src/cli/cmd/db.ts +119 -0
  79. package/src/cli/cmd/debug/agent.ts +167 -0
  80. package/src/cli/cmd/debug/config.ts +16 -0
  81. package/src/cli/cmd/debug/file.ts +97 -0
  82. package/src/cli/cmd/debug/index.ts +48 -0
  83. package/src/cli/cmd/debug/lsp.ts +53 -0
  84. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  85. package/src/cli/cmd/debug/scrap.ts +16 -0
  86. package/src/cli/cmd/debug/skill.ts +16 -0
  87. package/src/cli/cmd/debug/snapshot.ts +52 -0
  88. package/src/cli/cmd/export.ts +89 -0
  89. package/src/cli/cmd/generate.ts +38 -0
  90. package/src/cli/cmd/github.ts +1639 -0
  91. package/src/cli/cmd/import.ts +169 -0
  92. package/src/cli/cmd/mcp.ts +754 -0
  93. package/src/cli/cmd/models.ts +78 -0
  94. package/src/cli/cmd/plug.ts +233 -0
  95. package/src/cli/cmd/pr.ts +127 -0
  96. package/src/cli/cmd/providers.ts +478 -0
  97. package/src/cli/cmd/run.ts +681 -0
  98. package/src/cli/cmd/serve.ts +24 -0
  99. package/src/cli/cmd/session.ts +159 -0
  100. package/src/cli/cmd/stats.ts +410 -0
  101. package/src/cli/cmd/tui/app.tsx +945 -0
  102. package/src/cli/cmd/tui/attach.ts +88 -0
  103. package/src/cli/cmd/tui/component/border.tsx +21 -0
  104. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  105. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  106. package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
  107. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  108. package/src/cli/cmd/tui/component/dialog-model.tsx +190 -0
  109. package/src/cli/cmd/tui/component/dialog-provider.tsx +364 -0
  110. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  111. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  112. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  113. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  114. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  115. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  116. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  117. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  118. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  119. package/src/cli/cmd/tui/component/error-component.tsx +92 -0
  120. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  121. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  122. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +672 -0
  123. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  124. package/src/cli/cmd/tui/component/prompt/history.tsx +109 -0
  125. package/src/cli/cmd/tui/component/prompt/index.tsx +1336 -0
  126. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  127. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  128. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  129. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  130. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  131. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  132. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  133. package/src/cli/cmd/tui/context/args.tsx +15 -0
  134. package/src/cli/cmd/tui/context/directory.ts +13 -0
  135. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  136. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  137. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  138. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  139. package/src/cli/cmd/tui/context/local.tsx +456 -0
  140. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  141. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  142. package/src/cli/cmd/tui/context/route.tsx +52 -0
  143. package/src/cli/cmd/tui/context/sdk.tsx +115 -0
  144. package/src/cli/cmd/tui/context/sync.tsx +516 -0
  145. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  146. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  147. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  148. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  149. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  150. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  151. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  152. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  153. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  154. package/src/cli/cmd/tui/context/theme/epochcli.json +245 -0
  155. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  156. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  157. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  158. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  159. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  160. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  161. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  162. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  163. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  164. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  165. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  166. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  167. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  168. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  169. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  170. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  171. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  172. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  173. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  174. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  175. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  176. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  177. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  178. package/src/cli/cmd/tui/context/theme.tsx +1236 -0
  179. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  180. package/src/cli/cmd/tui/event.ts +48 -0
  181. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
  182. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +145 -0
  183. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  184. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  185. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  186. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  187. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  188. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  189. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  190. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  191. package/src/cli/cmd/tui/plugin/api.tsx +397 -0
  192. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  193. package/src/cli/cmd/tui/plugin/internal.ts +27 -0
  194. package/src/cli/cmd/tui/plugin/runtime.ts +1031 -0
  195. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  196. package/src/cli/cmd/tui/routes/home.tsx +84 -0
  197. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  198. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  199. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  200. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  201. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  202. package/src/cli/cmd/tui/routes/session/index.tsx +2161 -0
  203. package/src/cli/cmd/tui/routes/session/permission.tsx +691 -0
  204. package/src/cli/cmd/tui/routes/session/question.tsx +468 -0
  205. package/src/cli/cmd/tui/routes/session/sidebar.tsx +70 -0
  206. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
  207. package/src/cli/cmd/tui/thread.ts +241 -0
  208. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  209. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  210. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +211 -0
  211. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  212. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +115 -0
  213. package/src/cli/cmd/tui/ui/dialog-select.tsx +417 -0
  214. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  215. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  216. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  217. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  218. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  219. package/src/cli/cmd/tui/util/editor.ts +37 -0
  220. package/src/cli/cmd/tui/util/model.ts +23 -0
  221. package/src/cli/cmd/tui/util/provider-origin.ts +20 -0
  222. package/src/cli/cmd/tui/util/scroll.ts +23 -0
  223. package/src/cli/cmd/tui/util/selection.ts +25 -0
  224. package/src/cli/cmd/tui/util/signal.ts +7 -0
  225. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  226. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  227. package/src/cli/cmd/tui/win32.ts +129 -0
  228. package/src/cli/cmd/tui/worker.ts +195 -0
  229. package/src/cli/cmd/uninstall.ts +353 -0
  230. package/src/cli/cmd/upgrade.ts +73 -0
  231. package/src/cli/cmd/web.ts +81 -0
  232. package/src/cli/effect/prompt.ts +25 -0
  233. package/src/cli/error.ts +46 -0
  234. package/src/cli/heap.ts +59 -0
  235. package/src/cli/logo.ts +6 -0
  236. package/src/cli/network.ts +60 -0
  237. package/src/cli/ui.ts +133 -0
  238. package/src/cli/upgrade.ts +31 -0
  239. package/src/command/index.ts +197 -0
  240. package/src/command/template/initialize.txt +66 -0
  241. package/src/command/template/review.txt +101 -0
  242. package/src/config/config.ts +1610 -0
  243. package/src/config/console-state.ts +15 -0
  244. package/src/config/markdown.ts +99 -0
  245. package/src/config/paths.ts +167 -0
  246. package/src/config/tui-migrate.ts +155 -0
  247. package/src/config/tui-schema.ts +37 -0
  248. package/src/config/tui.ts +179 -0
  249. package/src/config/validator.ts +52 -0
  250. package/src/control-plane/adaptors/index.ts +20 -0
  251. package/src/control-plane/adaptors/worktree.ts +42 -0
  252. package/src/control-plane/schema.ts +17 -0
  253. package/src/control-plane/sse.ts +66 -0
  254. package/src/control-plane/types.ts +32 -0
  255. package/src/control-plane/workspace.sql.ts +17 -0
  256. package/src/control-plane/workspace.ts +168 -0
  257. package/src/effect/cross-spawn-spawner.ts +502 -0
  258. package/src/effect/instance-ref.ts +6 -0
  259. package/src/effect/instance-registry.ts +12 -0
  260. package/src/effect/instance-state.ts +82 -0
  261. package/src/effect/run-service.ts +33 -0
  262. package/src/effect/runner.ts +216 -0
  263. package/src/env/index.ts +28 -0
  264. package/src/file/ignore.ts +82 -0
  265. package/src/file/index.ts +686 -0
  266. package/src/file/protected.ts +59 -0
  267. package/src/file/ripgrep.ts +376 -0
  268. package/src/file/time.ts +133 -0
  269. package/src/file/watcher.ts +172 -0
  270. package/src/filesystem/index.ts +236 -0
  271. package/src/flag/flag.ts +157 -0
  272. package/src/format/formatter.ts +413 -0
  273. package/src/format/index.ts +203 -0
  274. package/src/git/index.ts +303 -0
  275. package/src/global/index.ts +54 -0
  276. package/src/id/id.ts +85 -0
  277. package/src/ide/index.ts +74 -0
  278. package/src/index.ts +253 -0
  279. package/src/installation/index.ts +355 -0
  280. package/src/installation/meta.ts +7 -0
  281. package/src/lsp/client.ts +256 -0
  282. package/src/lsp/index.ts +558 -0
  283. package/src/lsp/language.ts +120 -0
  284. package/src/lsp/launch.ts +21 -0
  285. package/src/lsp/server.ts +1968 -0
  286. package/src/mcp/auth.ts +173 -0
  287. package/src/mcp/index.ts +1250 -0
  288. package/src/mcp/oauth-callback.ts +216 -0
  289. package/src/mcp/oauth-provider.ts +185 -0
  290. package/src/mcp/schema-loader.ts +82 -0
  291. package/src/node.ts +1 -0
  292. package/src/npm/index.ts +188 -0
  293. package/src/patch/index.ts +680 -0
  294. package/src/permission/arity.ts +163 -0
  295. package/src/permission/evaluate.ts +15 -0
  296. package/src/permission/index.ts +323 -0
  297. package/src/permission/schema.ts +17 -0
  298. package/src/plugin/cloudflare.ts +67 -0
  299. package/src/plugin/codex.ts +608 -0
  300. package/src/plugin/github-copilot/copilot.ts +361 -0
  301. package/src/plugin/github-copilot/models.ts +144 -0
  302. package/src/plugin/index.ts +288 -0
  303. package/src/plugin/install.ts +439 -0
  304. package/src/plugin/loader.ts +174 -0
  305. package/src/plugin/meta.ts +188 -0
  306. package/src/plugin/shared.ts +323 -0
  307. package/src/project/bootstrap.ts +29 -0
  308. package/src/project/instance.ts +175 -0
  309. package/src/project/project.sql.ts +16 -0
  310. package/src/project/project.ts +519 -0
  311. package/src/project/schema.ts +16 -0
  312. package/src/project/state.ts +70 -0
  313. package/src/project/vcs.ts +240 -0
  314. package/src/provider/auth.ts +253 -0
  315. package/src/provider/error.ts +297 -0
  316. package/src/provider/models.ts +162 -0
  317. package/src/provider/provider.ts +1776 -0
  318. package/src/provider/schema.ts +38 -0
  319. package/src/provider/sdk/copilot/README.md +5 -0
  320. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  321. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  322. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  323. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  324. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +814 -0
  325. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  326. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  327. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  328. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  329. package/src/provider/sdk/copilot/index.ts +2 -0
  330. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  331. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  332. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  333. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  334. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  335. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  336. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  337. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  338. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  339. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  340. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  341. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  342. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  343. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  344. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  345. package/src/provider/transform.ts +1124 -0
  346. package/src/pty/index.ts +397 -0
  347. package/src/pty/pty.bun.ts +26 -0
  348. package/src/pty/pty.node.ts +27 -0
  349. package/src/pty/pty.ts +25 -0
  350. package/src/pty/schema.ts +17 -0
  351. package/src/question/index.ts +224 -0
  352. package/src/question/schema.ts +17 -0
  353. package/src/server/error.ts +36 -0
  354. package/src/server/event.ts +7 -0
  355. package/src/server/instance.ts +315 -0
  356. package/src/server/mdns.ts +60 -0
  357. package/src/server/middleware.ts +33 -0
  358. package/src/server/projectors.ts +28 -0
  359. package/src/server/proxy.ts +130 -0
  360. package/src/server/router.ts +105 -0
  361. package/src/server/routes/config.ts +92 -0
  362. package/src/server/routes/event.ts +83 -0
  363. package/src/server/routes/experimental.ts +374 -0
  364. package/src/server/routes/file.ts +197 -0
  365. package/src/server/routes/global.ts +312 -0
  366. package/src/server/routes/mcp.ts +225 -0
  367. package/src/server/routes/permission.ts +69 -0
  368. package/src/server/routes/project.ts +118 -0
  369. package/src/server/routes/provider.ts +171 -0
  370. package/src/server/routes/pty.ts +210 -0
  371. package/src/server/routes/question.ts +99 -0
  372. package/src/server/routes/session.ts +984 -0
  373. package/src/server/routes/tui.ts +378 -0
  374. package/src/server/routes/workspace.ts +94 -0
  375. package/src/server/server.ts +353 -0
  376. package/src/session/compaction.ts +86 -0
  377. package/src/session/index.ts +904 -0
  378. package/src/session/instruction.ts +261 -0
  379. package/src/session/llm/monitor.ts +87 -0
  380. package/src/session/llm.ts +1676 -0
  381. package/src/session/message-v2.ts +1082 -0
  382. package/src/session/message.ts +191 -0
  383. package/src/session/overflow.ts +35 -0
  384. package/src/session/processor.ts +635 -0
  385. package/src/session/projectors.ts +136 -0
  386. package/src/session/prompt/build-switch.txt +5 -0
  387. package/src/session/prompt/builder.ts +135 -0
  388. package/src/session/prompt/default.txt +11 -0
  389. package/src/session/prompt/engine.ts +1072 -0
  390. package/src/session/prompt/gemma4.txt +1 -0
  391. package/src/session/prompt/max-steps.txt +16 -0
  392. package/src/session/prompt/orchestrator.ts +426 -0
  393. package/src/session/prompt/plan.txt +28 -0
  394. package/src/session/prompt/qwen.txt +19 -0
  395. package/src/session/prompt/resolver.ts +670 -0
  396. package/src/session/prompt/router.ts +197 -0
  397. package/src/session/prompt/state.ts +96 -0
  398. package/src/session/prompt/types.ts +115 -0
  399. package/src/session/prompt/utils.ts +15 -0
  400. package/src/session/prompt.ts +362 -0
  401. package/src/session/retry.ts +106 -0
  402. package/src/session/revert.ts +176 -0
  403. package/src/session/sanitizer.ts +125 -0
  404. package/src/session/schema.ts +38 -0
  405. package/src/session/session.sql.ts +106 -0
  406. package/src/session/status.ts +102 -0
  407. package/src/session/summary.ts +183 -0
  408. package/src/session/system.ts +79 -0
  409. package/src/session/todo.ts +166 -0
  410. package/src/session/worker.ts +382 -0
  411. package/src/shell/shell.ts +110 -0
  412. package/src/skill/discovery.ts +116 -0
  413. package/src/skill/index.ts +287 -0
  414. package/src/snapshot/index.ts +726 -0
  415. package/src/sql.d.ts +4 -0
  416. package/src/storage/db.bun.ts +8 -0
  417. package/src/storage/db.node.ts +8 -0
  418. package/src/storage/db.ts +174 -0
  419. package/src/storage/json-migration.ts +387 -0
  420. package/src/storage/schema.sql.ts +10 -0
  421. package/src/storage/schema.ts +4 -0
  422. package/src/storage/storage.ts +353 -0
  423. package/src/sync/README.md +179 -0
  424. package/src/sync/event.sql.ts +16 -0
  425. package/src/sync/index.ts +263 -0
  426. package/src/sync/schema.ts +14 -0
  427. package/src/tool/apply_patch.ts +281 -0
  428. package/src/tool/apply_patch.txt +1 -0
  429. package/src/tool/arbitration.txt +5 -0
  430. package/src/tool/bash.ts +494 -0
  431. package/src/tool/bash.txt +2 -0
  432. package/src/tool/batch.ts +183 -0
  433. package/src/tool/batch.txt +1 -0
  434. package/src/tool/codesearch.ts +132 -0
  435. package/src/tool/codesearch.txt +1 -0
  436. package/src/tool/edit.ts +734 -0
  437. package/src/tool/edit.txt +1 -0
  438. package/src/tool/external-directory.ts +46 -0
  439. package/src/tool/glob.ts +73 -0
  440. package/src/tool/glob.txt +2 -0
  441. package/src/tool/grep.ts +156 -0
  442. package/src/tool/grep.txt +2 -0
  443. package/src/tool/invalid.ts +20 -0
  444. package/src/tool/ls.ts +121 -0
  445. package/src/tool/ls.txt +1 -0
  446. package/src/tool/lsp.ts +97 -0
  447. package/src/tool/lsp.txt +1 -0
  448. package/src/tool/multiedit.ts +46 -0
  449. package/src/tool/multiedit.txt +1 -0
  450. package/src/tool/plan-enter.txt +14 -0
  451. package/src/tool/plan-exit.txt +13 -0
  452. package/src/tool/plan.ts +131 -0
  453. package/src/tool/question.ts +46 -0
  454. package/src/tool/question.txt +10 -0
  455. package/src/tool/read.ts +332 -0
  456. package/src/tool/read.txt +1 -0
  457. package/src/tool/registry.ts +288 -0
  458. package/src/tool/revert.ts +37 -0
  459. package/src/tool/schema.ts +17 -0
  460. package/src/tool/skill.ts +105 -0
  461. package/src/tool/task.ts +150 -0
  462. package/src/tool/task.txt +3 -0
  463. package/src/tool/task_complete.ts +21 -0
  464. package/src/tool/tool.ts +112 -0
  465. package/src/tool/truncate.ts +144 -0
  466. package/src/tool/truncation-dir.ts +4 -0
  467. package/src/tool/webfetch.ts +206 -0
  468. package/src/tool/webfetch.txt +1 -0
  469. package/src/tool/websearch.ts +150 -0
  470. package/src/tool/websearch.txt +1 -0
  471. package/src/tool/write.ts +101 -0
  472. package/src/tool/write.txt +1 -0
  473. package/src/util/abort.ts +35 -0
  474. package/src/util/ai-sdk.ts +59 -0
  475. package/src/util/archive.ts +17 -0
  476. package/src/util/color.ts +19 -0
  477. package/src/util/context.ts +25 -0
  478. package/src/util/data-url.ts +9 -0
  479. package/src/util/defer.ts +12 -0
  480. package/src/util/effect-http-client.ts +11 -0
  481. package/src/util/effect-zod.ts +98 -0
  482. package/src/util/error.ts +77 -0
  483. package/src/util/filesystem.ts +245 -0
  484. package/src/util/flock.ts +333 -0
  485. package/src/util/fn.ts +21 -0
  486. package/src/util/format.ts +20 -0
  487. package/src/util/glob.ts +34 -0
  488. package/src/util/hash.ts +7 -0
  489. package/src/util/iife.ts +3 -0
  490. package/src/util/keybind.ts +103 -0
  491. package/src/util/lazy.ts +23 -0
  492. package/src/util/locale.ts +81 -0
  493. package/src/util/lock.ts +98 -0
  494. package/src/util/log-parser.ts +114 -0
  495. package/src/util/log.ts +250 -0
  496. package/src/util/network.ts +23 -0
  497. package/src/util/process.ts +176 -0
  498. package/src/util/queue.ts +32 -0
  499. package/src/util/record.ts +3 -0
  500. package/src/util/rpc.ts +66 -0
  501. package/src/util/schema.ts +53 -0
  502. package/src/util/scrap.ts +10 -0
  503. package/src/util/session-analyzer.ts +331 -0
  504. package/src/util/session-telemetry.ts +91 -0
  505. package/src/util/signal.ts +12 -0
  506. package/src/util/timeout.ts +14 -0
  507. package/src/util/token.ts +7 -0
  508. package/src/util/tokenizer.ts +50 -0
  509. package/src/util/toon.ts +45 -0
  510. package/src/util/update-schema.ts +13 -0
  511. package/src/util/which.ts +14 -0
  512. package/src/util/wildcard.ts +59 -0
  513. package/src/worktree/index.ts +612 -0
  514. package/sst-env.d.ts +10 -0
  515. package/test/AGENTS.md +81 -0
  516. package/test/account/repo.test.ts +326 -0
  517. package/test/account/service.test.ts +393 -0
  518. package/test/acp/agent-interface.test.ts +51 -0
  519. package/test/acp/event-subscription.test.ts +685 -0
  520. package/test/agent/agent.test.ts +716 -0
  521. package/test/auth/auth.test.ts +58 -0
  522. package/test/bus/bus-effect.test.ts +164 -0
  523. package/test/bus/bus-integration.test.ts +87 -0
  524. package/test/bus/bus.test.ts +219 -0
  525. package/test/cli/account.test.ts +26 -0
  526. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  527. package/test/cli/github-action.test.ts +198 -0
  528. package/test/cli/github-remote.test.ts +80 -0
  529. package/test/cli/plugin-auth-picker.test.ts +120 -0
  530. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  531. package/test/cli/tui/plugin-add.test.ts +107 -0
  532. package/test/cli/tui/plugin-install.test.ts +89 -0
  533. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  534. package/test/cli/tui/plugin-loader-entrypoint.test.ts +492 -0
  535. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  536. package/test/cli/tui/plugin-loader.test.ts +752 -0
  537. package/test/cli/tui/plugin-toggle.test.ts +159 -0
  538. package/test/cli/tui/slot-replace.test.tsx +47 -0
  539. package/test/cli/tui/theme-store.test.ts +51 -0
  540. package/test/cli/tui/thread.test.ts +128 -0
  541. package/test/cli/tui/transcript.test.ts +426 -0
  542. package/test/config/agent-color.test.ts +71 -0
  543. package/test/config/config.test.ts +2337 -0
  544. package/test/config/fixtures/empty-frontmatter.md +4 -0
  545. package/test/config/fixtures/frontmatter.md +28 -0
  546. package/test/config/fixtures/markdown-header.md +11 -0
  547. package/test/config/fixtures/no-frontmatter.md +1 -0
  548. package/test/config/fixtures/weird-model-id.md +13 -0
  549. package/test/config/markdown.test.ts +228 -0
  550. package/test/config/tui.test.ts +800 -0
  551. package/test/control-plane/sse.test.ts +56 -0
  552. package/test/effect/cross-spawn-spawner.test.ts +412 -0
  553. package/test/effect/instance-state.test.ts +482 -0
  554. package/test/effect/run-service.test.ts +46 -0
  555. package/test/effect/runner.test.ts +523 -0
  556. package/test/fake/provider.ts +82 -0
  557. package/test/file/fsmonitor.test.ts +62 -0
  558. package/test/file/ignore.test.ts +10 -0
  559. package/test/file/index.test.ts +946 -0
  560. package/test/file/path-traversal.test.ts +198 -0
  561. package/test/file/ripgrep.test.ts +54 -0
  562. package/test/file/time.test.ts +445 -0
  563. package/test/file/watcher.test.ts +247 -0
  564. package/test/filesystem/filesystem.test.ts +319 -0
  565. package/test/fixture/db.ts +11 -0
  566. package/test/fixture/fixture.test.ts +26 -0
  567. package/test/fixture/fixture.ts +172 -0
  568. package/test/fixture/flock-worker.ts +72 -0
  569. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  570. package/test/fixture/plug-worker.ts +93 -0
  571. package/test/fixture/plugin-meta-worker.ts +26 -0
  572. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  573. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  574. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  575. package/test/fixture/skills/index.json +6 -0
  576. package/test/fixture/tui-plugin.ts +328 -0
  577. package/test/fixture/tui-runtime.ts +27 -0
  578. package/test/format/format.test.ts +171 -0
  579. package/test/git/git.test.ts +128 -0
  580. package/test/ide/ide.test.ts +82 -0
  581. package/test/installation/installation.test.ts +152 -0
  582. package/test/keybind.test.ts +421 -0
  583. package/test/lib/effect.ts +53 -0
  584. package/test/lib/filesystem.ts +10 -0
  585. package/test/lib/llm-server.ts +794 -0
  586. package/test/lsp/client.test.ts +95 -0
  587. package/test/lsp/index.test.ts +133 -0
  588. package/test/lsp/launch.test.ts +22 -0
  589. package/test/lsp/lifecycle.test.ts +147 -0
  590. package/test/mcp/headers.test.ts +153 -0
  591. package/test/mcp/lifecycle.test.ts +750 -0
  592. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  593. package/test/mcp/oauth-browser.test.ts +249 -0
  594. package/test/mcp/sc-approve-validator.test.ts +431 -0
  595. package/test/memory/abort-leak.test.ts +137 -0
  596. package/test/npm.test.ts +18 -0
  597. package/test/patch/patch.test.ts +348 -0
  598. package/test/permission/arity.test.ts +33 -0
  599. package/test/permission/next.test.ts +1123 -0
  600. package/test/permission-task.test.ts +323 -0
  601. package/test/plugin/auth-override.test.ts +74 -0
  602. package/test/plugin/codex.test.ts +123 -0
  603. package/test/plugin/github-copilot-models.test.ts +117 -0
  604. package/test/plugin/install-concurrency.test.ts +140 -0
  605. package/test/plugin/install.test.ts +570 -0
  606. package/test/plugin/loader-shared.test.ts +1136 -0
  607. package/test/plugin/meta.test.ts +137 -0
  608. package/test/plugin/shared.test.ts +88 -0
  609. package/test/plugin/trigger.test.ts +111 -0
  610. package/test/preload.ts +90 -0
  611. package/test/project/migrate-global.test.ts +140 -0
  612. package/test/project/project.test.ts +459 -0
  613. package/test/project/state.test.ts +115 -0
  614. package/test/project/vcs.test.ts +228 -0
  615. package/test/project/worktree-remove.test.ts +96 -0
  616. package/test/project/worktree.test.ts +173 -0
  617. package/test/provider/amazon-bedrock.test.ts +447 -0
  618. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  619. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  620. package/test/provider/error.test.ts +49 -0
  621. package/test/provider/gitlab-duo.test.ts +412 -0
  622. package/test/provider/provider.test.ts +2494 -0
  623. package/test/provider/transform.test.ts +2944 -0
  624. package/test/pty/pty-output-isolation.test.ts +141 -0
  625. package/test/pty/pty-session.test.ts +92 -0
  626. package/test/pty/pty-shell.test.ts +59 -0
  627. package/test/question/question.test.ts +453 -0
  628. package/test/server/global-session-list.test.ts +89 -0
  629. package/test/server/project-init-git.test.ts +121 -0
  630. package/test/server/session-actions.test.ts +83 -0
  631. package/test/server/session-list.test.ts +98 -0
  632. package/test/server/session-messages.test.ts +159 -0
  633. package/test/server/session-select.test.ts +84 -0
  634. package/test/session/compaction.test.ts +683 -0
  635. package/test/session/continuity-handover.test.ts +620 -0
  636. package/test/session/deterministic-handover.test.ts +328 -0
  637. package/test/session/doom-protection.test.ts +247 -0
  638. package/test/session/hard-reset.test.ts +179 -0
  639. package/test/session/instruction.test.ts +286 -0
  640. package/test/session/llm/monitor.test.ts +53 -0
  641. package/test/session/llm-sanitizer.test.ts +90 -0
  642. package/test/session/llm-zones-e2e.test.ts +61 -0
  643. package/test/session/llm.test.ts +1308 -0
  644. package/test/session/mcpx-normalization.test.ts +86 -0
  645. package/test/session/mcpx-syntax-recovery.test.ts +28 -0
  646. package/test/session/message-v2.test.ts +957 -0
  647. package/test/session/messages-pagination.test.ts +885 -0
  648. package/test/session/processor-effect.test.ts +805 -0
  649. package/test/session/prompt/builder.test.ts +71 -0
  650. package/test/session/prompt/engine-loop.test.ts +80 -0
  651. package/test/session/prompt/orchestrator.test.ts +108 -0
  652. package/test/session/prompt/resolver.test.ts +211 -0
  653. package/test/session/prompt/router.test.ts +84 -0
  654. package/test/session/prompt/state.test.ts +57 -0
  655. package/test/session/prompt-effect.test.ts +1241 -0
  656. package/test/session/prompt.test.ts +522 -0
  657. package/test/session/refactor-system-zones.test.ts +241 -0
  658. package/test/session/retry.test.ts +232 -0
  659. package/test/session/revert-compact.test.ts +621 -0
  660. package/test/session/sanitizer.test.ts +61 -0
  661. package/test/session/session.test.ts +142 -0
  662. package/test/session/snapshot-tool-race.test.ts +242 -0
  663. package/test/session/structured-output-integration.test.ts +233 -0
  664. package/test/session/structured-output.test.ts +391 -0
  665. package/test/session/system.test.ts +59 -0
  666. package/test/session/telemetry.test.ts +35 -0
  667. package/test/shell/shell.test.ts +73 -0
  668. package/test/skill/discovery.test.ts +116 -0
  669. package/test/skill/skill.test.ts +392 -0
  670. package/test/snapshot/snapshot.test.ts +1404 -0
  671. package/test/storage/db.test.ts +14 -0
  672. package/test/storage/json-migration.test.ts +791 -0
  673. package/test/storage/storage.test.ts +295 -0
  674. package/test/sync/index.test.ts +191 -0
  675. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  676. package/test/tool/apply_patch.test.ts +567 -0
  677. package/test/tool/bash.test.ts +1099 -0
  678. package/test/tool/edit.test.ts +681 -0
  679. package/test/tool/external-directory.test.ts +198 -0
  680. package/test/tool/fixtures/large-image.png +0 -0
  681. package/test/tool/fixtures/models-api.json +65179 -0
  682. package/test/tool/grep.test.ts +111 -0
  683. package/test/tool/question.test.ts +126 -0
  684. package/test/tool/read.test.ts +468 -0
  685. package/test/tool/registry.test.ts +126 -0
  686. package/test/tool/skill.test.ts +167 -0
  687. package/test/tool/task.test.ts +49 -0
  688. package/test/tool/tool-define.test.ts +101 -0
  689. package/test/tool/truncation.test.ts +161 -0
  690. package/test/tool/webfetch.test.ts +101 -0
  691. package/test/tool/write.test.ts +354 -0
  692. package/test/util/data-url.test.ts +14 -0
  693. package/test/util/effect-zod.test.ts +61 -0
  694. package/test/util/error.test.ts +38 -0
  695. package/test/util/filesystem.test.ts +656 -0
  696. package/test/util/flock.test.ts +383 -0
  697. package/test/util/format.test.ts +59 -0
  698. package/test/util/glob.test.ts +164 -0
  699. package/test/util/iife.test.ts +36 -0
  700. package/test/util/lazy.test.ts +50 -0
  701. package/test/util/lock.test.ts +72 -0
  702. package/test/util/log-parser.test.ts +61 -0
  703. package/test/util/module.test.ts +59 -0
  704. package/test/util/process.test.ts +128 -0
  705. package/test/util/telemetry-integration.test.ts +104 -0
  706. package/test/util/timeout.test.ts +21 -0
  707. package/test/util/which.test.ts +100 -0
  708. package/test/util/wildcard.test.ts +90 -0
  709. package/test-regex.js +50 -0
  710. package/tsconfig.json +23 -0
@@ -0,0 +1,2944 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { ProviderTransform } from "../../src/provider/transform"
3
+ import { ModelID, ProviderID } from "../../src/provider/schema"
4
+
5
+ const OUTPUT_TOKEN_MAX = 32000
6
+
7
+ describe("ProviderTransform.options - setCacheKey", () => {
8
+ const sessionID = "test-session-123"
9
+
10
+ const mockModel = {
11
+ id: "anthropic/claude-3-5-sonnet",
12
+ providerID: "anthropic",
13
+ api: {
14
+ id: "claude-3-5-sonnet-20241022",
15
+ url: "https://api.anthropic.com",
16
+ npm: "@ai-sdk/anthropic",
17
+ },
18
+ name: "Claude 3.5 Sonnet",
19
+ capabilities: {
20
+ temperature: true,
21
+ reasoning: false,
22
+ attachment: true,
23
+ toolcall: true,
24
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
25
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
26
+ interleaved: false,
27
+ },
28
+ cost: {
29
+ input: 0.003,
30
+ output: 0.015,
31
+ cache: { read: 0.0003, write: 0.00375 },
32
+ },
33
+ limit: {
34
+ context: 200000,
35
+ output: 8192,
36
+ },
37
+ status: "active",
38
+ options: {},
39
+ headers: {},
40
+ } as any
41
+
42
+ test("should set promptCacheKey when providerOptions.setCacheKey is true", () => {
43
+ const result = ProviderTransform.options({
44
+ model: mockModel,
45
+ sessionID,
46
+ providerOptions: { setCacheKey: true },
47
+ })
48
+ expect(result.promptCacheKey).toBe(sessionID)
49
+ })
50
+
51
+ test("should not set promptCacheKey when providerOptions.setCacheKey is false", () => {
52
+ const result = ProviderTransform.options({
53
+ model: mockModel,
54
+ sessionID,
55
+ providerOptions: { setCacheKey: false },
56
+ })
57
+ expect(result.promptCacheKey).toBeUndefined()
58
+ })
59
+
60
+ test("should not set promptCacheKey when providerOptions is undefined", () => {
61
+ const result = ProviderTransform.options({
62
+ model: mockModel,
63
+ sessionID,
64
+ providerOptions: undefined,
65
+ })
66
+ expect(result.promptCacheKey).toBeUndefined()
67
+ })
68
+
69
+ test("should not set promptCacheKey when providerOptions does not have setCacheKey", () => {
70
+ const result = ProviderTransform.options({ model: mockModel, sessionID, providerOptions: {} })
71
+ expect(result.promptCacheKey).toBeUndefined()
72
+ })
73
+
74
+ test("should set promptCacheKey for openai provider regardless of setCacheKey", () => {
75
+ const openaiModel = {
76
+ ...mockModel,
77
+ providerID: "openai",
78
+ api: {
79
+ id: "gpt-4",
80
+ url: "https://api.openai.com",
81
+ npm: "@ai-sdk/openai",
82
+ },
83
+ }
84
+ const result = ProviderTransform.options({ model: openaiModel, sessionID, providerOptions: {} })
85
+ expect(result.promptCacheKey).toBe(sessionID)
86
+ })
87
+
88
+ test("should set store=false for openai provider", () => {
89
+ const openaiModel = {
90
+ ...mockModel,
91
+ providerID: "openai",
92
+ api: {
93
+ id: "gpt-4",
94
+ url: "https://api.openai.com",
95
+ npm: "@ai-sdk/openai",
96
+ },
97
+ }
98
+ const result = ProviderTransform.options({
99
+ model: openaiModel,
100
+ sessionID,
101
+ providerOptions: {},
102
+ })
103
+ expect(result.store).toBe(false)
104
+ })
105
+ })
106
+
107
+ describe("ProviderTransform.options - google thinkingConfig gating", () => {
108
+ const sessionID = "test-session-123"
109
+
110
+ const createGoogleModel = (reasoning: boolean, npm: "@ai-sdk/google" | "@ai-sdk/google-vertex") =>
111
+ ({
112
+ id: `${npm === "@ai-sdk/google" ? "google" : "google-vertex"}/gemini-2.0-flash`,
113
+ providerID: npm === "@ai-sdk/google" ? "google" : "google-vertex",
114
+ api: {
115
+ id: "gemini-2.0-flash",
116
+ url: npm === "@ai-sdk/google" ? "https://generativelanguage.googleapis.com" : "https://vertexai.googleapis.com",
117
+ npm,
118
+ },
119
+ name: "Gemini 2.0 Flash",
120
+ capabilities: {
121
+ temperature: true,
122
+ reasoning,
123
+ attachment: true,
124
+ toolcall: true,
125
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
126
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
127
+ interleaved: false,
128
+ },
129
+ cost: {
130
+ input: 0.001,
131
+ output: 0.002,
132
+ cache: { read: 0.0001, write: 0.0002 },
133
+ },
134
+ limit: {
135
+ context: 1_000_000,
136
+ output: 8192,
137
+ },
138
+ status: "active",
139
+ options: {},
140
+ headers: {},
141
+ }) as any
142
+
143
+ test("does not set thinkingConfig for google models without reasoning capability", () => {
144
+ const result = ProviderTransform.options({
145
+ model: createGoogleModel(false, "@ai-sdk/google"),
146
+ sessionID,
147
+ providerOptions: {},
148
+ })
149
+ expect(result.thinkingConfig).toBeUndefined()
150
+ })
151
+
152
+ test("sets thinkingConfig for google models with reasoning capability", () => {
153
+ const result = ProviderTransform.options({
154
+ model: createGoogleModel(true, "@ai-sdk/google"),
155
+ sessionID,
156
+ providerOptions: {},
157
+ })
158
+ expect(result.thinkingConfig).toEqual({
159
+ includeThoughts: true,
160
+ thinkingLevel: "low",
161
+ })
162
+ })
163
+
164
+ test("does not set thinkingConfig for vertex models without reasoning capability", () => {
165
+ const result = ProviderTransform.options({
166
+ model: createGoogleModel(false, "@ai-sdk/google-vertex"),
167
+ sessionID,
168
+ providerOptions: {},
169
+ })
170
+ expect(result.thinkingConfig).toBeUndefined()
171
+ })
172
+ })
173
+
174
+ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
175
+ const sessionID = "test-session-123"
176
+
177
+ const createGpt5Model = (apiId: string) =>
178
+ ({
179
+ id: `openai/${apiId}`,
180
+ providerID: "openai",
181
+ api: {
182
+ id: apiId,
183
+ url: "https://api.openai.com",
184
+ npm: "@ai-sdk/openai",
185
+ },
186
+ name: apiId,
187
+ capabilities: {
188
+ temperature: true,
189
+ reasoning: true,
190
+ attachment: true,
191
+ toolcall: true,
192
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
193
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
194
+ interleaved: false,
195
+ },
196
+ cost: { input: 0.03, output: 0.06, cache: { read: 0.001, write: 0.002 } },
197
+ limit: { context: 128000, output: 4096 },
198
+ status: "active",
199
+ options: {},
200
+ headers: {},
201
+ }) as any
202
+
203
+ test("gpt-5.2 should have textVerbosity set to low", () => {
204
+ const model = createGpt5Model("gpt-5.2")
205
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
206
+ expect(result.textVerbosity).toBe("low")
207
+ })
208
+
209
+ test("gpt-5.1 should have textVerbosity set to low", () => {
210
+ const model = createGpt5Model("gpt-5.1")
211
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
212
+ expect(result.textVerbosity).toBe("low")
213
+ })
214
+
215
+ test("gpt-5.2-chat-latest should NOT have textVerbosity set (only supports medium)", () => {
216
+ const model = createGpt5Model("gpt-5.2-chat-latest")
217
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
218
+ expect(result.textVerbosity).toBeUndefined()
219
+ })
220
+
221
+ test("gpt-5.1-chat-latest should NOT have textVerbosity set (only supports medium)", () => {
222
+ const model = createGpt5Model("gpt-5.1-chat-latest")
223
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
224
+ expect(result.textVerbosity).toBeUndefined()
225
+ })
226
+
227
+ test("gpt-5.2-chat should NOT have textVerbosity set", () => {
228
+ const model = createGpt5Model("gpt-5.2-chat")
229
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
230
+ expect(result.textVerbosity).toBeUndefined()
231
+ })
232
+
233
+ test("gpt-5-chat should NOT have textVerbosity set", () => {
234
+ const model = createGpt5Model("gpt-5-chat")
235
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
236
+ expect(result.textVerbosity).toBeUndefined()
237
+ })
238
+
239
+ test("gpt-5.2-codex should NOT have textVerbosity set (codex models excluded)", () => {
240
+ const model = createGpt5Model("gpt-5.2-codex")
241
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
242
+ expect(result.textVerbosity).toBeUndefined()
243
+ })
244
+ })
245
+
246
+ describe("ProviderTransform.options - gateway", () => {
247
+ const sessionID = "test-session-123"
248
+
249
+ const createModel = (id: string) =>
250
+ ({
251
+ id,
252
+ providerID: "vercel",
253
+ api: {
254
+ id,
255
+ url: "https://ai-gateway.vercel.sh/v3/ai",
256
+ npm: "@ai-sdk/gateway",
257
+ },
258
+ name: id,
259
+ capabilities: {
260
+ temperature: true,
261
+ reasoning: true,
262
+ attachment: true,
263
+ toolcall: true,
264
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
265
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
266
+ interleaved: false,
267
+ },
268
+ cost: {
269
+ input: 0.001,
270
+ output: 0.002,
271
+ cache: { read: 0.0001, write: 0.0002 },
272
+ },
273
+ limit: {
274
+ context: 200_000,
275
+ output: 8192,
276
+ },
277
+ status: "active",
278
+ options: {},
279
+ headers: {},
280
+ release_date: "2024-01-01",
281
+ }) as any
282
+
283
+ test("puts gateway defaults under gateway key", () => {
284
+ const model = createModel("anthropic/claude-sonnet-4")
285
+ const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
286
+ expect(result).toEqual({
287
+ gateway: {
288
+ caching: "auto",
289
+ },
290
+ })
291
+ })
292
+ })
293
+
294
+ describe("ProviderTransform.providerOptions", () => {
295
+ const createModel = (overrides: Partial<any> = {}) =>
296
+ ({
297
+ id: "test/test-model",
298
+ providerID: "test",
299
+ api: {
300
+ id: "test-model",
301
+ url: "https://api.test.com",
302
+ npm: "@ai-sdk/openai",
303
+ },
304
+ name: "Test Model",
305
+ capabilities: {
306
+ temperature: true,
307
+ reasoning: true,
308
+ attachment: true,
309
+ toolcall: true,
310
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
311
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
312
+ interleaved: false,
313
+ },
314
+ cost: {
315
+ input: 0.001,
316
+ output: 0.002,
317
+ cache: { read: 0.0001, write: 0.0002 },
318
+ },
319
+ limit: {
320
+ context: 200_000,
321
+ output: 64_000,
322
+ },
323
+ status: "active",
324
+ options: {},
325
+ headers: {},
326
+ release_date: "2024-01-01",
327
+ ...overrides,
328
+ }) as any
329
+
330
+ test("uses sdk key for non-gateway models", () => {
331
+ const model = createModel({
332
+ providerID: "my-bedrock",
333
+ api: {
334
+ id: "anthropic.claude-sonnet-4",
335
+ url: "https://bedrock.aws",
336
+ npm: "@ai-sdk/amazon-bedrock",
337
+ },
338
+ })
339
+
340
+ expect(ProviderTransform.providerOptions(model, { cachePoint: { type: "default" } })).toEqual({
341
+ bedrock: { cachePoint: { type: "default" } },
342
+ })
343
+ })
344
+
345
+ test("uses gateway model provider slug for gateway models", () => {
346
+ const model = createModel({
347
+ providerID: "vercel",
348
+ api: {
349
+ id: "anthropic/claude-sonnet-4",
350
+ url: "https://ai-gateway.vercel.sh/v3/ai",
351
+ npm: "@ai-sdk/gateway",
352
+ },
353
+ })
354
+
355
+ expect(ProviderTransform.providerOptions(model, { thinking: { type: "enabled", budgetTokens: 12_000 } })).toEqual({
356
+ anthropic: { thinking: { type: "enabled", budgetTokens: 12_000 } },
357
+ })
358
+ })
359
+
360
+ test("falls back to gateway key when gateway api id is unscoped", () => {
361
+ const model = createModel({
362
+ id: "anthropic/claude-sonnet-4",
363
+ providerID: "vercel",
364
+ api: {
365
+ id: "claude-sonnet-4",
366
+ url: "https://ai-gateway.vercel.sh/v3/ai",
367
+ npm: "@ai-sdk/gateway",
368
+ },
369
+ })
370
+
371
+ expect(ProviderTransform.providerOptions(model, { thinking: { type: "enabled", budgetTokens: 12_000 } })).toEqual({
372
+ gateway: { thinking: { type: "enabled", budgetTokens: 12_000 } },
373
+ })
374
+ })
375
+
376
+ test("splits gateway routing options from provider-specific options", () => {
377
+ const model = createModel({
378
+ providerID: "vercel",
379
+ api: {
380
+ id: "anthropic/claude-sonnet-4",
381
+ url: "https://ai-gateway.vercel.sh/v3/ai",
382
+ npm: "@ai-sdk/gateway",
383
+ },
384
+ })
385
+
386
+ expect(
387
+ ProviderTransform.providerOptions(model, {
388
+ gateway: { order: ["vertex", "anthropic"] },
389
+ thinking: { type: "enabled", budgetTokens: 12_000 },
390
+ }),
391
+ ).toEqual({
392
+ gateway: { order: ["vertex", "anthropic"] },
393
+ anthropic: { thinking: { type: "enabled", budgetTokens: 12_000 } },
394
+ } as any)
395
+ })
396
+
397
+ test("falls back to gateway key when model id has no provider slug", () => {
398
+ const model = createModel({
399
+ id: "claude-sonnet-4",
400
+ providerID: "vercel",
401
+ api: {
402
+ id: "claude-sonnet-4",
403
+ url: "https://ai-gateway.vercel.sh/v3/ai",
404
+ npm: "@ai-sdk/gateway",
405
+ },
406
+ })
407
+
408
+ expect(ProviderTransform.providerOptions(model, { reasoningEffort: "high" })).toEqual({
409
+ gateway: { reasoningEffort: "high" },
410
+ })
411
+ })
412
+
413
+ test("maps amazon slug to bedrock for provider options", () => {
414
+ const model = createModel({
415
+ providerID: "vercel",
416
+ api: {
417
+ id: "amazon/nova-2-lite",
418
+ url: "https://ai-gateway.vercel.sh/v3/ai",
419
+ npm: "@ai-sdk/gateway",
420
+ },
421
+ })
422
+
423
+ expect(ProviderTransform.providerOptions(model, { reasoningConfig: { type: "enabled" } })).toEqual({
424
+ bedrock: { reasoningConfig: { type: "enabled" } },
425
+ })
426
+ })
427
+
428
+ test("uses groq slug for groq models", () => {
429
+ const model = createModel({
430
+ providerID: "vercel",
431
+ api: {
432
+ id: "groq/llama-3.3-70b-versatile",
433
+ url: "https://ai-gateway.vercel.sh/v3/ai",
434
+ npm: "@ai-sdk/gateway",
435
+ },
436
+ })
437
+
438
+ expect(ProviderTransform.providerOptions(model, { reasoningFormat: "parsed" })).toEqual({
439
+ groq: { reasoningFormat: "parsed" },
440
+ })
441
+ })
442
+ })
443
+
444
+ describe("ProviderTransform.schema - gemini array items", () => {
445
+ test("adds missing items for array properties", () => {
446
+ const geminiModel = {
447
+ providerID: "google",
448
+ api: {
449
+ id: "gemini-3-pro",
450
+ },
451
+ } as any
452
+
453
+ const schema = {
454
+ type: "object",
455
+ properties: {
456
+ nodes: { type: "array" },
457
+ edges: { type: "array", items: { type: "string" } },
458
+ },
459
+ } as any
460
+
461
+ const result = ProviderTransform.schema(geminiModel, schema) as any
462
+
463
+ expect(result.properties.nodes.items).toBeDefined()
464
+ expect(result.properties.edges.items.type).toBe("string")
465
+ })
466
+ })
467
+
468
+ describe("ProviderTransform.schema - gemini nested array items", () => {
469
+ const geminiModel = {
470
+ providerID: "google",
471
+ api: {
472
+ id: "gemini-3-pro",
473
+ },
474
+ } as any
475
+
476
+ test("adds type to 2D array with empty inner items", () => {
477
+ const schema = {
478
+ type: "object",
479
+ properties: {
480
+ values: {
481
+ type: "array",
482
+ items: {
483
+ type: "array",
484
+ items: {}, // Empty items object
485
+ },
486
+ },
487
+ },
488
+ } as any
489
+
490
+ const result = ProviderTransform.schema(geminiModel, schema) as any
491
+
492
+ // Inner items should have a default type
493
+ expect(result.properties.values.items.items.type).toBe("string")
494
+ })
495
+
496
+ test("adds items and type to 2D array with missing inner items", () => {
497
+ const schema = {
498
+ type: "object",
499
+ properties: {
500
+ data: {
501
+ type: "array",
502
+ items: { type: "array" }, // No items at all
503
+ },
504
+ },
505
+ } as any
506
+
507
+ const result = ProviderTransform.schema(geminiModel, schema) as any
508
+
509
+ expect(result.properties.data.items.items).toBeDefined()
510
+ expect(result.properties.data.items.items.type).toBe("string")
511
+ })
512
+
513
+ test("handles deeply nested arrays (3D)", () => {
514
+ const schema = {
515
+ type: "object",
516
+ properties: {
517
+ matrix: {
518
+ type: "array",
519
+ items: {
520
+ type: "array",
521
+ items: {
522
+ type: "array",
523
+ // No items
524
+ },
525
+ },
526
+ },
527
+ },
528
+ } as any
529
+
530
+ const result = ProviderTransform.schema(geminiModel, schema) as any
531
+
532
+ expect(result.properties.matrix.items.items.items).toBeDefined()
533
+ expect(result.properties.matrix.items.items.items.type).toBe("string")
534
+ })
535
+
536
+ test("preserves existing item types in nested arrays", () => {
537
+ const schema = {
538
+ type: "object",
539
+ properties: {
540
+ numbers: {
541
+ type: "array",
542
+ items: {
543
+ type: "array",
544
+ items: { type: "number" }, // Has explicit type
545
+ },
546
+ },
547
+ },
548
+ } as any
549
+
550
+ const result = ProviderTransform.schema(geminiModel, schema) as any
551
+
552
+ // Should preserve the explicit type
553
+ expect(result.properties.numbers.items.items.type).toBe("number")
554
+ })
555
+
556
+ test("handles mixed nested structures with objects and arrays", () => {
557
+ const schema = {
558
+ type: "object",
559
+ properties: {
560
+ spreadsheetData: {
561
+ type: "object",
562
+ properties: {
563
+ rows: {
564
+ type: "array",
565
+ items: {
566
+ type: "array",
567
+ items: {}, // Empty items
568
+ },
569
+ },
570
+ },
571
+ },
572
+ },
573
+ } as any
574
+
575
+ const result = ProviderTransform.schema(geminiModel, schema) as any
576
+
577
+ expect(result.properties.spreadsheetData.properties.rows.items.items.type).toBe("string")
578
+ })
579
+ })
580
+
581
+ describe("ProviderTransform.schema - gemini combiner nodes", () => {
582
+ const geminiModel = {
583
+ providerID: "google",
584
+ api: {
585
+ id: "gemini-3-pro",
586
+ },
587
+ } as any
588
+
589
+ const walk = (node: any, cb: (node: any, path: (string | number)[]) => void, path: (string | number)[] = []) => {
590
+ if (node === null || typeof node !== "object") {
591
+ return
592
+ }
593
+ if (Array.isArray(node)) {
594
+ node.forEach((item, i) => walk(item, cb, [...path, i]))
595
+ return
596
+ }
597
+ cb(node, path)
598
+ Object.entries(node).forEach(([key, value]) => walk(value, cb, [...path, key]))
599
+ }
600
+
601
+ test("keeps edits.items.anyOf without adding type", () => {
602
+ const schema = {
603
+ type: "object",
604
+ properties: {
605
+ edits: {
606
+ type: "array",
607
+ items: {
608
+ anyOf: [
609
+ {
610
+ type: "object",
611
+ properties: {
612
+ old_string: { type: "string" },
613
+ new_string: { type: "string" },
614
+ },
615
+ required: ["old_string", "new_string"],
616
+ },
617
+ {
618
+ type: "object",
619
+ properties: {
620
+ old_string: { type: "string" },
621
+ new_string: { type: "string" },
622
+ replace_all: { type: "boolean" },
623
+ },
624
+ required: ["old_string", "new_string"],
625
+ },
626
+ ],
627
+ },
628
+ },
629
+ },
630
+ required: ["edits"],
631
+ } as any
632
+
633
+ const result = ProviderTransform.schema(geminiModel, schema) as any
634
+
635
+ expect(Array.isArray(result.properties.edits.items.anyOf)).toBe(true)
636
+ expect(result.properties.edits.items.type).toBeUndefined()
637
+ })
638
+
639
+ test("does not add sibling keys to combiner nodes during sanitize", () => {
640
+ const schema = {
641
+ type: "object",
642
+ properties: {
643
+ edits: {
644
+ type: "array",
645
+ items: {
646
+ anyOf: [{ type: "string" }, { type: "number" }],
647
+ },
648
+ },
649
+ value: {
650
+ oneOf: [{ type: "string" }, { type: "boolean" }],
651
+ },
652
+ meta: {
653
+ allOf: [
654
+ {
655
+ type: "object",
656
+ properties: { a: { type: "string" } },
657
+ },
658
+ {
659
+ type: "object",
660
+ properties: { b: { type: "string" } },
661
+ },
662
+ ],
663
+ },
664
+ },
665
+ } as any
666
+ const input = JSON.parse(JSON.stringify(schema))
667
+ const result = ProviderTransform.schema(geminiModel, schema) as any
668
+
669
+ walk(result, (node, path) => {
670
+ const hasCombiner = Array.isArray(node.anyOf) || Array.isArray(node.oneOf) || Array.isArray(node.allOf)
671
+ if (!hasCombiner) {
672
+ return
673
+ }
674
+ const before = path.reduce((acc: any, key) => acc?.[key], input)
675
+ const added = Object.keys(node).filter((key) => !(key in before))
676
+ expect(added).toEqual([])
677
+ })
678
+ })
679
+ })
680
+
681
+ describe("ProviderTransform.schema - gemini non-object properties removal", () => {
682
+ const geminiModel = {
683
+ providerID: "google",
684
+ api: {
685
+ id: "gemini-3-pro",
686
+ },
687
+ } as any
688
+
689
+ test("removes properties from non-object types", () => {
690
+ const schema = {
691
+ type: "object",
692
+ properties: {
693
+ data: {
694
+ type: "string",
695
+ properties: { invalid: { type: "string" } },
696
+ },
697
+ },
698
+ } as any
699
+
700
+ const result = ProviderTransform.schema(geminiModel, schema) as any
701
+
702
+ expect(result.properties.data.type).toBe("string")
703
+ expect(result.properties.data.properties).toBeUndefined()
704
+ })
705
+
706
+ test("removes required from non-object types", () => {
707
+ const schema = {
708
+ type: "object",
709
+ properties: {
710
+ data: {
711
+ type: "array",
712
+ items: { type: "string" },
713
+ required: ["invalid"],
714
+ },
715
+ },
716
+ } as any
717
+
718
+ const result = ProviderTransform.schema(geminiModel, schema) as any
719
+
720
+ expect(result.properties.data.type).toBe("array")
721
+ expect(result.properties.data.required).toBeUndefined()
722
+ })
723
+
724
+ test("removes properties and required from nested non-object types", () => {
725
+ const schema = {
726
+ type: "object",
727
+ properties: {
728
+ outer: {
729
+ type: "object",
730
+ properties: {
731
+ inner: {
732
+ type: "number",
733
+ properties: { bad: { type: "string" } },
734
+ required: ["bad"],
735
+ },
736
+ },
737
+ },
738
+ },
739
+ } as any
740
+
741
+ const result = ProviderTransform.schema(geminiModel, schema) as any
742
+
743
+ expect(result.properties.outer.properties.inner.type).toBe("number")
744
+ expect(result.properties.outer.properties.inner.properties).toBeUndefined()
745
+ expect(result.properties.outer.properties.inner.required).toBeUndefined()
746
+ })
747
+
748
+ test("keeps properties and required on object types", () => {
749
+ const schema = {
750
+ type: "object",
751
+ properties: {
752
+ data: {
753
+ type: "object",
754
+ properties: { name: { type: "string" } },
755
+ required: ["name"],
756
+ },
757
+ },
758
+ } as any
759
+
760
+ const result = ProviderTransform.schema(geminiModel, schema) as any
761
+
762
+ expect(result.properties.data.type).toBe("object")
763
+ expect(result.properties.data.properties).toBeDefined()
764
+ expect(result.properties.data.required).toEqual(["name"])
765
+ })
766
+
767
+ test("does not affect non-gemini providers", () => {
768
+ const openaiModel = {
769
+ providerID: "openai",
770
+ api: {
771
+ id: "gpt-4",
772
+ },
773
+ } as any
774
+
775
+ const schema = {
776
+ type: "object",
777
+ properties: {
778
+ data: {
779
+ type: "string",
780
+ properties: { invalid: { type: "string" } },
781
+ },
782
+ },
783
+ } as any
784
+
785
+ const result = ProviderTransform.schema(openaiModel, schema) as any
786
+
787
+ expect(result.properties.data.properties).toBeDefined()
788
+ })
789
+ })
790
+
791
+ describe("ProviderTransform.schema - gemma4 schema flattening", () => {
792
+ const gemma4Model = {
793
+ providerID: "local",
794
+ id: "gemma-4",
795
+ api: {
796
+ id: "gemma-4",
797
+ },
798
+ } as any
799
+
800
+ test("flattens nested object parameters", () => {
801
+ const schema = {
802
+ type: "object",
803
+ properties: {
804
+ data: {
805
+ type: "object",
806
+ description: "A nested object",
807
+ properties: { name: { type: "string" } },
808
+ required: ["name"],
809
+ },
810
+ simple: { type: "string" },
811
+ },
812
+ } as any
813
+
814
+ const result = ProviderTransform.schema(gemma4Model, schema) as any
815
+
816
+ // The root keeps its properties, but nested 'data' becomes a string
817
+ expect(result.properties.data.type).toBe("string")
818
+ expect(result.properties.data.properties).toBeUndefined()
819
+ expect(result.properties.data.required).toBeUndefined()
820
+ expect(result.properties.simple.type).toBe("string")
821
+ })
822
+
823
+ test("replaces double quotes with single quotes in descriptions", () => {
824
+ const schema = {
825
+ type: "object",
826
+ properties: {
827
+ data: {
828
+ type: "string",
829
+ description: 'This is a "description" with "quotes"',
830
+ },
831
+ },
832
+ } as any
833
+
834
+ const result = ProviderTransform.schema(gemma4Model, schema) as any
835
+
836
+ expect(result.properties.data.description).toBe("This is a 'description' with 'quotes'")
837
+ })
838
+
839
+ test("simplifies anyOf/oneOf/allOf to the first valid type", () => {
840
+ const schema = {
841
+ type: "object",
842
+ properties: {
843
+ data: {
844
+ anyOf: [{ type: "null" }, { type: "string" }, { type: "number" }],
845
+ },
846
+ data2: {
847
+ oneOf: [{ type: "boolean" }],
848
+ },
849
+ },
850
+ } as any
851
+
852
+ const result = ProviderTransform.schema(gemma4Model, schema) as any
853
+
854
+ expect(result.properties.data.type).toBe("string")
855
+ expect(result.properties.data.anyOf).toBeUndefined()
856
+ expect(result.properties.data2.type).toBe("boolean")
857
+ expect(result.properties.data2.oneOf).toBeUndefined()
858
+ })
859
+ })
860
+
861
+ describe("ProviderTransform.schema - qwen schema flattening", () => {
862
+ const qwenModel = {
863
+ providerID: "local-main",
864
+ id: "qwen-35b",
865
+ api: {
866
+ id: "qwen-35b",
867
+ },
868
+ } as any
869
+
870
+ test("flattens nested object parameters for qwen", () => {
871
+ const schema = {
872
+ type: "object",
873
+ properties: {
874
+ config: {
875
+ type: "object",
876
+ description: "Configuration object",
877
+ properties: {
878
+ debug: { type: "boolean" },
879
+ nested: {
880
+ type: "object",
881
+ properties: { key: { type: "string" } },
882
+ },
883
+ },
884
+ },
885
+ },
886
+ } as any
887
+
888
+ const result = ProviderTransform.schema(qwenModel, schema) as any
889
+
890
+ expect(result.properties.config.type).toBe("string")
891
+ expect(result.properties.config.properties).toBeUndefined()
892
+ })
893
+ })
894
+
895
+ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
896
+ test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
897
+ const msgs = [
898
+ {
899
+ role: "assistant",
900
+ content: [
901
+ { type: "reasoning", text: "Let me think about this..." },
902
+ {
903
+ type: "tool-call",
904
+ toolCallId: "test",
905
+ toolName: "bash",
906
+ input: { command: "echo hello" },
907
+ },
908
+ ],
909
+ },
910
+ ] as any[]
911
+
912
+ const result = ProviderTransform.message(
913
+ msgs,
914
+ {
915
+ id: ModelID.make("deepseek/deepseek-chat"),
916
+ providerID: ProviderID.make("deepseek"),
917
+ api: {
918
+ id: "deepseek-chat",
919
+ url: "https://api.deepseek.com",
920
+ npm: "@ai-sdk/openai-compatible",
921
+ },
922
+ name: "DeepSeek Chat",
923
+ capabilities: {
924
+ temperature: true,
925
+ reasoning: true,
926
+ attachment: false,
927
+ toolcall: true,
928
+ input: { text: true, audio: false, image: false, video: false, pdf: false },
929
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
930
+ interleaved: {
931
+ field: "reasoning_content",
932
+ },
933
+ },
934
+ cost: {
935
+ input: 0.001,
936
+ output: 0.002,
937
+ cache: { read: 0.0001, write: 0.0002 },
938
+ },
939
+ limit: {
940
+ context: 128000,
941
+ output: 8192,
942
+ },
943
+ status: "active",
944
+ options: {},
945
+ headers: {},
946
+ release_date: "2023-04-01",
947
+ },
948
+ {},
949
+ )
950
+
951
+ expect(result).toHaveLength(1)
952
+ expect(result[0].content).toEqual([
953
+ {
954
+ type: "tool-call",
955
+ toolCallId: "test",
956
+ toolName: "bash",
957
+ input: { command: "echo hello" },
958
+ },
959
+ ])
960
+ expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Let me think about this...")
961
+ })
962
+
963
+ test("Non-DeepSeek providers leave reasoning content unchanged", () => {
964
+ const msgs = [
965
+ {
966
+ role: "assistant",
967
+ content: [
968
+ { type: "reasoning", text: "Should not be processed" },
969
+ { type: "text", text: "Answer" },
970
+ ],
971
+ },
972
+ ] as any[]
973
+
974
+ const result = ProviderTransform.message(
975
+ msgs,
976
+ {
977
+ id: ModelID.make("openai/gpt-4"),
978
+ providerID: ProviderID.make("openai"),
979
+ api: {
980
+ id: "gpt-4",
981
+ url: "https://api.openai.com",
982
+ npm: "@ai-sdk/openai",
983
+ },
984
+ name: "GPT-4",
985
+ capabilities: {
986
+ temperature: true,
987
+ reasoning: false,
988
+ attachment: true,
989
+ toolcall: true,
990
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
991
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
992
+ interleaved: false,
993
+ },
994
+ cost: {
995
+ input: 0.03,
996
+ output: 0.06,
997
+ cache: { read: 0.001, write: 0.002 },
998
+ },
999
+ limit: {
1000
+ context: 128000,
1001
+ output: 4096,
1002
+ },
1003
+ status: "active",
1004
+ options: {},
1005
+ headers: {},
1006
+ release_date: "2023-04-01",
1007
+ },
1008
+ {},
1009
+ )
1010
+
1011
+ expect(result[0].content).toEqual([
1012
+ { type: "reasoning", text: "Should not be processed" },
1013
+ { type: "text", text: "Answer" },
1014
+ ])
1015
+ expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBeUndefined()
1016
+ })
1017
+ })
1018
+
1019
+ describe("ProviderTransform.message - empty image handling", () => {
1020
+ const mockModel = {
1021
+ id: "anthropic/claude-3-5-sonnet",
1022
+ providerID: "anthropic",
1023
+ api: {
1024
+ id: "claude-3-5-sonnet-20241022",
1025
+ url: "https://api.anthropic.com",
1026
+ npm: "@ai-sdk/anthropic",
1027
+ },
1028
+ name: "Claude 3.5 Sonnet",
1029
+ capabilities: {
1030
+ temperature: true,
1031
+ reasoning: false,
1032
+ attachment: true,
1033
+ toolcall: true,
1034
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
1035
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1036
+ interleaved: false,
1037
+ },
1038
+ cost: {
1039
+ input: 0.003,
1040
+ output: 0.015,
1041
+ cache: { read: 0.0003, write: 0.00375 },
1042
+ },
1043
+ limit: {
1044
+ context: 200000,
1045
+ output: 8192,
1046
+ },
1047
+ status: "active",
1048
+ options: {},
1049
+ headers: {},
1050
+ } as any
1051
+
1052
+ test("should replace empty base64 image with error text", () => {
1053
+ const msgs = [
1054
+ {
1055
+ role: "user",
1056
+ content: [
1057
+ { type: "text", text: "What is in this image?" },
1058
+ { type: "image", image: "data:image/png;base64," },
1059
+ ],
1060
+ },
1061
+ ] as any[]
1062
+
1063
+ const result = ProviderTransform.message(msgs, mockModel, {})
1064
+
1065
+ expect(result).toHaveLength(1)
1066
+ expect(result[0].content).toHaveLength(2)
1067
+ expect(result[0].content[0]).toEqual({ type: "text", text: "What is in this image?" })
1068
+ expect(result[0].content[1]).toEqual({
1069
+ type: "text",
1070
+ text: "ERROR: Image file is empty or corrupted. Please provide a valid image.",
1071
+ })
1072
+ })
1073
+
1074
+ test("should keep valid base64 images unchanged", () => {
1075
+ const validBase64 =
1076
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
1077
+ const msgs = [
1078
+ {
1079
+ role: "user",
1080
+ content: [
1081
+ { type: "text", text: "What is in this image?" },
1082
+ { type: "image", image: `data:image/png;base64,${validBase64}` },
1083
+ ],
1084
+ },
1085
+ ] as any[]
1086
+
1087
+ const result = ProviderTransform.message(msgs, mockModel, {})
1088
+
1089
+ expect(result).toHaveLength(1)
1090
+ expect(result[0].content).toHaveLength(2)
1091
+ expect(result[0].content[0]).toEqual({ type: "text", text: "What is in this image?" })
1092
+ expect(result[0].content[1]).toEqual({ type: "image", image: `data:image/png;base64,${validBase64}` })
1093
+ })
1094
+
1095
+ test("should handle mixed valid and empty images", () => {
1096
+ const validBase64 =
1097
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
1098
+ const msgs = [
1099
+ {
1100
+ role: "user",
1101
+ content: [
1102
+ { type: "text", text: "Compare these images" },
1103
+ { type: "image", image: `data:image/png;base64,${validBase64}` },
1104
+ { type: "image", image: "data:image/jpeg;base64," },
1105
+ ],
1106
+ },
1107
+ ] as any[]
1108
+
1109
+ const result = ProviderTransform.message(msgs, mockModel, {})
1110
+
1111
+ expect(result).toHaveLength(1)
1112
+ expect(result[0].content).toHaveLength(3)
1113
+ expect(result[0].content[0]).toEqual({ type: "text", text: "Compare these images" })
1114
+ expect(result[0].content[1]).toEqual({ type: "image", image: `data:image/png;base64,${validBase64}` })
1115
+ expect(result[0].content[2]).toEqual({
1116
+ type: "text",
1117
+ text: "ERROR: Image file is empty or corrupted. Please provide a valid image.",
1118
+ })
1119
+ })
1120
+ })
1121
+
1122
+ describe("ProviderTransform.message - anthropic empty content filtering", () => {
1123
+ const anthropicModel = {
1124
+ id: "anthropic/claude-3-5-sonnet",
1125
+ providerID: "anthropic",
1126
+ api: {
1127
+ id: "claude-3-5-sonnet-20241022",
1128
+ url: "https://api.anthropic.com",
1129
+ npm: "@ai-sdk/anthropic",
1130
+ },
1131
+ name: "Claude 3.5 Sonnet",
1132
+ capabilities: {
1133
+ temperature: true,
1134
+ reasoning: false,
1135
+ attachment: true,
1136
+ toolcall: true,
1137
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
1138
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1139
+ interleaved: false,
1140
+ },
1141
+ cost: {
1142
+ input: 0.003,
1143
+ output: 0.015,
1144
+ cache: { read: 0.0003, write: 0.00375 },
1145
+ },
1146
+ limit: {
1147
+ context: 200000,
1148
+ output: 8192,
1149
+ },
1150
+ status: "active",
1151
+ options: {},
1152
+ headers: {},
1153
+ } as any
1154
+
1155
+ test("filters out messages with empty string content", () => {
1156
+ const msgs = [
1157
+ { role: "user", content: "Hello" },
1158
+ { role: "assistant", content: "" },
1159
+ { role: "user", content: "World" },
1160
+ ] as any[]
1161
+
1162
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1163
+
1164
+ expect(result).toHaveLength(2)
1165
+ expect(result[0].content).toBe("Hello")
1166
+ expect(result[1].content).toBe("World")
1167
+ })
1168
+
1169
+ test("filters out empty text parts from array content", () => {
1170
+ const msgs = [
1171
+ {
1172
+ role: "assistant",
1173
+ content: [
1174
+ { type: "text", text: "" },
1175
+ { type: "text", text: "Hello" },
1176
+ { type: "text", text: "" },
1177
+ ],
1178
+ },
1179
+ ] as any[]
1180
+
1181
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1182
+
1183
+ expect(result).toHaveLength(1)
1184
+ expect(result[0].content).toHaveLength(1)
1185
+ expect(result[0].content[0]).toEqual({ type: "text", text: "Hello" })
1186
+ })
1187
+
1188
+ test("filters out empty reasoning parts from array content", () => {
1189
+ const msgs = [
1190
+ {
1191
+ role: "assistant",
1192
+ content: [
1193
+ { type: "reasoning", text: "" },
1194
+ { type: "text", text: "Answer" },
1195
+ { type: "reasoning", text: "" },
1196
+ ],
1197
+ },
1198
+ ] as any[]
1199
+
1200
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1201
+
1202
+ expect(result).toHaveLength(1)
1203
+ expect(result[0].content).toHaveLength(1)
1204
+ expect(result[0].content[0]).toEqual({ type: "text", text: "Answer" })
1205
+ })
1206
+
1207
+ test("removes entire message when all parts are empty", () => {
1208
+ const msgs = [
1209
+ { role: "user", content: "Hello" },
1210
+ {
1211
+ role: "assistant",
1212
+ content: [
1213
+ { type: "text", text: "" },
1214
+ { type: "reasoning", text: "" },
1215
+ ],
1216
+ },
1217
+ { role: "user", content: "World" },
1218
+ ] as any[]
1219
+
1220
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1221
+
1222
+ expect(result).toHaveLength(2)
1223
+ expect(result[0].content).toBe("Hello")
1224
+ expect(result[1].content).toBe("World")
1225
+ })
1226
+
1227
+ test("keeps non-text/reasoning parts even if text parts are empty", () => {
1228
+ const msgs = [
1229
+ {
1230
+ role: "assistant",
1231
+ content: [
1232
+ { type: "text", text: "" },
1233
+ { type: "tool-call", toolCallId: "123", toolName: "bash", input: { command: "ls" } },
1234
+ ],
1235
+ },
1236
+ ] as any[]
1237
+
1238
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1239
+
1240
+ expect(result).toHaveLength(1)
1241
+ expect(result[0].content).toHaveLength(1)
1242
+ expect(result[0].content[0]).toEqual({
1243
+ type: "tool-call",
1244
+ toolCallId: "123",
1245
+ toolName: "bash",
1246
+ input: { command: "ls" },
1247
+ })
1248
+ })
1249
+
1250
+ test("keeps messages with valid text alongside empty parts", () => {
1251
+ const msgs = [
1252
+ {
1253
+ role: "assistant",
1254
+ content: [
1255
+ { type: "reasoning", text: "Thinking..." },
1256
+ { type: "text", text: "" },
1257
+ { type: "text", text: "Result" },
1258
+ ],
1259
+ },
1260
+ ] as any[]
1261
+
1262
+ const result = ProviderTransform.message(msgs, anthropicModel, {})
1263
+
1264
+ expect(result).toHaveLength(1)
1265
+ expect(result[0].content).toHaveLength(2)
1266
+ expect(result[0].content[0]).toEqual({ type: "reasoning", text: "Thinking..." })
1267
+ expect(result[0].content[1]).toEqual({ type: "text", text: "Result" })
1268
+ })
1269
+
1270
+ test("filters empty content for bedrock provider", () => {
1271
+ const bedrockModel = {
1272
+ ...anthropicModel,
1273
+ id: "amazon-bedrock/anthropic.claude-opus-4-6",
1274
+ providerID: "amazon-bedrock",
1275
+ api: {
1276
+ id: "anthropic.claude-opus-4-6",
1277
+ url: "https://bedrock-runtime.us-east-1.amazonaws.com",
1278
+ npm: "@ai-sdk/amazon-bedrock",
1279
+ },
1280
+ }
1281
+
1282
+ const msgs = [
1283
+ { role: "user", content: "Hello" },
1284
+ { role: "assistant", content: "" },
1285
+ {
1286
+ role: "assistant",
1287
+ content: [
1288
+ { type: "text", text: "" },
1289
+ { type: "text", text: "Answer" },
1290
+ ],
1291
+ },
1292
+ ] as any[]
1293
+
1294
+ const result = ProviderTransform.message(msgs, bedrockModel, {})
1295
+
1296
+ expect(result).toHaveLength(2)
1297
+ expect(result[0].content).toBe("Hello")
1298
+ expect(result[1].content).toHaveLength(1)
1299
+ expect(result[1].content[0]).toEqual({ type: "text", text: "Answer" })
1300
+ })
1301
+
1302
+ test("does not filter for non-anthropic providers", () => {
1303
+ const openaiModel = {
1304
+ ...anthropicModel,
1305
+ providerID: "openai",
1306
+ api: {
1307
+ id: "gpt-4",
1308
+ url: "https://api.openai.com",
1309
+ npm: "@ai-sdk/openai",
1310
+ },
1311
+ }
1312
+
1313
+ const msgs = [
1314
+ { role: "assistant", content: "" },
1315
+ {
1316
+ role: "assistant",
1317
+ content: [{ type: "text", text: "" }],
1318
+ },
1319
+ ] as any[]
1320
+
1321
+ const result = ProviderTransform.message(msgs, openaiModel, {})
1322
+
1323
+ expect(result).toHaveLength(2)
1324
+ expect(result[0].content).toBe("")
1325
+ expect(result[1].content).toHaveLength(1)
1326
+ })
1327
+ })
1328
+
1329
+ describe("ProviderTransform.message - strip openai metadata when store=false", () => {
1330
+ const openaiModel = {
1331
+ id: "openai/gpt-5",
1332
+ providerID: "openai",
1333
+ api: {
1334
+ id: "gpt-5",
1335
+ url: "https://api.openai.com",
1336
+ npm: "@ai-sdk/openai",
1337
+ },
1338
+ name: "GPT-5",
1339
+ capabilities: {
1340
+ temperature: true,
1341
+ reasoning: true,
1342
+ attachment: true,
1343
+ toolcall: true,
1344
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
1345
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1346
+ interleaved: false,
1347
+ },
1348
+ cost: { input: 0.03, output: 0.06, cache: { read: 0.001, write: 0.002 } },
1349
+ limit: { context: 128000, output: 4096 },
1350
+ status: "active",
1351
+ options: {},
1352
+ headers: {},
1353
+ } as any
1354
+
1355
+ test("preserves itemId and reasoningEncryptedContent when store=false", () => {
1356
+ const msgs = [
1357
+ {
1358
+ role: "assistant",
1359
+ content: [
1360
+ {
1361
+ type: "reasoning",
1362
+ text: "thinking...",
1363
+ providerOptions: {
1364
+ openai: {
1365
+ itemId: "rs_123",
1366
+ reasoningEncryptedContent: "encrypted",
1367
+ },
1368
+ },
1369
+ },
1370
+ {
1371
+ type: "text",
1372
+ text: "Hello",
1373
+ providerOptions: {
1374
+ openai: {
1375
+ itemId: "msg_456",
1376
+ },
1377
+ },
1378
+ },
1379
+ ],
1380
+ },
1381
+ ] as any[]
1382
+
1383
+ const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
1384
+
1385
+ expect(result).toHaveLength(1)
1386
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
1387
+ expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
1388
+ })
1389
+
1390
+ test("preserves itemId and reasoningEncryptedContent when store=false even when not openai", () => {
1391
+ const zenModel = {
1392
+ ...openaiModel,
1393
+ providerID: "zen",
1394
+ }
1395
+ const msgs = [
1396
+ {
1397
+ role: "assistant",
1398
+ content: [
1399
+ {
1400
+ type: "reasoning",
1401
+ text: "thinking...",
1402
+ providerOptions: {
1403
+ openai: {
1404
+ itemId: "rs_123",
1405
+ reasoningEncryptedContent: "encrypted",
1406
+ },
1407
+ },
1408
+ },
1409
+ {
1410
+ type: "text",
1411
+ text: "Hello",
1412
+ providerOptions: {
1413
+ openai: {
1414
+ itemId: "msg_456",
1415
+ },
1416
+ },
1417
+ },
1418
+ ],
1419
+ },
1420
+ ] as any[]
1421
+
1422
+ const result = ProviderTransform.message(msgs, zenModel, { store: false }) as any[]
1423
+
1424
+ expect(result).toHaveLength(1)
1425
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("rs_123")
1426
+ expect(result[0].content[1].providerOptions?.openai?.itemId).toBe("msg_456")
1427
+ })
1428
+
1429
+ test("preserves other openai options including itemId", () => {
1430
+ const msgs = [
1431
+ {
1432
+ role: "assistant",
1433
+ content: [
1434
+ {
1435
+ type: "text",
1436
+ text: "Hello",
1437
+ providerOptions: {
1438
+ openai: {
1439
+ itemId: "msg_123",
1440
+ otherOption: "value",
1441
+ },
1442
+ },
1443
+ },
1444
+ ],
1445
+ },
1446
+ ] as any[]
1447
+
1448
+ const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[]
1449
+
1450
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1451
+ expect(result[0].content[0].providerOptions?.openai?.otherOption).toBe("value")
1452
+ })
1453
+
1454
+ test("preserves metadata for openai package when store is true", () => {
1455
+ const msgs = [
1456
+ {
1457
+ role: "assistant",
1458
+ content: [
1459
+ {
1460
+ type: "text",
1461
+ text: "Hello",
1462
+ providerOptions: {
1463
+ openai: {
1464
+ itemId: "msg_123",
1465
+ },
1466
+ },
1467
+ },
1468
+ ],
1469
+ },
1470
+ ] as any[]
1471
+
1472
+ // openai package preserves itemId regardless of store value
1473
+ const result = ProviderTransform.message(msgs, openaiModel, { store: true }) as any[]
1474
+
1475
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1476
+ })
1477
+
1478
+ test("preserves metadata for non-openai packages when store is false", () => {
1479
+ const anthropicModel = {
1480
+ ...openaiModel,
1481
+ providerID: "anthropic",
1482
+ api: {
1483
+ id: "claude-3",
1484
+ url: "https://api.anthropic.com",
1485
+ npm: "@ai-sdk/anthropic",
1486
+ },
1487
+ }
1488
+ const msgs = [
1489
+ {
1490
+ role: "assistant",
1491
+ content: [
1492
+ {
1493
+ type: "text",
1494
+ text: "Hello",
1495
+ providerOptions: {
1496
+ openai: {
1497
+ itemId: "msg_123",
1498
+ },
1499
+ },
1500
+ },
1501
+ ],
1502
+ },
1503
+ ] as any[]
1504
+
1505
+ // store=false preserves metadata for non-openai packages
1506
+ const result = ProviderTransform.message(msgs, anthropicModel, { store: false }) as any[]
1507
+
1508
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1509
+ })
1510
+
1511
+ test("preserves metadata using providerID key when store is false", () => {
1512
+ const epochcliModel = {
1513
+ ...openaiModel,
1514
+ providerID: "epochcli",
1515
+ api: {
1516
+ id: "epochcli-test",
1517
+ url: "https://api.epochcli.ai",
1518
+ npm: "@ai-sdk/openai-compatible",
1519
+ },
1520
+ }
1521
+ const msgs = [
1522
+ {
1523
+ role: "assistant",
1524
+ content: [
1525
+ {
1526
+ type: "text",
1527
+ text: "Hello",
1528
+ providerOptions: {
1529
+ epochcli: {
1530
+ itemId: "msg_123",
1531
+ otherOption: "value",
1532
+ },
1533
+ },
1534
+ },
1535
+ ],
1536
+ },
1537
+ ] as any[]
1538
+
1539
+ const result = ProviderTransform.message(msgs, epochcliModel, { store: false }) as any[]
1540
+
1541
+ expect(result[0].content[0].providerOptions?.epochcli?.itemId).toBe("msg_123")
1542
+ expect(result[0].content[0].providerOptions?.epochcli?.otherOption).toBe("value")
1543
+ })
1544
+
1545
+ test("preserves itemId across all providerOptions keys", () => {
1546
+ const epochcliModel = {
1547
+ ...openaiModel,
1548
+ providerID: "epochcli",
1549
+ api: {
1550
+ id: "epochcli-test",
1551
+ url: "https://api.epochcli.ai",
1552
+ npm: "@ai-sdk/openai-compatible",
1553
+ },
1554
+ }
1555
+ const msgs = [
1556
+ {
1557
+ role: "assistant",
1558
+ providerOptions: {
1559
+ openai: { itemId: "msg_root" },
1560
+ epochcli: { itemId: "msg_epochcli" },
1561
+ extra: { itemId: "msg_extra" },
1562
+ },
1563
+ content: [
1564
+ {
1565
+ type: "text",
1566
+ text: "Hello",
1567
+ providerOptions: {
1568
+ openai: { itemId: "msg_openai_part" },
1569
+ epochcli: { itemId: "msg_epochcli_part" },
1570
+ extra: { itemId: "msg_extra_part" },
1571
+ },
1572
+ },
1573
+ ],
1574
+ },
1575
+ ] as any[]
1576
+
1577
+ const result = ProviderTransform.message(msgs, epochcliModel, { store: false }) as any[]
1578
+
1579
+ expect(result[0].providerOptions?.openai?.itemId).toBe("msg_root")
1580
+ expect(result[0].providerOptions?.epochcli?.itemId).toBe("msg_epochcli")
1581
+ expect(result[0].providerOptions?.extra?.itemId).toBe("msg_extra")
1582
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_openai_part")
1583
+ expect(result[0].content[0].providerOptions?.epochcli?.itemId).toBe("msg_epochcli_part")
1584
+ expect(result[0].content[0].providerOptions?.extra?.itemId).toBe("msg_extra_part")
1585
+ })
1586
+
1587
+ test("does not strip metadata for non-openai packages when store is not false", () => {
1588
+ const anthropicModel = {
1589
+ ...openaiModel,
1590
+ providerID: "anthropic",
1591
+ api: {
1592
+ id: "claude-3",
1593
+ url: "https://api.anthropic.com",
1594
+ npm: "@ai-sdk/anthropic",
1595
+ },
1596
+ }
1597
+ const msgs = [
1598
+ {
1599
+ role: "assistant",
1600
+ content: [
1601
+ {
1602
+ type: "text",
1603
+ text: "Hello",
1604
+ providerOptions: {
1605
+ openai: {
1606
+ itemId: "msg_123",
1607
+ },
1608
+ },
1609
+ },
1610
+ ],
1611
+ },
1612
+ ] as any[]
1613
+
1614
+ const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
1615
+
1616
+ expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123")
1617
+ })
1618
+ })
1619
+
1620
+ describe("ProviderTransform.message - providerOptions key remapping", () => {
1621
+ const createModel = (providerID: string, npm: string) =>
1622
+ ({
1623
+ id: `${providerID}/test-model`,
1624
+ providerID,
1625
+ api: {
1626
+ id: "test-model",
1627
+ url: "https://api.test.com",
1628
+ npm,
1629
+ },
1630
+ name: "Test Model",
1631
+ capabilities: {
1632
+ temperature: true,
1633
+ reasoning: false,
1634
+ attachment: true,
1635
+ toolcall: true,
1636
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
1637
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1638
+ interleaved: false,
1639
+ },
1640
+ cost: { input: 0.001, output: 0.002, cache: { read: 0.0001, write: 0.0002 } },
1641
+ limit: { context: 128000, output: 8192 },
1642
+ status: "active",
1643
+ options: {},
1644
+ headers: {},
1645
+ }) as any
1646
+
1647
+ test("azure keeps 'azure' key and does not remap to 'openai'", () => {
1648
+ const model = createModel("azure", "@ai-sdk/azure")
1649
+ const msgs = [
1650
+ {
1651
+ role: "user",
1652
+ content: "Hello",
1653
+ providerOptions: {
1654
+ azure: { someOption: "value" },
1655
+ },
1656
+ },
1657
+ ] as any[]
1658
+
1659
+ const result = ProviderTransform.message(msgs, model, {})
1660
+
1661
+ expect(result[0].providerOptions?.azure).toEqual({ someOption: "value" })
1662
+ expect(result[0].providerOptions?.openai).toBeUndefined()
1663
+ })
1664
+
1665
+ test("azure cognitive services remaps providerID to 'azure' key", () => {
1666
+ const model = createModel("azure-cognitive-services", "@ai-sdk/azure")
1667
+ const msgs = [
1668
+ {
1669
+ role: "user",
1670
+ content: [
1671
+ {
1672
+ type: "text",
1673
+ text: "Hello",
1674
+ providerOptions: {
1675
+ "azure-cognitive-services": { part: true },
1676
+ },
1677
+ },
1678
+ ],
1679
+ providerOptions: {
1680
+ "azure-cognitive-services": { someOption: "value" },
1681
+ },
1682
+ },
1683
+ ] as any[]
1684
+
1685
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
1686
+ const part = result[0].content[0] as any
1687
+
1688
+ expect(result[0].providerOptions?.azure).toEqual({ someOption: "value" })
1689
+ expect(result[0].providerOptions?.["azure-cognitive-services"]).toBeUndefined()
1690
+ expect(part.providerOptions?.azure).toEqual({ part: true })
1691
+ expect(part.providerOptions?.["azure-cognitive-services"]).toBeUndefined()
1692
+ })
1693
+
1694
+ test("copilot remaps providerID to 'copilot' key", () => {
1695
+ const model = createModel("github-copilot", "@ai-sdk/github-copilot")
1696
+ const msgs = [
1697
+ {
1698
+ role: "user",
1699
+ content: "Hello",
1700
+ providerOptions: {
1701
+ copilot: { someOption: "value" },
1702
+ },
1703
+ },
1704
+ ] as any[]
1705
+
1706
+ const result = ProviderTransform.message(msgs, model, {})
1707
+
1708
+ expect(result[0].providerOptions?.copilot).toEqual({ someOption: "value" })
1709
+ expect(result[0].providerOptions?.["github-copilot"]).toBeUndefined()
1710
+ })
1711
+
1712
+ test("bedrock remaps providerID to 'bedrock' key", () => {
1713
+ const model = createModel("my-bedrock", "@ai-sdk/amazon-bedrock")
1714
+ const msgs = [
1715
+ {
1716
+ role: "user",
1717
+ content: "Hello",
1718
+ providerOptions: {
1719
+ "my-bedrock": { someOption: "value" },
1720
+ },
1721
+ },
1722
+ ] as any[]
1723
+
1724
+ const result = ProviderTransform.message(msgs, model, {})
1725
+
1726
+ expect(result[0].providerOptions?.bedrock).toEqual({ someOption: "value" })
1727
+ expect(result[0].providerOptions?.["my-bedrock"]).toBeUndefined()
1728
+ })
1729
+ })
1730
+
1731
+ describe("ProviderTransform.message - claude w/bedrock custom inference profile", () => {
1732
+ test("adds cachePoint", () => {
1733
+ const model = {
1734
+ id: "amazon-bedrock/custom-claude-sonnet-4.5",
1735
+ providerID: "amazon-bedrock",
1736
+ api: {
1737
+ id: "arn:aws:bedrock:xxx:yyy:application-inference-profile/zzz",
1738
+ url: "https://api.test.com",
1739
+ npm: "@ai-sdk/amazon-bedrock",
1740
+ },
1741
+ name: "Custom inference profile",
1742
+ capabilities: {},
1743
+ options: {},
1744
+ headers: {},
1745
+ } as any
1746
+
1747
+ const msgs = [
1748
+ {
1749
+ role: "user",
1750
+ content: "Hello",
1751
+ },
1752
+ ] as any[]
1753
+
1754
+ const result = ProviderTransform.message(msgs, model, {})
1755
+
1756
+ expect(result[0].providerOptions?.bedrock).toEqual(
1757
+ expect.objectContaining({
1758
+ cachePoint: {
1759
+ type: "default",
1760
+ },
1761
+ }),
1762
+ )
1763
+ })
1764
+ })
1765
+
1766
+ describe("ProviderTransform.message - bedrock caching with non-bedrock providerID", () => {
1767
+ test("applies cache options at message level when npm package is amazon-bedrock", () => {
1768
+ const model = {
1769
+ id: "aws/us.anthropic.claude-opus-4-6-v1",
1770
+ providerID: "aws",
1771
+ api: {
1772
+ id: "us.anthropic.claude-opus-4-6-v1",
1773
+ url: "https://bedrock-runtime.us-east-1.amazonaws.com",
1774
+ npm: "@ai-sdk/amazon-bedrock",
1775
+ },
1776
+ name: "Claude Opus 4.6",
1777
+ capabilities: {},
1778
+ options: {},
1779
+ headers: {},
1780
+ } as any
1781
+
1782
+ const msgs = [
1783
+ {
1784
+ role: "system",
1785
+ content: [{ type: "text", text: "You are a helpful assistant" }],
1786
+ },
1787
+ {
1788
+ role: "user",
1789
+ content: [{ type: "text", text: "Hello" }],
1790
+ },
1791
+ ] as any[]
1792
+
1793
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
1794
+
1795
+ // Cache should be at the message level and not the content-part level
1796
+ expect(result[0].providerOptions?.bedrock).toEqual({
1797
+ cachePoint: { type: "default" },
1798
+ })
1799
+ expect(result[0].content[0].providerOptions?.bedrock).toBeUndefined()
1800
+ })
1801
+ })
1802
+
1803
+ describe("ProviderTransform.message - cache control on gateway", () => {
1804
+ const createModel = (overrides: Partial<any> = {}) =>
1805
+ ({
1806
+ id: "anthropic/claude-sonnet-4",
1807
+ providerID: "vercel",
1808
+ api: {
1809
+ id: "anthropic/claude-sonnet-4",
1810
+ url: "https://ai-gateway.vercel.sh/v3/ai",
1811
+ npm: "@ai-sdk/gateway",
1812
+ },
1813
+ name: "Claude Sonnet 4",
1814
+ capabilities: {
1815
+ temperature: true,
1816
+ reasoning: true,
1817
+ attachment: true,
1818
+ toolcall: true,
1819
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
1820
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1821
+ interleaved: false,
1822
+ },
1823
+ cost: { input: 0.001, output: 0.002, cache: { read: 0.0001, write: 0.0002 } },
1824
+ limit: { context: 200_000, output: 8192 },
1825
+ status: "active",
1826
+ options: {},
1827
+ headers: {},
1828
+ ...overrides,
1829
+ }) as any
1830
+
1831
+ test("gateway does not set cache control for anthropic models", () => {
1832
+ const model = createModel()
1833
+ const msgs = [
1834
+ {
1835
+ role: "system",
1836
+ content: [{ type: "text", text: "You are a helpful assistant" }],
1837
+ },
1838
+ {
1839
+ role: "user",
1840
+ content: "Hello",
1841
+ },
1842
+ ] as any[]
1843
+
1844
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
1845
+
1846
+ expect(result[0].content[0].providerOptions).toBeUndefined()
1847
+ expect(result[0].providerOptions).toBeUndefined()
1848
+ })
1849
+
1850
+ test("non-gateway anthropic keeps existing cache control behavior", () => {
1851
+ const model = createModel({
1852
+ providerID: "anthropic",
1853
+ api: {
1854
+ id: "claude-sonnet-4",
1855
+ url: "https://api.anthropic.com",
1856
+ npm: "@ai-sdk/anthropic",
1857
+ },
1858
+ })
1859
+ const msgs = [
1860
+ {
1861
+ role: "system",
1862
+ content: "You are a helpful assistant",
1863
+ },
1864
+ {
1865
+ role: "user",
1866
+ content: "Hello",
1867
+ },
1868
+ ] as any[]
1869
+
1870
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
1871
+
1872
+ expect(result[0].providerOptions).toEqual({
1873
+ anthropic: {
1874
+ cacheControl: {
1875
+ type: "ephemeral",
1876
+ },
1877
+ },
1878
+ openrouter: {
1879
+ cacheControl: {
1880
+ type: "ephemeral",
1881
+ },
1882
+ },
1883
+ bedrock: {
1884
+ cachePoint: {
1885
+ type: "default",
1886
+ },
1887
+ },
1888
+ openaiCompatible: {
1889
+ cache_control: {
1890
+ type: "ephemeral",
1891
+ },
1892
+ },
1893
+ copilot: {
1894
+ copilot_cache_control: {
1895
+ type: "ephemeral",
1896
+ },
1897
+ },
1898
+ })
1899
+ })
1900
+
1901
+ test("google-vertex-anthropic applies cache control", () => {
1902
+ const model = createModel({
1903
+ providerID: "google-vertex-anthropic",
1904
+ api: {
1905
+ id: "google-vertex-anthropic",
1906
+ url: "https://us-central1-aiplatform.googleapis.com",
1907
+ npm: "@ai-sdk/google-vertex/anthropic",
1908
+ },
1909
+ id: "claude-sonnet-4@20250514",
1910
+ })
1911
+ const msgs = [
1912
+ {
1913
+ role: "system",
1914
+ content: "You are a helpful assistant",
1915
+ },
1916
+ {
1917
+ role: "user",
1918
+ content: "Hello",
1919
+ },
1920
+ ] as any[]
1921
+
1922
+ const result = ProviderTransform.message(msgs, model, {}) as any[]
1923
+
1924
+ expect(result[0].providerOptions).toEqual({
1925
+ anthropic: {
1926
+ cacheControl: {
1927
+ type: "ephemeral",
1928
+ },
1929
+ },
1930
+ openrouter: {
1931
+ cacheControl: {
1932
+ type: "ephemeral",
1933
+ },
1934
+ },
1935
+ bedrock: {
1936
+ cachePoint: {
1937
+ type: "default",
1938
+ },
1939
+ },
1940
+ openaiCompatible: {
1941
+ cache_control: {
1942
+ type: "ephemeral",
1943
+ },
1944
+ },
1945
+ copilot: {
1946
+ copilot_cache_control: {
1947
+ type: "ephemeral",
1948
+ },
1949
+ },
1950
+ })
1951
+ })
1952
+ })
1953
+
1954
+ describe("ProviderTransform.variants", () => {
1955
+ const createMockModel = (overrides: Partial<any> = {}): any => ({
1956
+ id: "test/test-model",
1957
+ providerID: "test",
1958
+ api: {
1959
+ id: "test-model",
1960
+ url: "https://api.test.com",
1961
+ npm: "@ai-sdk/openai",
1962
+ },
1963
+ name: "Test Model",
1964
+ capabilities: {
1965
+ temperature: true,
1966
+ reasoning: true,
1967
+ attachment: true,
1968
+ toolcall: true,
1969
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
1970
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
1971
+ interleaved: false,
1972
+ },
1973
+ cost: {
1974
+ input: 0.001,
1975
+ output: 0.002,
1976
+ cache: { read: 0.0001, write: 0.0002 },
1977
+ },
1978
+ limit: {
1979
+ context: 200_000,
1980
+ output: 64_000,
1981
+ },
1982
+ status: "active",
1983
+ options: {},
1984
+ headers: {},
1985
+ release_date: "2024-01-01",
1986
+ ...overrides,
1987
+ })
1988
+
1989
+ test("returns empty object when model has no reasoning capabilities", () => {
1990
+ const model = createMockModel({
1991
+ capabilities: { reasoning: false },
1992
+ })
1993
+ const result = ProviderTransform.variants(model)
1994
+ expect(result).toEqual({})
1995
+ })
1996
+
1997
+ test("deepseek returns empty object", () => {
1998
+ const model = createMockModel({
1999
+ id: "deepseek/deepseek-chat",
2000
+ providerID: "deepseek",
2001
+ api: {
2002
+ id: "deepseek-chat",
2003
+ url: "https://api.deepseek.com",
2004
+ npm: "@ai-sdk/openai-compatible",
2005
+ },
2006
+ })
2007
+ const result = ProviderTransform.variants(model)
2008
+ expect(result).toEqual({})
2009
+ })
2010
+
2011
+ test("minimax returns empty object", () => {
2012
+ const model = createMockModel({
2013
+ id: "minimax/minimax-model",
2014
+ providerID: "minimax",
2015
+ api: {
2016
+ id: "minimax-model",
2017
+ url: "https://api.minimax.com",
2018
+ npm: "@ai-sdk/openai-compatible",
2019
+ },
2020
+ })
2021
+ const result = ProviderTransform.variants(model)
2022
+ expect(result).toEqual({})
2023
+ })
2024
+
2025
+ test("glm returns empty object", () => {
2026
+ const model = createMockModel({
2027
+ id: "glm/glm-4",
2028
+ providerID: "glm",
2029
+ api: {
2030
+ id: "glm-4",
2031
+ url: "https://api.glm.com",
2032
+ npm: "@ai-sdk/openai-compatible",
2033
+ },
2034
+ })
2035
+ const result = ProviderTransform.variants(model)
2036
+ expect(result).toEqual({})
2037
+ })
2038
+
2039
+ test("mistral returns empty object", () => {
2040
+ const model = createMockModel({
2041
+ id: "mistral/mistral-large",
2042
+ providerID: "mistral",
2043
+ api: {
2044
+ id: "mistral-large-latest",
2045
+ url: "https://api.mistral.com",
2046
+ npm: "@ai-sdk/mistral",
2047
+ },
2048
+ })
2049
+ const result = ProviderTransform.variants(model)
2050
+ expect(result).toEqual({})
2051
+ })
2052
+
2053
+ describe("@openrouter/ai-sdk-provider", () => {
2054
+ test("returns empty object for non-qualifying models", () => {
2055
+ const model = createMockModel({
2056
+ id: "openrouter/test-model",
2057
+ providerID: "openrouter",
2058
+ api: {
2059
+ id: "test-model",
2060
+ url: "https://openrouter.ai",
2061
+ npm: "@openrouter/ai-sdk-provider",
2062
+ },
2063
+ })
2064
+ const result = ProviderTransform.variants(model)
2065
+ expect(result).toEqual({})
2066
+ })
2067
+
2068
+ test("gpt models return OPENAI_EFFORTS with reasoning", () => {
2069
+ const model = createMockModel({
2070
+ id: "openrouter/gpt-4",
2071
+ providerID: "openrouter",
2072
+ api: {
2073
+ id: "gpt-4",
2074
+ url: "https://openrouter.ai",
2075
+ npm: "@openrouter/ai-sdk-provider",
2076
+ },
2077
+ })
2078
+ const result = ProviderTransform.variants(model)
2079
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2080
+ expect(result.low).toEqual({ reasoning: { effort: "low" } })
2081
+ expect(result.high).toEqual({ reasoning: { effort: "high" } })
2082
+ })
2083
+
2084
+ test("gemini-3 returns OPENAI_EFFORTS with reasoning", () => {
2085
+ const model = createMockModel({
2086
+ id: "openrouter/gemini-3-5-pro",
2087
+ providerID: "openrouter",
2088
+ api: {
2089
+ id: "gemini-3-5-pro",
2090
+ url: "https://openrouter.ai",
2091
+ npm: "@openrouter/ai-sdk-provider",
2092
+ },
2093
+ })
2094
+ const result = ProviderTransform.variants(model)
2095
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2096
+ })
2097
+
2098
+ test("grok-4 returns empty object", () => {
2099
+ const model = createMockModel({
2100
+ id: "openrouter/grok-4",
2101
+ providerID: "openrouter",
2102
+ api: {
2103
+ id: "grok-4",
2104
+ url: "https://openrouter.ai",
2105
+ npm: "@openrouter/ai-sdk-provider",
2106
+ },
2107
+ })
2108
+ const result = ProviderTransform.variants(model)
2109
+ expect(result).toEqual({})
2110
+ })
2111
+
2112
+ test("grok-3-mini returns low and high with reasoning", () => {
2113
+ const model = createMockModel({
2114
+ id: "openrouter/grok-3-mini",
2115
+ providerID: "openrouter",
2116
+ api: {
2117
+ id: "grok-3-mini",
2118
+ url: "https://openrouter.ai",
2119
+ npm: "@openrouter/ai-sdk-provider",
2120
+ },
2121
+ })
2122
+ const result = ProviderTransform.variants(model)
2123
+ expect(Object.keys(result)).toEqual(["low", "high"])
2124
+ expect(result.low).toEqual({ reasoning: { effort: "low" } })
2125
+ expect(result.high).toEqual({ reasoning: { effort: "high" } })
2126
+ })
2127
+ })
2128
+
2129
+ describe("@ai-sdk/gateway", () => {
2130
+ test("anthropic sonnet 4.6 models return adaptive thinking options", () => {
2131
+ const model = createMockModel({
2132
+ id: "anthropic/claude-sonnet-4-6",
2133
+ providerID: "gateway",
2134
+ api: {
2135
+ id: "anthropic/claude-sonnet-4-6",
2136
+ url: "https://gateway.ai",
2137
+ npm: "@ai-sdk/gateway",
2138
+ },
2139
+ })
2140
+ const result = ProviderTransform.variants(model)
2141
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2142
+ expect(result.medium).toEqual({
2143
+ thinking: {
2144
+ type: "adaptive",
2145
+ },
2146
+ effort: "medium",
2147
+ })
2148
+ })
2149
+
2150
+ test("anthropic sonnet 4.6 dot-format models return adaptive thinking options", () => {
2151
+ const model = createMockModel({
2152
+ id: "anthropic/claude-sonnet-4-6",
2153
+ providerID: "gateway",
2154
+ api: {
2155
+ id: "anthropic/claude-sonnet-4.6",
2156
+ url: "https://gateway.ai",
2157
+ npm: "@ai-sdk/gateway",
2158
+ },
2159
+ })
2160
+ const result = ProviderTransform.variants(model)
2161
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2162
+ expect(result.medium).toEqual({
2163
+ thinking: {
2164
+ type: "adaptive",
2165
+ },
2166
+ effort: "medium",
2167
+ })
2168
+ })
2169
+
2170
+ test("anthropic opus 4.6 dot-format models return adaptive thinking options", () => {
2171
+ const model = createMockModel({
2172
+ id: "anthropic/claude-opus-4-6",
2173
+ providerID: "gateway",
2174
+ api: {
2175
+ id: "anthropic/claude-opus-4.6",
2176
+ url: "https://gateway.ai",
2177
+ npm: "@ai-sdk/gateway",
2178
+ },
2179
+ })
2180
+ const result = ProviderTransform.variants(model)
2181
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2182
+ expect(result.high).toEqual({
2183
+ thinking: {
2184
+ type: "adaptive",
2185
+ },
2186
+ effort: "high",
2187
+ })
2188
+ })
2189
+
2190
+ test("anthropic models return anthropic thinking options", () => {
2191
+ const model = createMockModel({
2192
+ id: "anthropic/claude-sonnet-4",
2193
+ providerID: "gateway",
2194
+ api: {
2195
+ id: "anthropic/claude-sonnet-4",
2196
+ url: "https://gateway.ai",
2197
+ npm: "@ai-sdk/gateway",
2198
+ },
2199
+ })
2200
+ const result = ProviderTransform.variants(model)
2201
+ expect(Object.keys(result)).toEqual(["high", "max"])
2202
+ expect(result.high).toEqual({
2203
+ thinking: {
2204
+ type: "enabled",
2205
+ budgetTokens: 16000,
2206
+ },
2207
+ })
2208
+ expect(result.max).toEqual({
2209
+ thinking: {
2210
+ type: "enabled",
2211
+ budgetTokens: 31999,
2212
+ },
2213
+ })
2214
+ })
2215
+
2216
+ test("returns OPENAI_EFFORTS with reasoningEffort", () => {
2217
+ const model = createMockModel({
2218
+ id: "gateway/gateway-model",
2219
+ providerID: "gateway",
2220
+ api: {
2221
+ id: "gateway-model",
2222
+ url: "https://gateway.ai",
2223
+ npm: "@ai-sdk/gateway",
2224
+ },
2225
+ })
2226
+ const result = ProviderTransform.variants(model)
2227
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2228
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2229
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2230
+ })
2231
+ })
2232
+
2233
+ describe("@ai-sdk/github-copilot", () => {
2234
+ test("standard models return low, medium, high", () => {
2235
+ const model = createMockModel({
2236
+ id: "gpt-4.5",
2237
+ providerID: "github-copilot",
2238
+ api: {
2239
+ id: "gpt-4.5",
2240
+ url: "https://api.githubcopilot.com",
2241
+ npm: "@ai-sdk/github-copilot",
2242
+ },
2243
+ })
2244
+ const result = ProviderTransform.variants(model)
2245
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2246
+ expect(result.low).toEqual({
2247
+ reasoningEffort: "low",
2248
+ reasoningSummary: "auto",
2249
+ include: ["reasoning.encrypted_content"],
2250
+ })
2251
+ })
2252
+
2253
+ test("gpt-5.1-codex-max includes xhigh", () => {
2254
+ const model = createMockModel({
2255
+ id: "gpt-5.1-codex-max",
2256
+ providerID: "github-copilot",
2257
+ api: {
2258
+ id: "gpt-5.1-codex-max",
2259
+ url: "https://api.githubcopilot.com",
2260
+ npm: "@ai-sdk/github-copilot",
2261
+ },
2262
+ })
2263
+ const result = ProviderTransform.variants(model)
2264
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2265
+ })
2266
+
2267
+ test("gpt-5.1-codex-mini does not include xhigh", () => {
2268
+ const model = createMockModel({
2269
+ id: "gpt-5.1-codex-mini",
2270
+ providerID: "github-copilot",
2271
+ api: {
2272
+ id: "gpt-5.1-codex-mini",
2273
+ url: "https://api.githubcopilot.com",
2274
+ npm: "@ai-sdk/github-copilot",
2275
+ },
2276
+ })
2277
+ const result = ProviderTransform.variants(model)
2278
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2279
+ })
2280
+
2281
+ test("gpt-5.1-codex does not include xhigh", () => {
2282
+ const model = createMockModel({
2283
+ id: "gpt-5.1-codex",
2284
+ providerID: "github-copilot",
2285
+ api: {
2286
+ id: "gpt-5.1-codex",
2287
+ url: "https://api.githubcopilot.com",
2288
+ npm: "@ai-sdk/github-copilot",
2289
+ },
2290
+ })
2291
+ const result = ProviderTransform.variants(model)
2292
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2293
+ })
2294
+
2295
+ test("gpt-5.2 includes xhigh", () => {
2296
+ const model = createMockModel({
2297
+ id: "gpt-5.2",
2298
+ providerID: "github-copilot",
2299
+ api: {
2300
+ id: "gpt-5.2",
2301
+ url: "https://api.githubcopilot.com",
2302
+ npm: "@ai-sdk/github-copilot",
2303
+ },
2304
+ })
2305
+ const result = ProviderTransform.variants(model)
2306
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2307
+ expect(result.xhigh).toEqual({
2308
+ reasoningEffort: "xhigh",
2309
+ reasoningSummary: "auto",
2310
+ include: ["reasoning.encrypted_content"],
2311
+ })
2312
+ })
2313
+
2314
+ test("gpt-5.2-codex includes xhigh", () => {
2315
+ const model = createMockModel({
2316
+ id: "gpt-5.2-codex",
2317
+ providerID: "github-copilot",
2318
+ api: {
2319
+ id: "gpt-5.2-codex",
2320
+ url: "https://api.githubcopilot.com",
2321
+ npm: "@ai-sdk/github-copilot",
2322
+ },
2323
+ })
2324
+ const result = ProviderTransform.variants(model)
2325
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2326
+ })
2327
+
2328
+ test("gpt-5.3-codex includes xhigh", () => {
2329
+ const model = createMockModel({
2330
+ id: "gpt-5.3-codex",
2331
+ providerID: "github-copilot",
2332
+ api: {
2333
+ id: "gpt-5.3-codex",
2334
+ url: "https://api.githubcopilot.com",
2335
+ npm: "@ai-sdk/github-copilot",
2336
+ },
2337
+ })
2338
+ const result = ProviderTransform.variants(model)
2339
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2340
+ })
2341
+
2342
+ test("gpt-5.4 includes xhigh", () => {
2343
+ const model = createMockModel({
2344
+ id: "gpt-5.4",
2345
+ release_date: "2026-03-05",
2346
+ providerID: "github-copilot",
2347
+ api: {
2348
+ id: "gpt-5.4",
2349
+ url: "https://api.githubcopilot.com",
2350
+ npm: "@ai-sdk/github-copilot",
2351
+ },
2352
+ })
2353
+ const result = ProviderTransform.variants(model)
2354
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
2355
+ })
2356
+ })
2357
+
2358
+ describe("@ai-sdk/cerebras", () => {
2359
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
2360
+ const model = createMockModel({
2361
+ id: "cerebras/llama-4",
2362
+ providerID: "cerebras",
2363
+ api: {
2364
+ id: "llama-4-sc",
2365
+ url: "https://api.cerebras.ai",
2366
+ npm: "@ai-sdk/cerebras",
2367
+ },
2368
+ })
2369
+ const result = ProviderTransform.variants(model)
2370
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2371
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2372
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2373
+ })
2374
+ })
2375
+
2376
+ describe("@ai-sdk/togetherai", () => {
2377
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
2378
+ const model = createMockModel({
2379
+ id: "togetherai/llama-4",
2380
+ providerID: "togetherai",
2381
+ api: {
2382
+ id: "llama-4-sc",
2383
+ url: "https://api.togetherai.com",
2384
+ npm: "@ai-sdk/togetherai",
2385
+ },
2386
+ })
2387
+ const result = ProviderTransform.variants(model)
2388
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2389
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2390
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2391
+ })
2392
+ })
2393
+
2394
+ describe("@ai-sdk/xai", () => {
2395
+ test("grok-3 returns empty object", () => {
2396
+ const model = createMockModel({
2397
+ id: "xai/grok-3",
2398
+ providerID: "xai",
2399
+ api: {
2400
+ id: "grok-3",
2401
+ url: "https://api.x.ai",
2402
+ npm: "@ai-sdk/xai",
2403
+ },
2404
+ })
2405
+ const result = ProviderTransform.variants(model)
2406
+ expect(result).toEqual({})
2407
+ })
2408
+
2409
+ test("grok-3-mini returns low and high with reasoningEffort", () => {
2410
+ const model = createMockModel({
2411
+ id: "xai/grok-3-mini",
2412
+ providerID: "xai",
2413
+ api: {
2414
+ id: "grok-3-mini",
2415
+ url: "https://api.x.ai",
2416
+ npm: "@ai-sdk/xai",
2417
+ },
2418
+ })
2419
+ const result = ProviderTransform.variants(model)
2420
+ expect(Object.keys(result)).toEqual(["low", "high"])
2421
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2422
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2423
+ })
2424
+ })
2425
+
2426
+ describe("@ai-sdk/deepinfra", () => {
2427
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
2428
+ const model = createMockModel({
2429
+ id: "deepinfra/llama-4",
2430
+ providerID: "deepinfra",
2431
+ api: {
2432
+ id: "llama-4-sc",
2433
+ url: "https://api.deepinfra.com",
2434
+ npm: "@ai-sdk/deepinfra",
2435
+ },
2436
+ })
2437
+ const result = ProviderTransform.variants(model)
2438
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2439
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2440
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2441
+ })
2442
+ })
2443
+
2444
+ describe("@ai-sdk/openai-compatible", () => {
2445
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => {
2446
+ const model = createMockModel({
2447
+ id: "custom-provider/custom-model",
2448
+ providerID: "custom-provider",
2449
+ api: {
2450
+ id: "custom-model",
2451
+ url: "https://api.custom.com",
2452
+ npm: "@ai-sdk/openai-compatible",
2453
+ },
2454
+ })
2455
+ const result = ProviderTransform.variants(model)
2456
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2457
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2458
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2459
+ })
2460
+ })
2461
+
2462
+ describe("@ai-sdk/azure", () => {
2463
+ test("o1-mini returns empty object", () => {
2464
+ const model = createMockModel({
2465
+ id: "o1-mini",
2466
+ providerID: "azure",
2467
+ api: {
2468
+ id: "o1-mini",
2469
+ url: "https://azure.com",
2470
+ npm: "@ai-sdk/azure",
2471
+ },
2472
+ })
2473
+ const result = ProviderTransform.variants(model)
2474
+ expect(result).toEqual({})
2475
+ })
2476
+
2477
+ test("standard azure models return custom efforts with reasoningSummary", () => {
2478
+ const model = createMockModel({
2479
+ id: "o1",
2480
+ providerID: "azure",
2481
+ api: {
2482
+ id: "o1",
2483
+ url: "https://azure.com",
2484
+ npm: "@ai-sdk/azure",
2485
+ },
2486
+ })
2487
+ const result = ProviderTransform.variants(model)
2488
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2489
+ expect(result.low).toEqual({
2490
+ reasoningEffort: "low",
2491
+ reasoningSummary: "auto",
2492
+ include: ["reasoning.encrypted_content"],
2493
+ })
2494
+ })
2495
+
2496
+ test("gpt-5 adds minimal effort", () => {
2497
+ const model = createMockModel({
2498
+ id: "gpt-5",
2499
+ providerID: "azure",
2500
+ api: {
2501
+ id: "gpt-5",
2502
+ url: "https://azure.com",
2503
+ npm: "@ai-sdk/azure",
2504
+ },
2505
+ })
2506
+ const result = ProviderTransform.variants(model)
2507
+ expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
2508
+ })
2509
+ })
2510
+
2511
+ describe("@ai-sdk/openai", () => {
2512
+ test("gpt-5-pro returns empty object", () => {
2513
+ const model = createMockModel({
2514
+ id: "gpt-5-pro",
2515
+ providerID: "openai",
2516
+ api: {
2517
+ id: "gpt-5-pro",
2518
+ url: "https://api.openai.com",
2519
+ npm: "@ai-sdk/openai",
2520
+ },
2521
+ })
2522
+ const result = ProviderTransform.variants(model)
2523
+ expect(result).toEqual({})
2524
+ })
2525
+
2526
+ test("standard openai models return custom efforts with reasoningSummary", () => {
2527
+ const model = createMockModel({
2528
+ id: "gpt-5",
2529
+ providerID: "openai",
2530
+ api: {
2531
+ id: "gpt-5",
2532
+ url: "https://api.openai.com",
2533
+ npm: "@ai-sdk/openai",
2534
+ },
2535
+ release_date: "2024-06-01",
2536
+ })
2537
+ const result = ProviderTransform.variants(model)
2538
+ expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high"])
2539
+ expect(result.low).toEqual({
2540
+ reasoningEffort: "low",
2541
+ reasoningSummary: "auto",
2542
+ include: ["reasoning.encrypted_content"],
2543
+ })
2544
+ })
2545
+
2546
+ test("models after 2025-11-13 include 'none' effort", () => {
2547
+ const model = createMockModel({
2548
+ id: "gpt-5-nano",
2549
+ providerID: "openai",
2550
+ api: {
2551
+ id: "gpt-5-nano",
2552
+ url: "https://api.openai.com",
2553
+ npm: "@ai-sdk/openai",
2554
+ },
2555
+ release_date: "2025-11-14",
2556
+ })
2557
+ const result = ProviderTransform.variants(model)
2558
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high"])
2559
+ })
2560
+
2561
+ test("models after 2025-12-04 include 'xhigh' effort", () => {
2562
+ const model = createMockModel({
2563
+ id: "openai/gpt-5-chat",
2564
+ providerID: "openai",
2565
+ api: {
2566
+ id: "gpt-5-chat",
2567
+ url: "https://api.openai.com",
2568
+ npm: "@ai-sdk/openai",
2569
+ },
2570
+ release_date: "2025-12-05",
2571
+ })
2572
+ const result = ProviderTransform.variants(model)
2573
+ expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"])
2574
+ })
2575
+ })
2576
+
2577
+ describe("@ai-sdk/anthropic", () => {
2578
+ test("sonnet 4.6 returns adaptive thinking options", () => {
2579
+ const model = createMockModel({
2580
+ id: "anthropic/claude-sonnet-4-6",
2581
+ providerID: "anthropic",
2582
+ api: {
2583
+ id: "claude-sonnet-4-6",
2584
+ url: "https://api.anthropic.com",
2585
+ npm: "@ai-sdk/anthropic",
2586
+ },
2587
+ })
2588
+ const result = ProviderTransform.variants(model)
2589
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2590
+ expect(result.high).toEqual({
2591
+ thinking: {
2592
+ type: "adaptive",
2593
+ },
2594
+ effort: "high",
2595
+ })
2596
+ })
2597
+
2598
+ test("returns high and max with thinking config", () => {
2599
+ const model = createMockModel({
2600
+ id: "anthropic/claude-4",
2601
+ providerID: "anthropic",
2602
+ api: {
2603
+ id: "claude-4",
2604
+ url: "https://api.anthropic.com",
2605
+ npm: "@ai-sdk/anthropic",
2606
+ },
2607
+ })
2608
+ const result = ProviderTransform.variants(model)
2609
+ expect(Object.keys(result)).toEqual(["high", "max"])
2610
+ expect(result.high).toEqual({
2611
+ thinking: {
2612
+ type: "enabled",
2613
+ budgetTokens: 16000,
2614
+ },
2615
+ })
2616
+ expect(result.max).toEqual({
2617
+ thinking: {
2618
+ type: "enabled",
2619
+ budgetTokens: 31999,
2620
+ },
2621
+ })
2622
+ })
2623
+ })
2624
+
2625
+ describe("@ai-sdk/amazon-bedrock", () => {
2626
+ test("anthropic sonnet 4.6 returns adaptive reasoning options", () => {
2627
+ const model = createMockModel({
2628
+ id: "bedrock/anthropic-claude-sonnet-4-6",
2629
+ providerID: "bedrock",
2630
+ api: {
2631
+ id: "anthropic.claude-sonnet-4-6",
2632
+ url: "https://bedrock.amazonaws.com",
2633
+ npm: "@ai-sdk/amazon-bedrock",
2634
+ },
2635
+ })
2636
+ const result = ProviderTransform.variants(model)
2637
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2638
+ expect(result.max).toEqual({
2639
+ reasoningConfig: {
2640
+ type: "adaptive",
2641
+ maxReasoningEffort: "max",
2642
+ },
2643
+ })
2644
+ })
2645
+
2646
+ test("returns WIDELY_SUPPORTED_EFFORTS with reasoningConfig", () => {
2647
+ const model = createMockModel({
2648
+ id: "bedrock/llama-4",
2649
+ providerID: "bedrock",
2650
+ api: {
2651
+ id: "llama-4-sc",
2652
+ url: "https://bedrock.amazonaws.com",
2653
+ npm: "@ai-sdk/amazon-bedrock",
2654
+ },
2655
+ })
2656
+ const result = ProviderTransform.variants(model)
2657
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2658
+ expect(result.low).toEqual({
2659
+ reasoningConfig: {
2660
+ type: "enabled",
2661
+ maxReasoningEffort: "low",
2662
+ },
2663
+ })
2664
+ })
2665
+ })
2666
+
2667
+ describe("@ai-sdk/google", () => {
2668
+ test("gemini-2.5 returns high and max with thinkingConfig and thinkingBudget", () => {
2669
+ const model = createMockModel({
2670
+ id: "google/gemini-2.5-pro",
2671
+ providerID: "google",
2672
+ api: {
2673
+ id: "gemini-2.5-pro",
2674
+ url: "https://generativelanguage.googleapis.com",
2675
+ npm: "@ai-sdk/google",
2676
+ },
2677
+ })
2678
+ const result = ProviderTransform.variants(model)
2679
+ expect(Object.keys(result)).toEqual(["high", "max"])
2680
+ expect(result.high).toEqual({
2681
+ thinkingConfig: {
2682
+ includeThoughts: true,
2683
+ thinkingBudget: 16000,
2684
+ },
2685
+ })
2686
+ expect(result.max).toEqual({
2687
+ thinkingConfig: {
2688
+ includeThoughts: true,
2689
+ thinkingBudget: 24576,
2690
+ },
2691
+ })
2692
+ })
2693
+
2694
+ test("other gemini models return low and high with thinkingLevel", () => {
2695
+ const model = createMockModel({
2696
+ id: "google/gemini-2.0-pro",
2697
+ providerID: "google",
2698
+ api: {
2699
+ id: "gemini-2.0-pro",
2700
+ url: "https://generativelanguage.googleapis.com",
2701
+ npm: "@ai-sdk/google",
2702
+ },
2703
+ })
2704
+ const result = ProviderTransform.variants(model)
2705
+ expect(Object.keys(result)).toEqual(["low", "high"])
2706
+ expect(result.low).toEqual({
2707
+ thinkingConfig: {
2708
+ includeThoughts: true,
2709
+ thinkingLevel: "low",
2710
+ },
2711
+ })
2712
+ expect(result.high).toEqual({
2713
+ thinkingConfig: {
2714
+ includeThoughts: true,
2715
+ thinkingLevel: "high",
2716
+ },
2717
+ })
2718
+ })
2719
+ })
2720
+
2721
+ describe("@ai-sdk/google-vertex", () => {
2722
+ test("gemini-2.5 returns high and max with thinkingConfig and thinkingBudget", () => {
2723
+ const model = createMockModel({
2724
+ id: "google-vertex/gemini-2.5-pro",
2725
+ providerID: "google-vertex",
2726
+ api: {
2727
+ id: "gemini-2.5-pro",
2728
+ url: "https://vertexai.googleapis.com",
2729
+ npm: "@ai-sdk/google-vertex",
2730
+ },
2731
+ })
2732
+ const result = ProviderTransform.variants(model)
2733
+ expect(Object.keys(result)).toEqual(["high", "max"])
2734
+ })
2735
+
2736
+ test("other vertex models return low and high with thinkingLevel", () => {
2737
+ const model = createMockModel({
2738
+ id: "google-vertex/gemini-2.0-pro",
2739
+ providerID: "google-vertex",
2740
+ api: {
2741
+ id: "gemini-2.0-pro",
2742
+ url: "https://vertexai.googleapis.com",
2743
+ npm: "@ai-sdk/google-vertex",
2744
+ },
2745
+ })
2746
+ const result = ProviderTransform.variants(model)
2747
+ expect(Object.keys(result)).toEqual(["low", "high"])
2748
+ })
2749
+ })
2750
+
2751
+ describe("@ai-sdk/cohere", () => {
2752
+ test("returns empty object", () => {
2753
+ const model = createMockModel({
2754
+ id: "cohere/command-r",
2755
+ providerID: "cohere",
2756
+ api: {
2757
+ id: "command-r",
2758
+ url: "https://api.cohere.com",
2759
+ npm: "@ai-sdk/cohere",
2760
+ },
2761
+ })
2762
+ const result = ProviderTransform.variants(model)
2763
+ expect(result).toEqual({})
2764
+ })
2765
+ })
2766
+
2767
+ describe("@ai-sdk/groq", () => {
2768
+ test("returns none and WIDELY_SUPPORTED_EFFORTS with thinkingLevel", () => {
2769
+ const model = createMockModel({
2770
+ id: "groq/llama-4",
2771
+ providerID: "groq",
2772
+ api: {
2773
+ id: "llama-4-sc",
2774
+ url: "https://api.groq.com",
2775
+ npm: "@ai-sdk/groq",
2776
+ },
2777
+ })
2778
+ const result = ProviderTransform.variants(model)
2779
+ expect(Object.keys(result)).toEqual(["none", "low", "medium", "high"])
2780
+ expect(result.none).toEqual({
2781
+ reasoningEffort: "none",
2782
+ })
2783
+ expect(result.low).toEqual({
2784
+ reasoningEffort: "low",
2785
+ })
2786
+ })
2787
+ })
2788
+
2789
+ describe("@ai-sdk/perplexity", () => {
2790
+ test("returns empty object", () => {
2791
+ const model = createMockModel({
2792
+ id: "perplexity/sonar-plus",
2793
+ providerID: "perplexity",
2794
+ api: {
2795
+ id: "sonar-plus",
2796
+ url: "https://api.perplexity.ai",
2797
+ npm: "@ai-sdk/perplexity",
2798
+ },
2799
+ })
2800
+ const result = ProviderTransform.variants(model)
2801
+ expect(result).toEqual({})
2802
+ })
2803
+ })
2804
+
2805
+ describe("@jerome-benoit/sap-ai-provider-v2", () => {
2806
+ test("anthropic models return thinking variants", () => {
2807
+ const model = createMockModel({
2808
+ id: "sap-ai-core/anthropic--claude-sonnet-4",
2809
+ providerID: "sap-ai-core",
2810
+ api: {
2811
+ id: "anthropic--claude-sonnet-4",
2812
+ url: "https://api.ai.sap",
2813
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2814
+ },
2815
+ })
2816
+ const result = ProviderTransform.variants(model)
2817
+ expect(Object.keys(result)).toEqual(["high", "max"])
2818
+ expect(result.high).toEqual({
2819
+ thinking: {
2820
+ type: "enabled",
2821
+ budgetTokens: 16000,
2822
+ },
2823
+ })
2824
+ expect(result.max).toEqual({
2825
+ thinking: {
2826
+ type: "enabled",
2827
+ budgetTokens: 31999,
2828
+ },
2829
+ })
2830
+ })
2831
+
2832
+ test("anthropic 4.6 models return adaptive thinking variants", () => {
2833
+ const model = createMockModel({
2834
+ id: "sap-ai-core/anthropic--claude-sonnet-4-6",
2835
+ providerID: "sap-ai-core",
2836
+ api: {
2837
+ id: "anthropic--claude-sonnet-4-6",
2838
+ url: "https://api.ai.sap",
2839
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2840
+ },
2841
+ })
2842
+ const result = ProviderTransform.variants(model)
2843
+ expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
2844
+ expect(result.low).toEqual({
2845
+ thinking: {
2846
+ type: "adaptive",
2847
+ },
2848
+ effort: "low",
2849
+ })
2850
+ expect(result.max).toEqual({
2851
+ thinking: {
2852
+ type: "adaptive",
2853
+ },
2854
+ effort: "max",
2855
+ })
2856
+ })
2857
+
2858
+ test("gemini 2.5 models return thinkingConfig variants", () => {
2859
+ const model = createMockModel({
2860
+ id: "sap-ai-core/gcp--gemini-2.5-pro",
2861
+ providerID: "sap-ai-core",
2862
+ api: {
2863
+ id: "gcp--gemini-2.5-pro",
2864
+ url: "https://api.ai.sap",
2865
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2866
+ },
2867
+ })
2868
+ const result = ProviderTransform.variants(model)
2869
+ expect(Object.keys(result)).toEqual(["high", "max"])
2870
+ expect(result.high).toEqual({
2871
+ thinkingConfig: {
2872
+ includeThoughts: true,
2873
+ thinkingBudget: 16000,
2874
+ },
2875
+ })
2876
+ expect(result.max).toEqual({
2877
+ thinkingConfig: {
2878
+ includeThoughts: true,
2879
+ thinkingBudget: 24576,
2880
+ },
2881
+ })
2882
+ })
2883
+
2884
+ test("gpt models return reasoningEffort variants", () => {
2885
+ const model = createMockModel({
2886
+ id: "sap-ai-core/azure-openai--gpt-4o",
2887
+ providerID: "sap-ai-core",
2888
+ api: {
2889
+ id: "azure-openai--gpt-4o",
2890
+ url: "https://api.ai.sap",
2891
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2892
+ },
2893
+ })
2894
+ const result = ProviderTransform.variants(model)
2895
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2896
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2897
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2898
+ })
2899
+
2900
+ test("o-series models return reasoningEffort variants", () => {
2901
+ const model = createMockModel({
2902
+ id: "sap-ai-core/azure-openai--o3-mini",
2903
+ providerID: "sap-ai-core",
2904
+ api: {
2905
+ id: "azure-openai--o3-mini",
2906
+ url: "https://api.ai.sap",
2907
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2908
+ },
2909
+ })
2910
+ const result = ProviderTransform.variants(model)
2911
+ expect(Object.keys(result)).toEqual(["low", "medium", "high"])
2912
+ expect(result.low).toEqual({ reasoningEffort: "low" })
2913
+ expect(result.high).toEqual({ reasoningEffort: "high" })
2914
+ })
2915
+
2916
+ test("sonar models return empty object", () => {
2917
+ const model = createMockModel({
2918
+ id: "sap-ai-core/perplexity--sonar-pro",
2919
+ providerID: "sap-ai-core",
2920
+ api: {
2921
+ id: "perplexity--sonar-pro",
2922
+ url: "https://api.ai.sap",
2923
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2924
+ },
2925
+ })
2926
+ const result = ProviderTransform.variants(model)
2927
+ expect(result).toEqual({})
2928
+ })
2929
+
2930
+ test("mistral models return empty object", () => {
2931
+ const model = createMockModel({
2932
+ id: "sap-ai-core/mistral--mistral-large",
2933
+ providerID: "sap-ai-core",
2934
+ api: {
2935
+ id: "mistral--mistral-large",
2936
+ url: "https://api.ai.sap",
2937
+ npm: "@jerome-benoit/sap-ai-provider-v2",
2938
+ },
2939
+ })
2940
+ const result = ProviderTransform.variants(model)
2941
+ expect(result).toEqual({})
2942
+ })
2943
+ })
2944
+ })