@miaws/miaw 1.18.3 → 1.18.5

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