soraacode 0.1.7 → 0.2.1

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 (352) hide show
  1. package/LICENSE +21 -0
  2. package/bin/soraacode.exe +10 -0
  3. package/package.json +17 -114
  4. package/{script/postinstall.mjs → postinstall.mjs} +5 -5
  5. package/AGENTS.md +0 -131
  6. package/Dockerfile +0 -18
  7. package/README.md +0 -15
  8. package/bin/opencode +0 -199
  9. package/bin/soraacode +0 -199
  10. package/bunfig.toml +0 -7
  11. package/git +0 -0
  12. package/migration/20260511173437_session-metadata/migration.sql +0 -1
  13. package/migration/20260511173437_session-metadata/snapshot.json +0 -1500
  14. package/parsers-config.ts +0 -1
  15. package/script/bench-search.ts +0 -94
  16. package/script/bench-test-suite.ts +0 -52
  17. package/script/build-headless.ts +0 -51
  18. package/script/build.ts +0 -245
  19. package/script/generate.ts +0 -14
  20. package/script/httpapi-exercise.ts +0 -1
  21. package/script/profile-test-files.ts +0 -42
  22. package/script/publish.ts +0 -213
  23. package/script/run-workspace-server +0 -106
  24. package/script/schema.ts +0 -77
  25. package/script/time.ts +0 -6
  26. package/script/trace-imports.ts +0 -153
  27. package/specs/effect/error-boundaries-plan.md +0 -235
  28. package/specs/effect/errors.md +0 -207
  29. package/specs/effect/facades.md +0 -218
  30. package/specs/effect/guide.md +0 -247
  31. package/specs/effect/instance-context.md +0 -13
  32. package/specs/effect/loose-ends.md +0 -30
  33. package/specs/effect/migration.md +0 -62
  34. package/specs/effect/routes.md +0 -61
  35. package/specs/effect/schema.md +0 -88
  36. package/specs/effect/server-package.md +0 -58
  37. package/specs/effect/todo.md +0 -241
  38. package/specs/effect/tools.md +0 -88
  39. package/specs/openapi-translation-cleanup.md +0 -204
  40. package/specs/tui-plugins.md +0 -544
  41. package/specs/v2/api.ts +0 -67
  42. package/specs/v2/message-shape.md +0 -136
  43. package/specs/v2/notifications.md +0 -13
  44. package/specs/v2/tui-command-shim.md +0 -67
  45. package/src/account/account.ts +0 -481
  46. package/src/account/repo.ts +0 -171
  47. package/src/account/schema.ts +0 -99
  48. package/src/account/url.ts +0 -8
  49. package/src/acp/agent.ts +0 -95
  50. package/src/acp/config-option.ts +0 -206
  51. package/src/acp/content.ts +0 -269
  52. package/src/acp/directory.ts +0 -212
  53. package/src/acp/error.ts +0 -97
  54. package/src/acp/event.ts +0 -421
  55. package/src/acp/permission.ts +0 -254
  56. package/src/acp/profile.ts +0 -42
  57. package/src/acp/service.ts +0 -1226
  58. package/src/acp/session.ts +0 -232
  59. package/src/acp/tool.ts +0 -364
  60. package/src/acp/usage.ts +0 -243
  61. package/src/agent/agent.ts +0 -450
  62. package/src/agent/generate.txt +0 -75
  63. package/src/agent/prompt/compaction.txt +0 -5
  64. package/src/agent/prompt/explore.txt +0 -18
  65. package/src/agent/prompt/summary.txt +0 -11
  66. package/src/agent/prompt/title.txt +0 -44
  67. package/src/agent/subagent-permissions.ts +0 -27
  68. package/src/audio.d.ts +0 -14
  69. package/src/auth/index.ts +0 -97
  70. package/src/background/job.ts +0 -37
  71. package/src/bus/global.ts +0 -22
  72. package/src/cli/bootstrap.ts +0 -11
  73. package/src/cli/cmd/cmd.ts +0 -7
  74. package/src/cli/cmd/serve.ts +0 -24
  75. package/src/cli/effect/prompt.ts +0 -37
  76. package/src/cli/effect-cmd.ts +0 -96
  77. package/src/cli/error.ts +0 -130
  78. package/src/cli/heap.ts +0 -45
  79. package/src/cli/logo.ts +0 -2
  80. package/src/cli/network.ts +0 -80
  81. package/src/cli/ui.ts +0 -132
  82. package/src/command/index.ts +0 -177
  83. package/src/command/template/initialize.txt +0 -66
  84. package/src/command/template/review.txt +0 -101
  85. package/src/config/agent.ts +0 -59
  86. package/src/config/command.ts +0 -39
  87. package/src/config/config.ts +0 -707
  88. package/src/config/entry-name.ts +0 -19
  89. package/src/config/managed.ts +0 -69
  90. package/src/config/markdown.ts +0 -36
  91. package/src/config/parse.ts +0 -61
  92. package/src/config/paths.ts +0 -45
  93. package/src/config/plugin.ts +0 -79
  94. package/src/config/v2-compat.ts +0 -449
  95. package/src/config/variable.ts +0 -91
  96. package/src/control-plane/adapters/index.ts +0 -41
  97. package/src/control-plane/adapters/worktree.ts +0 -96
  98. package/src/control-plane/dev/README.md +0 -19
  99. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  100. package/src/control-plane/types.ts +0 -59
  101. package/src/control-plane/util.ts +0 -39
  102. package/src/control-plane/workspace-adapter-runtime.ts +0 -51
  103. package/src/control-plane/workspace-context.ts +0 -26
  104. package/src/control-plane/workspace.ts +0 -966
  105. package/src/effect/app-node-builder-v1.ts +0 -12
  106. package/src/effect/app-runtime.ts +0 -135
  107. package/src/effect/bootstrap-runtime.ts +0 -19
  108. package/src/effect/bridge.ts +0 -84
  109. package/src/effect/config-service.ts +0 -67
  110. package/src/effect/instance-ref.ts +0 -11
  111. package/src/effect/instance-registry.ts +0 -12
  112. package/src/effect/instance-state.ts +0 -69
  113. package/src/effect/promise.ts +0 -17
  114. package/src/effect/run-service.ts +0 -47
  115. package/src/effect/runner.ts +0 -217
  116. package/src/effect/runtime-flags.ts +0 -78
  117. package/src/env/index.ts +0 -41
  118. package/src/event-manifest.ts +0 -3
  119. package/src/event-v2-bridge.ts +0 -71
  120. package/src/format/formatter.ts +0 -404
  121. package/src/format/index.ts +0 -203
  122. package/src/git/index.ts +0 -348
  123. package/src/id/id.ts +0 -80
  124. package/src/ide/index.ts +0 -54
  125. package/src/image/image.ts +0 -172
  126. package/src/index-headless.ts +0 -89
  127. package/src/index.ts +0 -93
  128. package/src/installation/index.ts +0 -336
  129. package/src/lsp/client.ts +0 -650
  130. package/src/lsp/diagnostic.ts +0 -29
  131. package/src/lsp/language.ts +0 -121
  132. package/src/lsp/launch.ts +0 -21
  133. package/src/lsp/lsp.ts +0 -507
  134. package/src/lsp/server.ts +0 -1983
  135. package/src/markdown.d.ts +0 -4
  136. package/src/mcp/auth.ts +0 -163
  137. package/src/mcp/browser.ts +0 -37
  138. package/src/mcp/catalog.ts +0 -170
  139. package/src/mcp/index.ts +0 -1004
  140. package/src/mcp/oauth-callback.ts +0 -194
  141. package/src/mcp/oauth-provider.ts +0 -259
  142. package/src/node.ts +0 -4
  143. package/src/patch/index.ts +0 -686
  144. package/src/permission/arity.ts +0 -163
  145. package/src/permission/evaluate.ts +0 -1
  146. package/src/permission/index.ts +0 -223
  147. package/src/plugin/github-copilot/copilot.ts +0 -415
  148. package/src/plugin/github-copilot/models.ts +0 -261
  149. package/src/plugin/index.ts +0 -298
  150. package/src/plugin/install.ts +0 -439
  151. package/src/plugin/loader.ts +0 -237
  152. package/src/plugin/meta.ts +0 -188
  153. package/src/plugin/openai/README.md +0 -31
  154. package/src/plugin/openai/codex.ts +0 -575
  155. package/src/plugin/openai/ws-pool.ts +0 -271
  156. package/src/plugin/openai/ws.ts +0 -383
  157. package/src/plugin/pty-environment.ts +0 -24
  158. package/src/plugin/shared.ts +0 -323
  159. package/src/project/bootstrap-service.ts +0 -9
  160. package/src/project/bootstrap.ts +0 -58
  161. package/src/project/instance-context.ts +0 -24
  162. package/src/project/instance-runtime.ts +0 -16
  163. package/src/project/instance-store.ts +0 -213
  164. package/src/project/project.ts +0 -483
  165. package/src/project/vcs.ts +0 -423
  166. package/src/provider/auth.ts +0 -229
  167. package/src/provider/error.ts +0 -195
  168. package/src/provider/model-status.ts +0 -8
  169. package/src/provider/provider.ts +0 -1955
  170. package/src/provider/transform.ts +0 -1923
  171. package/src/question/index.ts +0 -161
  172. package/src/question/schema.ts +0 -4
  173. package/src/server/auth.ts +0 -48
  174. package/src/server/event.ts +0 -10
  175. package/src/server/global-lifecycle.ts +0 -28
  176. package/src/server/mdns.ts +0 -47
  177. package/src/server/proxy-util.ts +0 -48
  178. package/src/server/routes/instance/httpapi/AGENTS.md +0 -39
  179. package/src/server/routes/instance/httpapi/api.ts +0 -97
  180. package/src/server/routes/instance/httpapi/errors.ts +0 -193
  181. package/src/server/routes/instance/httpapi/groups/config.ts +0 -65
  182. package/src/server/routes/instance/httpapi/groups/control-plane.ts +0 -35
  183. package/src/server/routes/instance/httpapi/groups/control.ts +0 -76
  184. package/src/server/routes/instance/httpapi/groups/event.ts +0 -29
  185. package/src/server/routes/instance/httpapi/groups/experimental.ts +0 -275
  186. package/src/server/routes/instance/httpapi/groups/file.ts +0 -185
  187. package/src/server/routes/instance/httpapi/groups/global.ts +0 -139
  188. package/src/server/routes/instance/httpapi/groups/instance.ts +0 -206
  189. package/src/server/routes/instance/httpapi/groups/mcp.ts +0 -156
  190. package/src/server/routes/instance/httpapi/groups/metadata.ts +0 -18
  191. package/src/server/routes/instance/httpapi/groups/permission.ts +0 -61
  192. package/src/server/routes/instance/httpapi/groups/project-copy.ts +0 -32
  193. package/src/server/routes/instance/httpapi/groups/project.ts +0 -93
  194. package/src/server/routes/instance/httpapi/groups/provider.ts +0 -101
  195. package/src/server/routes/instance/httpapi/groups/pty.ts +0 -172
  196. package/src/server/routes/instance/httpapi/groups/query.ts +0 -12
  197. package/src/server/routes/instance/httpapi/groups/question.ts +0 -74
  198. package/src/server/routes/instance/httpapi/groups/session.ts +0 -462
  199. package/src/server/routes/instance/httpapi/groups/sync.ts +0 -113
  200. package/src/server/routes/instance/httpapi/groups/tui.ts +0 -208
  201. package/src/server/routes/instance/httpapi/groups/workspace.ts +0 -141
  202. package/src/server/routes/instance/httpapi/handlers/config.ts +0 -34
  203. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +0 -37
  204. package/src/server/routes/instance/httpapi/handlers/control.ts +0 -43
  205. package/src/server/routes/instance/httpapi/handlers/event.ts +0 -99
  206. package/src/server/routes/instance/httpapi/handlers/experimental.ts +0 -193
  207. package/src/server/routes/instance/httpapi/handlers/file.ts +0 -139
  208. package/src/server/routes/instance/httpapi/handlers/global.ts +0 -126
  209. package/src/server/routes/instance/httpapi/handlers/instance.ts +0 -110
  210. package/src/server/routes/instance/httpapi/handlers/mcp.ts +0 -111
  211. package/src/server/routes/instance/httpapi/handlers/permission.ts +0 -41
  212. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +0 -83
  213. package/src/server/routes/instance/httpapi/handlers/project.ts +0 -63
  214. package/src/server/routes/instance/httpapi/handlers/provider.ts +0 -116
  215. package/src/server/routes/instance/httpapi/handlers/pty.ts +0 -273
  216. package/src/server/routes/instance/httpapi/handlers/question.ts +0 -54
  217. package/src/server/routes/instance/httpapi/handlers/session-errors.ts +0 -21
  218. package/src/server/routes/instance/httpapi/handlers/session.ts +0 -442
  219. package/src/server/routes/instance/httpapi/handlers/sync.ts +0 -89
  220. package/src/server/routes/instance/httpapi/handlers/tui.ts +0 -131
  221. package/src/server/routes/instance/httpapi/handlers/workspace.ts +0 -102
  222. package/src/server/routes/instance/httpapi/lifecycle.ts +0 -54
  223. package/src/server/routes/instance/httpapi/middleware/authorization.ts +0 -150
  224. package/src/server/routes/instance/httpapi/middleware/compression.ts +0 -64
  225. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +0 -29
  226. package/src/server/routes/instance/httpapi/middleware/error.ts +0 -44
  227. package/src/server/routes/instance/httpapi/middleware/fence.ts +0 -25
  228. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +0 -43
  229. package/src/server/routes/instance/httpapi/middleware/proxy.ts +0 -131
  230. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +0 -41
  231. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +0 -250
  232. package/src/server/routes/instance/httpapi/public.ts +0 -537
  233. package/src/server/routes/instance/httpapi/server.ts +0 -325
  234. package/src/server/routes/instance/httpapi/websocket-tracker.ts +0 -60
  235. package/src/server/server.ts +0 -224
  236. package/src/server/shared/fence.ts +0 -60
  237. package/src/server/shared/pty-ticket.ts +0 -15
  238. package/src/server/shared/public-ui.ts +0 -12
  239. package/src/server/shared/tui-control.ts +0 -28
  240. package/src/server/shared/ui.ts +0 -108
  241. package/src/server/shared/workspace-routing.ts +0 -45
  242. package/src/server/tui-event.ts +0 -1
  243. package/src/session/compaction.ts +0 -608
  244. package/src/session/instruction.ts +0 -237
  245. package/src/session/llm/AGENTS.md +0 -90
  246. package/src/session/llm/ai-sdk.ts +0 -291
  247. package/src/session/llm/native-request.ts +0 -196
  248. package/src/session/llm/native-runtime.ts +0 -195
  249. package/src/session/llm/request.ts +0 -226
  250. package/src/session/llm.ts +0 -390
  251. package/src/session/message-error.ts +0 -14
  252. package/src/session/message-v2.ts +0 -742
  253. package/src/session/message.ts +0 -148
  254. package/src/session/overflow.ts +0 -34
  255. package/src/session/processor.ts +0 -732
  256. package/src/session/prompt/build-switch.txt +0 -5
  257. package/src/session/prompt/default.txt +0 -35
  258. package/src/session/prompt/plan-mode.txt +0 -70
  259. package/src/session/prompt/plan.txt +0 -26
  260. package/src/session/prompt.ts +0 -1631
  261. package/src/session/reminders.ts +0 -92
  262. package/src/session/retry.ts +0 -209
  263. package/src/session/revert.ts +0 -136
  264. package/src/session/run-state.ts +0 -151
  265. package/src/session/schema.ts +0 -26
  266. package/src/session/session.ts +0 -1016
  267. package/src/session/status.ts +0 -56
  268. package/src/session/summary.ts +0 -160
  269. package/src/session/system.ts +0 -123
  270. package/src/session/todo.ts +0 -74
  271. package/src/session/tools.ts +0 -590
  272. package/src/share/session.ts +0 -58
  273. package/src/share/share-next.ts +0 -371
  274. package/src/skill/discovery.ts +0 -140
  275. package/src/skill/index.ts +0 -354
  276. package/src/snapshot/index.ts +0 -807
  277. package/src/sql.d.ts +0 -4
  278. package/src/storage/schema.ts +0 -5
  279. package/src/storage/storage.ts +0 -327
  280. package/src/sync/README.md +0 -179
  281. package/src/sync/schema.ts +0 -11
  282. package/src/temporary.ts +0 -31
  283. package/src/tool/apply_patch.ts +0 -313
  284. package/src/tool/apply_patch.txt +0 -14
  285. package/src/tool/code-mode.ts +0 -310
  286. package/src/tool/edit.ts +0 -737
  287. package/src/tool/edit.txt +0 -13
  288. package/src/tool/external-directory.ts +0 -49
  289. package/src/tool/glob.ts +0 -76
  290. package/src/tool/glob.txt +0 -7
  291. package/src/tool/grep.ts +0 -115
  292. package/src/tool/grep.txt +0 -7
  293. package/src/tool/invalid.ts +0 -21
  294. package/src/tool/json-schema.ts +0 -164
  295. package/src/tool/lsp.ts +0 -113
  296. package/src/tool/lsp.txt +0 -1
  297. package/src/tool/mcp-websearch.ts +0 -96
  298. package/src/tool/plan-enter.txt +0 -1
  299. package/src/tool/plan-exit.txt +0 -1
  300. package/src/tool/plan.ts +0 -79
  301. package/src/tool/question.ts +0 -44
  302. package/src/tool/question.txt +0 -1
  303. package/src/tool/read.ts +0 -386
  304. package/src/tool/read.txt +0 -13
  305. package/src/tool/registry.ts +0 -455
  306. package/src/tool/schema.ts +0 -14
  307. package/src/tool/shell/id.ts +0 -19
  308. package/src/tool/shell/prompt.ts +0 -293
  309. package/src/tool/shell/shell.txt +0 -21
  310. package/src/tool/shell.ts +0 -645
  311. package/src/tool/skill.ts +0 -70
  312. package/src/tool/skill.txt +0 -1
  313. package/src/tool/task.ts +0 -371
  314. package/src/tool/task.txt +0 -11
  315. package/src/tool/todo.ts +0 -46
  316. package/src/tool/todowrite.txt +0 -11
  317. package/src/tool/tool.ts +0 -183
  318. package/src/tool/truncate.ts +0 -156
  319. package/src/tool/truncation-dir.ts +0 -4
  320. package/src/tool/webfetch.ts +0 -192
  321. package/src/tool/webfetch.txt +0 -9
  322. package/src/tool/websearch.ts +0 -143
  323. package/src/tool/websearch.txt +0 -9
  324. package/src/tool/write.ts +0 -104
  325. package/src/tool/write.txt +0 -11
  326. package/src/util/archive.ts +0 -17
  327. package/src/util/bom.ts +0 -27
  328. package/src/util/data-url.ts +0 -9
  329. package/src/util/defer.ts +0 -10
  330. package/src/util/effect-http-client.ts +0 -11
  331. package/src/util/error.ts +0 -16
  332. package/src/util/filesystem.ts +0 -251
  333. package/src/util/html.ts +0 -8
  334. package/src/util/iife.ts +0 -3
  335. package/src/util/lazy.ts +0 -20
  336. package/src/util/local-context.ts +0 -25
  337. package/src/util/locale.ts +0 -4
  338. package/src/util/media.ts +0 -26
  339. package/src/util/process.ts +0 -177
  340. package/src/util/proxy-env.ts +0 -72
  341. package/src/util/queue.ts +0 -32
  342. package/src/util/record.ts +0 -3
  343. package/src/util/repository.ts +0 -232
  344. package/src/util/rpc.ts +0 -66
  345. package/src/util/signal.ts +0 -12
  346. package/src/util/timeout.ts +0 -13
  347. package/src/util/token.ts +0 -1
  348. package/src/util/wildcard.ts +0 -59
  349. package/src/worktree/index.ts +0 -623
  350. package/sst-env.d.ts +0 -10
  351. package/tsconfig.headless.json +0 -13
  352. package/tsconfig.json +0 -16
@@ -1,1923 +0,0 @@
1
- import type { ModelMessage, ToolResultPart } from "ai"
2
- import { mergeDeep, unique } from "remeda"
3
- import type { JSONSchema7 } from "@ai-sdk/provider"
4
- import type * as Provider from "./provider"
5
- import type * as ModelsDev from "@soraacode/core/models-dev"
6
- import { iife } from "@/util/iife"
7
-
8
- type Modality = NonNullable<ModelsDev.Model["modalities"]>["input"][number]
9
-
10
- function mimeToModality(mime: string): Modality | undefined {
11
- if (mime.startsWith("image/")) return "image"
12
- if (mime.startsWith("audio/")) return "audio"
13
- if (mime.startsWith("video/")) return "video"
14
- if (mime === "application/pdf") return "pdf"
15
- return undefined
16
- }
17
-
18
- export const OUTPUT_TOKEN_MAX = 32_000
19
-
20
- // OpenAI Responses `include` value that returns the encrypted reasoning state
21
- // needed for stateless multi-turn reasoning (store: false). Hoisted so every
22
- // branch that requests it stays in lockstep.
23
- const INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"] as const
24
-
25
- export function sanitizeSurrogates(content: string) {
26
- return content.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD")
27
- }
28
-
29
- function isKimiFamily(model: Provider.Model) {
30
- if (
31
- [model.providerID, model.api.id].some((id) => {
32
- const value = id.toLowerCase()
33
- return value.includes("kimi") || value.includes("moonshot")
34
- })
35
- )
36
- return true
37
- const url = model.api.url.toLowerCase()
38
- return ["api.kimi.com", "api.moonshot.ai", "api.moonshot.cn", "api.moonshotai.cn"].some((host) => url.includes(host))
39
- }
40
-
41
- // Maps npm package to the key the AI SDK expects for providerOptions
42
- function sdkKey(npm: string): string | undefined {
43
- switch (npm) {
44
- case "@ai-sdk/github-copilot":
45
- return "copilot"
46
- case "@ai-sdk/azure":
47
- return "azure"
48
- case "@ai-sdk/openai":
49
- return "openai"
50
- case "@ai-sdk/amazon-bedrock/mantle":
51
- return "openai"
52
- case "@ai-sdk/amazon-bedrock":
53
- return "bedrock"
54
- case "@ai-sdk/anthropic":
55
- case "@ai-sdk/google-vertex/anthropic":
56
- return "anthropic"
57
- case "@ai-sdk/google-vertex":
58
- return "vertex"
59
- case "@ai-sdk/google":
60
- return "google"
61
- case "@ai-sdk/alibaba":
62
- return "alibaba"
63
- case "@ai-sdk/cerebras":
64
- return "cerebras"
65
- case "@ai-sdk/cohere":
66
- return "cohere"
67
- case "@ai-sdk/deepinfra":
68
- return "deepinfra"
69
- case "@ai-sdk/groq":
70
- return "groq"
71
- case "@ai-sdk/mistral":
72
- return "mistral"
73
- case "@ai-sdk/perplexity":
74
- return "perplexity"
75
- case "@ai-sdk/togetherai":
76
- return "togetherai"
77
- case "@ai-sdk/vercel":
78
- return "vercel"
79
- case "@ai-sdk/xai":
80
- return "xai"
81
- case "venice-ai-sdk-provider":
82
- return "venice"
83
- case "@ai-sdk/gateway":
84
- return "gateway"
85
- case "@openrouter/ai-sdk-provider":
86
- return "openrouter"
87
- case "merge-gateway-ai-sdk-provider":
88
- return "mergeGateway"
89
- case "ai-gateway-provider":
90
- // ai-gateway-provider/unified wraps createOpenAICompatible({ name: "Unified" }),
91
- // and @ai-sdk/openai-compatible parses compatibleOptions from one of
92
- // "openai-compatible" / "openaiCompatible" / "Unified" / "unified". The
93
- // "openai-compatible" key emits a deprecation warning at runtime, so we
94
- // pick the camelCase form the SDK now treats as canonical.
95
- return "openaiCompatible"
96
- }
97
- return undefined
98
- }
99
-
100
- // TODO: fix this stupid inefficient dogshit function
101
- function normalizeMessages(
102
- msgs: ModelMessage[],
103
- model: Provider.Model,
104
- _options: Record<string, unknown>,
105
- ): ModelMessage[] {
106
- const sanitizeToolResultOutput = (content: ToolResultPart) => {
107
- if (content.output.type === "text" || content.output.type === "error-text") {
108
- content.output.value = sanitizeSurrogates(content.output.value)
109
- }
110
- if (content.output.type === "content") {
111
- content.output.value = content.output.value.map((item) => {
112
- if (item.type === "text") {
113
- item.text = sanitizeSurrogates(item.text)
114
- }
115
- return item
116
- })
117
- }
118
- return content
119
- }
120
-
121
- msgs = msgs.map((msg) => {
122
- switch (msg.role) {
123
- case "tool":
124
- if (!Array.isArray(msg.content)) return msg
125
- msg.content = msg.content.map((content) => {
126
- if (content.type === "tool-result") {
127
- return sanitizeToolResultOutput(content)
128
- }
129
- return content
130
- })
131
- return msg
132
-
133
- case "system":
134
- msg.content = sanitizeSurrogates(msg.content)
135
- return msg
136
-
137
- case "user":
138
- if (typeof msg.content === "string") {
139
- msg.content = sanitizeSurrogates(msg.content)
140
- } else {
141
- msg.content = msg.content.map((content) => {
142
- if (content.type === "text") {
143
- content.text = sanitizeSurrogates(content.text)
144
- }
145
- return content
146
- })
147
- }
148
- return msg
149
-
150
- case "assistant":
151
- if (typeof msg.content === "string") {
152
- msg.content = sanitizeSurrogates(msg.content)
153
- } else {
154
- msg.content = msg.content.map((content) => {
155
- if (content.type === "text" || content.type === "reasoning") {
156
- content.text = sanitizeSurrogates(content.text)
157
- }
158
- if (content.type === "tool-result") {
159
- return sanitizeToolResultOutput(content)
160
- }
161
- return content
162
- })
163
- }
164
- return msg
165
- }
166
- })
167
-
168
- // Anthropic rejects messages with empty content - filter out empty string messages
169
- // and remove empty text/reasoning parts from array content
170
- if (model.api.npm === "@ai-sdk/anthropic") {
171
- msgs = msgs
172
- .map((msg) => {
173
- if (typeof msg.content === "string") {
174
- if (msg.content === "") return undefined
175
- return msg
176
- }
177
- if (!Array.isArray(msg.content)) return msg
178
- const filtered = msg.content.filter((part) => {
179
- if (part.type === "text") {
180
- return part.text !== ""
181
- }
182
- if (part.type === "reasoning") {
183
- return (
184
- part.text.trim().length > 0 ||
185
- part.providerOptions?.anthropic?.signature != null ||
186
- part.providerOptions?.anthropic?.redactedData != null
187
- )
188
- }
189
- return true
190
- })
191
- if (filtered.length === 0) return undefined
192
- return { ...msg, content: filtered }
193
- })
194
- .filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "")
195
- }
196
-
197
- // Bedrock specific transforms
198
- if (model.api.npm === "@ai-sdk/amazon-bedrock") {
199
- msgs = msgs
200
- .map((msg) => {
201
- if (typeof msg.content === "string") {
202
- if (msg.content === "") return undefined
203
- return msg
204
- }
205
- if (!Array.isArray(msg.content)) return msg
206
- const filtered = msg.content.filter((part) => {
207
- if (part.type === "text") {
208
- return part.text !== ""
209
- }
210
- if (part.type === "reasoning") {
211
- // Match what the SDK can replay before assigning cache points. Otherwise
212
- // unsigned reasoning can leave an empty or cache-point-only message.
213
- const metadata = part.providerOptions?.[model.providerID] ?? part.providerOptions?.bedrock
214
- return metadata?.signature != null || metadata?.redactedContent != null || metadata?.redactedData != null
215
- }
216
- return true
217
- })
218
- if (filtered.length === 0) return undefined
219
- return { ...msg, content: filtered }
220
- })
221
- .filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "")
222
- }
223
-
224
- if (model.api.id.includes("claude")) {
225
- const scrub = (id: string) => id.replace(/[^a-zA-Z0-9_-]/g, "_")
226
- msgs = msgs.map((msg) => {
227
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
228
- return {
229
- ...msg,
230
- content: msg.content.map((part) => {
231
- if (part.type === "tool-call" || part.type === "tool-result") {
232
- return { ...part, toolCallId: scrub(part.toolCallId) }
233
- }
234
- return part
235
- }),
236
- }
237
- }
238
- if (msg.role === "tool" && Array.isArray(msg.content)) {
239
- return {
240
- ...msg,
241
- content: msg.content.map((part) => {
242
- if (part.type === "tool-result") {
243
- return { ...part, toolCallId: scrub(part.toolCallId) }
244
- }
245
- return part
246
- }),
247
- }
248
- }
249
- return msg
250
- })
251
- }
252
-
253
- const modelID = model.api.id.toLowerCase()
254
- if (
255
- model.providerID === "mistral" ||
256
- ["mistral", "devstral", "codestral", "pixtral", "mixtral"].some((family) => modelID.includes(family))
257
- ) {
258
- const scrub = (id: string) => {
259
- return id
260
- .replace(/[^a-zA-Z0-9]/g, "") // Remove non-alphanumeric characters
261
- .substring(0, 9) // Take first 9 characters
262
- .padEnd(9, "0") // Pad with zeros if less than 9 characters
263
- }
264
- const result: ModelMessage[] = []
265
- for (let i = 0; i < msgs.length; i++) {
266
- const msg = msgs[i]
267
- const nextMsg = msgs[i + 1]
268
-
269
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
270
- msg.content = msg.content.map((part) => {
271
- if (part.type === "tool-call" || part.type === "tool-result") {
272
- return { ...part, toolCallId: scrub(part.toolCallId) }
273
- }
274
- return part
275
- })
276
- }
277
- if (msg.role === "tool" && Array.isArray(msg.content)) {
278
- msg.content = msg.content.map((part) => {
279
- if (part.type === "tool-result") {
280
- return { ...part, toolCallId: scrub(part.toolCallId) }
281
- }
282
- return part
283
- })
284
- }
285
- result.push(msg)
286
-
287
- // Fix message sequence: tool messages cannot be followed by user messages
288
- if (msg.role === "tool" && nextMsg?.role === "user") {
289
- result.push({
290
- role: "assistant",
291
- content: [
292
- {
293
- type: "text",
294
- text: "Done.",
295
- },
296
- ],
297
- })
298
- }
299
- }
300
- return result
301
- }
302
-
303
- // Deepseek requires all assistant messages to have reasoning on them
304
- if (model.api.id.toLowerCase().includes("deepseek")) {
305
- msgs = msgs.map((msg) => {
306
- if (msg.role !== "assistant") return msg
307
- if (Array.isArray(msg.content)) {
308
- if (msg.content.some((part) => part.type === "reasoning")) return msg
309
- return { ...msg, content: [...msg.content, { type: "reasoning", text: "" }] }
310
- }
311
- return {
312
- ...msg,
313
- content: [
314
- ...(msg.content ? [{ type: "text" as const, text: msg.content }] : []),
315
- { type: "reasoning" as const, text: "" },
316
- ],
317
- }
318
- })
319
- }
320
-
321
- if (
322
- typeof model.capabilities.interleaved === "object" &&
323
- model.capabilities.interleaved.field &&
324
- model.api.npm !== "@openrouter/ai-sdk-provider"
325
- ) {
326
- const field = model.capabilities.interleaved.field
327
- return msgs.map((msg) => {
328
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
329
- const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning")
330
- const reasoningText = reasoningParts.map((part: any) => part.text).join("")
331
-
332
- // Filter out reasoning parts from content
333
- const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning")
334
-
335
- // Include reasoning_content | reasoning_details directly on the message for all assistant messages.
336
- // Always set the field even when empty — some providers (e.g. DeepSeek) may return empty
337
- // reasoning_content which still needs to be sent back in subsequent requests.
338
- return {
339
- ...msg,
340
- content: filteredContent,
341
- providerOptions: {
342
- ...msg.providerOptions,
343
- openaiCompatible: {
344
- ...msg.providerOptions?.openaiCompatible,
345
- [field]: reasoningText,
346
- },
347
- },
348
- }
349
- }
350
-
351
- return msg
352
- })
353
- }
354
-
355
- return msgs
356
- }
357
-
358
- function applyCaching(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] {
359
- const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
360
- const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
361
-
362
- const providerOptions = {
363
- anthropic: {
364
- cacheControl: { type: "ephemeral" },
365
- },
366
- openrouter: {
367
- cacheControl: { type: "ephemeral" },
368
- },
369
- bedrock: {
370
- cachePoint: { type: "default" },
371
- },
372
- openaiCompatible: {
373
- cache_control: { type: "ephemeral" },
374
- },
375
- copilot: {
376
- copilot_cache_control: { type: "ephemeral" },
377
- },
378
- alibaba: {
379
- cacheControl: { type: "ephemeral" },
380
- },
381
- }
382
-
383
- for (const msg of unique([...system, ...final])) {
384
- const useMessageLevelOptions =
385
- model.providerID === "anthropic" ||
386
- model.providerID.includes("bedrock") ||
387
- model.api.npm === "@ai-sdk/amazon-bedrock"
388
- const shouldUseContentOptions = !useMessageLevelOptions && Array.isArray(msg.content) && msg.content.length > 0
389
-
390
- if (shouldUseContentOptions) {
391
- const lastContent = msg.content[msg.content.length - 1]
392
- if (
393
- lastContent &&
394
- typeof lastContent === "object" &&
395
- lastContent.type !== "tool-approval-request" &&
396
- lastContent.type !== "tool-approval-response"
397
- ) {
398
- lastContent.providerOptions = mergeDeep(lastContent.providerOptions ?? {}, providerOptions)
399
- continue
400
- }
401
- }
402
-
403
- msg.providerOptions = mergeDeep(msg.providerOptions ?? {}, providerOptions)
404
- }
405
-
406
- return msgs
407
- }
408
-
409
- function unsupportedParts(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] {
410
- return msgs.map((msg) => {
411
- if (msg.role !== "user" || !Array.isArray(msg.content)) return msg
412
-
413
- const filtered = msg.content.map((part) => {
414
- if (part.type !== "file" && part.type !== "image") return part
415
-
416
- // Check for empty base64 image data
417
- if (part.type === "image") {
418
- const imageStr = String(part.image)
419
- if (imageStr.startsWith("data:")) {
420
- const match = imageStr.match(/^data:([^;]+);base64,(.*)$/)
421
- if (match && (!match[2] || match[2].length === 0)) {
422
- return {
423
- type: "text" as const,
424
- text: "ERROR: Image file is empty or corrupted. Please provide a valid image.",
425
- }
426
- }
427
- }
428
- }
429
-
430
- const mime = part.type === "image" ? String(part.image).split(";")[0].replace("data:", "") : part.mediaType
431
- const filename = part.type === "file" ? part.filename : undefined
432
- const modality = mimeToModality(mime)
433
- if (!modality) return part
434
- if (model.capabilities.input[modality]) return part
435
-
436
- const name = filename ? `"${filename}"` : modality
437
- return {
438
- type: "text" as const,
439
- text: `ERROR: Cannot read ${name} (this model does not support ${modality} input). Inform the user.`,
440
- }
441
- })
442
-
443
- return { ...msg, content: filtered }
444
- })
445
- }
446
-
447
- function mapProviderOptions(
448
- msgs: ModelMessage[],
449
- transform: (options: Record<string, any> | undefined) => Record<string, any> | undefined,
450
- ) {
451
- return msgs.map((msg) => {
452
- if (!Array.isArray(msg.content)) return { ...msg, providerOptions: transform(msg.providerOptions) }
453
- return {
454
- ...msg,
455
- providerOptions: transform(msg.providerOptions),
456
- content: msg.content.map((part) =>
457
- part.type === "tool-approval-request" || part.type === "tool-approval-response"
458
- ? part
459
- : { ...part, providerOptions: transform(part.providerOptions) },
460
- ),
461
- } as typeof msg
462
- })
463
- }
464
-
465
- export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
466
- msgs = unsupportedParts(msgs, model)
467
- msgs = normalizeMessages(msgs, model, options)
468
- const usesAnthropicAutomaticCaching =
469
- options.cacheControl !== undefined &&
470
- (model.api.npm === "@ai-sdk/anthropic" || model.api.npm === "@ai-sdk/google-vertex/anthropic")
471
- if (
472
- (model.providerID === "anthropic" ||
473
- model.providerID === "google-vertex-anthropic" ||
474
- model.api.id.includes("anthropic") ||
475
- model.api.id.includes("claude") ||
476
- model.id.includes("anthropic") ||
477
- model.id.includes("claude") ||
478
- model.api.npm === "@ai-sdk/anthropic" ||
479
- model.api.npm === "@ai-sdk/alibaba" ||
480
- model.api.npm === "@ai-sdk/openai-compatible") &&
481
- model.api.npm !== "@ai-sdk/gateway" &&
482
- !usesAnthropicAutomaticCaching
483
- ) {
484
- msgs = applyCaching(msgs, model)
485
- }
486
-
487
- // Remap providerOptions keys from stored providerID to expected SDK key
488
- const key = sdkKey(model.api.npm)
489
- if (key && key !== model.providerID) {
490
- const remap = (opts: Record<string, any> | undefined) => {
491
- if (!opts) return opts
492
- if (!(model.providerID in opts)) return opts
493
- const result = { ...opts }
494
- result[key] = result[model.providerID]
495
- delete result[model.providerID]
496
- return result
497
- }
498
-
499
- msgs = mapProviderOptions(msgs, remap)
500
- }
501
-
502
- // Strip Responses item IDs before serialization, following Codex and keeping signed request bodies immutable.
503
- if (
504
- options.store !== true &&
505
- key &&
506
- ["@ai-sdk/openai", "@ai-sdk/azure", "@ai-sdk/amazon-bedrock/mantle", "@ai-sdk/github-copilot"].includes(
507
- model.api.npm,
508
- )
509
- ) {
510
- msgs = mapProviderOptions(msgs, (options) => {
511
- if (!options?.[key] || !("itemId" in options[key])) return options
512
- const metadata = { ...options[key] }
513
- delete metadata.itemId
514
- return { ...options, [key]: metadata }
515
- })
516
- }
517
-
518
- return msgs
519
- }
520
-
521
- const GEMINI_MODELS_WITH_SAMPLING_DEFAULTS = [
522
- /gemini-2[.-]5(?:[.-]|$)/,
523
- /gemini-3-(?:flash|pro)(?:[.-]|$)/,
524
- /gemini-3[.-]1(?:[.-]|$)/,
525
- /gemini-3[.-]5-flash(?!-lite)(?:[.-]|$)/,
526
- ]
527
-
528
- export function temperature(model: Provider.Model) {
529
- const id = model.api.id.toLowerCase()
530
- if (id.includes("north-mini-code")) return 1.0
531
- if (id.includes("claude")) return undefined
532
- if (id.includes("gemini"))
533
- return GEMINI_MODELS_WITH_SAMPLING_DEFAULTS.some((model) => model.test(id)) ? 1.0 : undefined
534
- if (id.includes("glm-4.6")) return 1.0
535
- if (id.includes("glm-4.7")) return 1.0
536
- if (id.includes("minimax-m2")) return 1.0
537
- if (id.includes("kimi-k2")) {
538
- // kimi-k2-thinking & kimi-k2.5 && kimi-k2p5 && kimi-k2-5
539
- if (["thinking", "k2.", "k2p", "k2-5"].some((s) => id.includes(s))) {
540
- return 1.0
541
- }
542
- return 0.6
543
- }
544
- return undefined
545
- }
546
-
547
- export function topP(model: Provider.Model) {
548
- const id = model.api.id.toLowerCase()
549
- if (id.includes("gemini"))
550
- return GEMINI_MODELS_WITH_SAMPLING_DEFAULTS.some((model) => model.test(id)) ? 0.95 : undefined
551
- if (["minimax-m2", "kimi-k2.5", "kimi-k2p5", "kimi-k2-5"].some((s) => id.includes(s))) {
552
- return 0.95
553
- }
554
- if (
555
- ["deepseek-v4-flash-0731", "deepseek-v4-flash:0731"].some((name) => id.includes(name)) ||
556
- (id.includes("deepseek-v4-flash") && (model.providerID === "deepseek" || model.providerID.startsWith("opencode")))
557
- ) {
558
- return 0.95
559
- }
560
- return undefined
561
- }
562
-
563
- export function topK(model: Provider.Model) {
564
- const id = model.api.id.toLowerCase()
565
- if (id.includes("minimax-m2")) {
566
- if (["m2.", "m25", "m21"].some((s) => id.includes(s))) return 40
567
- return 20
568
- }
569
- if (id.includes("gemini"))
570
- return GEMINI_MODELS_WITH_SAMPLING_DEFAULTS.some((model) => model.test(id)) ? 64 : undefined
571
- return undefined
572
- }
573
-
574
- const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
575
- const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
576
- const OPENAI_GPT5_1_EFFORTS = ["none", ...WIDELY_SUPPORTED_EFFORTS]
577
- const OPENAI_GPT5_2_PLUS_EFFORTS = [...OPENAI_GPT5_1_EFFORTS, "xhigh"]
578
- const OPENAI_GPT5_PRO_EFFORTS = ["high"]
579
- const OPENAI_GPT5_PRO_2_PLUS_EFFORTS = ["medium", "high", "xhigh"]
580
- const OPENAI_GPT5_CHAT_EFFORTS = ["medium"]
581
- const OPENAI_GPT5_CODEX_XHIGH_EFFORTS = [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
582
- const OPENAI_GPT5_CODEX_3_PLUS_EFFORTS = ["none", ...OPENAI_GPT5_CODEX_XHIGH_EFFORTS]
583
-
584
- // OpenAI rolled out the `none` reasoning_effort tier on this date (Responses API).
585
- // Models released before it 400 on `reasoning_effort: "none"`, so we only expose
586
- // it as a variant for models new enough to accept it.
587
- const OPENAI_NONE_EFFORT_RELEASE_DATE = "2025-11-13"
588
-
589
- // OpenAI rolled out the `xhigh` reasoning_effort tier on this date. Same reasoning.
590
- const OPENAI_XHIGH_EFFORT_RELEASE_DATE = "2025-12-04"
591
-
592
- // Matches members of the gpt-5 family across the id formats we encounter:
593
- // "gpt-5", "gpt-5-nano", "gpt-5.4", "openai/gpt-5.4-codex".
594
- // Anchored to start-of-string or "/" so it doesn't false-match "gpt-50" or "gpt-5o".
595
- const GPT5_FAMILY_RE = /(?:^|\/)gpt-5(?:[.-]|$)/
596
- const GPT5_VERSION_RE = /(?:^|\/)gpt-5[.-](\d+)(?:[.-]|$)/
597
- const GPT5_PRO_RE = /(?:^|\/)gpt-5[.-]?pro(?:[.-]|$)/
598
- const GPT5_VERSIONED_PRO_RE = /(?:^|\/)gpt-5[.-]\d+[.-]pro(?:[.-]|$)/
599
-
600
- function gpt5Version(apiId: string) {
601
- return Number(GPT5_VERSION_RE.exec(apiId)?.[1]) || undefined
602
- }
603
-
604
- function versionedGpt5ReasoningEfforts(apiId: string) {
605
- if (GPT5_VERSIONED_PRO_RE.test(apiId)) return OPENAI_GPT5_PRO_2_PLUS_EFFORTS
606
- const version = gpt5Version(apiId)
607
- if (version === undefined) return undefined
608
- if (version === 1) return OPENAI_GPT5_1_EFFORTS
609
- return OPENAI_GPT5_2_PLUS_EFFORTS
610
- }
611
-
612
- function gpt5CodexReasoningEfforts(apiId: string) {
613
- if (!GPT5_FAMILY_RE.test(apiId) || !apiId.includes("codex")) return undefined
614
- const version = gpt5Version(apiId)
615
- if (version !== undefined && version >= 3) return OPENAI_GPT5_CODEX_3_PLUS_EFFORTS
616
- if (apiId.includes("codex-max") || (version !== undefined && version >= 2)) return OPENAI_GPT5_CODEX_XHIGH_EFFORTS
617
- return WIDELY_SUPPORTED_EFFORTS
618
- }
619
-
620
- function gpt5ChatReasoningEfforts(apiId: string) {
621
- if (!GPT5_FAMILY_RE.test(apiId) || !apiId.includes("-chat")) return undefined
622
- return gpt5Version(apiId) === undefined ? [] : OPENAI_GPT5_CHAT_EFFORTS
623
- }
624
-
625
- // Computes the reasoning_effort tiers an OpenAI (or OpenAI-compatible upstream
626
- // routed through it, e.g. cf-ai-gateway) model exposes. Effort order: weakest
627
- // to strongest.
628
- function openaiReasoningEfforts(apiId: string, releaseDate: string) {
629
- const id = apiId.toLowerCase()
630
- if (id.includes("deep-research")) return ["medium"]
631
- const chatEfforts = gpt5ChatReasoningEfforts(id)
632
- if (chatEfforts) return chatEfforts
633
- if (GPT5_PRO_RE.test(id)) return OPENAI_GPT5_PRO_EFFORTS
634
- const codexEfforts = gpt5CodexReasoningEfforts(id)
635
- if (codexEfforts) return codexEfforts
636
- const versionedEfforts = versionedGpt5ReasoningEfforts(id)
637
- // GPT-5.1 replaced GPT-5's `minimal` effort with `none`; GPT-5.2+
638
- // additionally accepts `xhigh`. Model pages list the supported subset.
639
- if (versionedEfforts) return versionedEfforts
640
- const efforts = [...WIDELY_SUPPORTED_EFFORTS]
641
- if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal")
642
- if (releaseDate >= OPENAI_NONE_EFFORT_RELEASE_DATE) efforts.unshift("none")
643
- if (releaseDate >= OPENAI_XHIGH_EFFORT_RELEASE_DATE) efforts.push("xhigh")
644
- return efforts
645
- }
646
-
647
- function openaiCompatibleReasoningEfforts(id: string) {
648
- const apiId = id.toLowerCase()
649
- const chatEfforts = gpt5ChatReasoningEfforts(apiId)
650
- if (chatEfforts) return chatEfforts
651
- if (GPT5_PRO_RE.test(apiId)) return OPENAI_GPT5_PRO_EFFORTS
652
- return gpt5CodexReasoningEfforts(apiId) ?? versionedGpt5ReasoningEfforts(apiId) ?? OPENAI_EFFORTS
653
- }
654
-
655
- function anthropicUsesModernAdaptiveThinking(apiId: string) {
656
- if (!apiId.toLowerCase().includes("claude-")) return false
657
- // Covers family-first IDs such as claude-opus-4.7 and version-first IDs such as claude-4.7-opus.
658
- // Limit minors to two digits so release dates in IDs such as claude-opus-4-20250514 are not versions.
659
- const version = /claude-(?:[a-z]+-)?(\d+)(?:[.-](\d{1,2}))?(?:[.@-]|$)/i.exec(apiId)
660
- if (!version) return true
661
- const major = Number(version[1])
662
- const minor = Number(version[2] ?? 0)
663
- return major > 4 || (major === 4 && minor >= 7)
664
- }
665
-
666
- function anthropicOpus45(apiId: string) {
667
- return ["opus-4-5", "opus-4.5"].some((value) => apiId.includes(value))
668
- }
669
-
670
- function anthropicAdaptiveEfforts(apiId: string): string[] | null {
671
- if (anthropicUsesModernAdaptiveThinking(apiId)) {
672
- return ["low", "medium", "high", "xhigh", "max"]
673
- }
674
- if (
675
- ["opus-4-6", "opus-4.6", "4-6-opus", "4.6-opus", "sonnet-4-6", "sonnet-4.6", "4-6-sonnet", "4.6-sonnet"].some((v) =>
676
- apiId.includes(v),
677
- )
678
- ) {
679
- return ["low", "medium", "high", "max"]
680
- }
681
- return null
682
- }
683
-
684
- function anthropicOmitsThinking(apiId: string) {
685
- return anthropicUsesModernAdaptiveThinking(apiId)
686
- }
687
-
688
- // Default to binding controls for Claude 5.1+ as enforcement expands to later models.
689
- // Mythos 5.1 explicitly does not run the conversation-prefix check.
690
- // https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-in-conversation
691
- function anthropicBindsThinking(apiId: string) {
692
- // Capture either family/version order, without reading release dates as minor versions.
693
- const version = /claude-(?:([a-z]+)-)?(\d+)(?:[.-](\d{1,2}))?(?:-([a-z]+))?(?:[.@-]|$)/i.exec(apiId)
694
- if (!version) return false
695
- const major = Number(version[2])
696
- const minor = Number(version[3] ?? 0)
697
- if (major === 5 && minor === 1 && (version[1] ?? version[4])?.toLowerCase() === "mythos") return false
698
- return major > 5 || (major === 5 && minor >= 1)
699
- }
700
-
701
- // Fable 5.1 binds each thinking signature to the system prompt, tool list, and
702
- // messages above it, and rejects the request when any of that changes. opencode
703
- // re-renders parts of that prefix between turns (system prompt, tools, compaction),
704
- // so ask the API to drop the affected blocks instead of failing the request.
705
- // Older model deployments may reject this field, even with thinking enabled.
706
- // The patched AI SDK adds the thinking-binding-controls beta whenever it is set.
707
- const ANTHROPIC_BLOCK_BINDING = { prefixMismatchBehavior: "drop_block" }
708
-
709
- function anthropicBlockBinding(model: Provider.Model, options: { [x: string]: any }) {
710
- const sdk = sdkKey(model.api.npm)
711
- const key = sdk === "bedrock" ? "reasoningConfig" : sdk === "anthropic" ? "thinking" : undefined
712
- // Consume the OpenCode-only opt-out even on models outside the default scope.
713
- if (key && options[key]?.blockBinding === false) {
714
- const result = { ...options, [key]: { ...options[key] } }
715
- delete result[key].blockBinding
716
- if (Object.keys(result[key]).length === 0) delete result[key]
717
- return result
718
- }
719
-
720
- if (!anthropicBindsThinking(model.api.id)) return options
721
- switch (model.api.npm) {
722
- case "@ai-sdk/anthropic":
723
- case "@ai-sdk/google-vertex/anthropic": {
724
- const thinking = options.thinking ?? { type: "adaptive" }
725
- if (thinking.type !== "adaptive" && thinking.type !== "enabled") return options
726
- if (thinking.blockBinding !== undefined) return options
727
- return { ...options, thinking: { ...thinking, blockBinding: ANTHROPIC_BLOCK_BINDING } }
728
- }
729
- case "@ai-sdk/amazon-bedrock": {
730
- const reasoningConfig = options.reasoningConfig ?? { type: "adaptive" }
731
- if (reasoningConfig.type !== "adaptive" && reasoningConfig.type !== "enabled") return options
732
- if (reasoningConfig.blockBinding !== undefined) return options
733
- return { ...options, reasoningConfig: { ...reasoningConfig, blockBinding: ANTHROPIC_BLOCK_BINDING } }
734
- }
735
- }
736
- return options
737
- }
738
-
739
- function googleThinkingLevelEfforts(apiId: string) {
740
- const id = apiId.toLowerCase()
741
- if (!id.includes("gemini-3")) return ["low", "high"]
742
- if (id.includes("flash-image")) return ["minimal", "high"]
743
- if (id.includes("pro-image")) return ["high"]
744
- if (id.includes("flash")) return ["minimal", "low", "medium", "high"]
745
- return ["low", "medium", "high"]
746
- }
747
-
748
- function googleThinkingBudgetMax(apiId: string) {
749
- const id = apiId.toLowerCase()
750
- if (id.includes("2.5") && id.includes("pro") && !id.includes("flash")) return 32_768
751
- return 24_576
752
- }
753
-
754
- // SAP's Zod schema drops unknown top-level keys; reasoning controls survive
755
- // only via `modelParams` (catchall), forwarded verbatim by the SAP SDKs.
756
- function wrapInSapModelParams(variants: Record<string, Record<string, any>>): Record<string, Record<string, any>> {
757
- return Object.fromEntries(Object.entries(variants).map(([k, v]) => [k, { modelParams: v }]))
758
- }
759
-
760
- function googleThinkingVariants(model: Provider.Model): Record<string, Record<string, any>> {
761
- const id = model.api.id.toLowerCase()
762
- if (id.includes("2.5")) {
763
- return {
764
- high: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
765
- max: {
766
- thinkingConfig: { includeThoughts: true, thinkingBudget: googleThinkingBudgetMax(id) },
767
- },
768
- }
769
- }
770
- return Object.fromEntries(
771
- googleThinkingLevelEfforts(id).map((effort) => [
772
- effort,
773
- { thinkingConfig: { includeThoughts: true, thinkingLevel: effort } },
774
- ]),
775
- )
776
- }
777
-
778
- export function variants(model: Provider.Model): Record<string, Record<string, any>> {
779
- if (!model.capabilities.reasoning) return {}
780
-
781
- const id = model.id.toLowerCase()
782
- const glm52 = ["glm-5.2", "glm-5-2", "glm-5p2"].some(
783
- (name) => id.includes(name) || model.api.id.toLowerCase().includes(name),
784
- )
785
- if (
786
- model.api.id.toLowerCase().includes("minimax-m3") &&
787
- ["@ai-sdk/anthropic", "@ai-sdk/openai-compatible"].includes(model.api.npm)
788
- ) {
789
- if (["nvidia", "lilac"].includes(model.providerID)) {
790
- return {
791
- none: { chat_template_kwargs: { thinking_mode: "disabled" } },
792
- thinking: { chat_template_kwargs: { thinking_mode: "enabled" } },
793
- }
794
- }
795
- return {
796
- none: { thinking: { type: "disabled" } },
797
- thinking: { thinking: { type: "adaptive" } },
798
- }
799
- }
800
- const adaptiveThinkingOmitted = anthropicOmitsThinking(model.api.id)
801
- const adaptiveEfforts = anthropicAdaptiveEfforts(model.api.id)
802
- if (glm52 && model.api.npm === "@openrouter/ai-sdk-provider") {
803
- // OpenRouter maps xhigh to GLM-5.2's native max effort.
804
- return {
805
- high: { reasoning: { effort: "high" } },
806
- xhigh: { reasoning: { effort: "xhigh" } },
807
- }
808
- }
809
- if (glm52 && model.api.npm === "@ai-sdk/openai-compatible") {
810
- return {
811
- high: { reasoningEffort: "high" },
812
- max: { reasoningEffort: "max" },
813
- }
814
- }
815
- if (glm52 && model.api.npm === "@ai-sdk/anthropic") {
816
- return {
817
- high: { effort: "high" },
818
- max: { effort: "max" },
819
- }
820
- }
821
- // Kimi's Anthropic-compatible transports implement adaptive thinking effort.
822
- if (isKimiFamily(model) && ["@ai-sdk/anthropic", "@ai-sdk/google-vertex/anthropic"].includes(model.api.npm)) {
823
- return Object.fromEntries(
824
- ["low", "medium", "high", "xhigh", "max"].map((effort) => [
825
- effort,
826
- { thinking: { type: "adaptive", display: "summarized" }, effort },
827
- ]),
828
- )
829
- }
830
- if (
831
- id.includes("deepseek-chat") ||
832
- id.includes("deepseek-reasoner") ||
833
- id.includes("deepseek-r1") ||
834
- id.includes("deepseek-v3") ||
835
- id.includes("minimax") ||
836
- (id.includes("glm") && !glm52) ||
837
- id.includes("kimi") ||
838
- id.includes("k2p") ||
839
- id.includes("qwen") ||
840
- id.includes("big-pickle")
841
- )
842
- return {}
843
-
844
- // see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks
845
- if (id.includes("grok") && id.includes("grok-3-mini")) {
846
- if (model.api.npm === "@openrouter/ai-sdk-provider") {
847
- return {
848
- low: { reasoning: { effort: "low" } },
849
- high: { reasoning: { effort: "high" } },
850
- }
851
- }
852
- return {
853
- low: { reasoningEffort: "low" },
854
- high: { reasoningEffort: "high" },
855
- }
856
- }
857
-
858
- switch (model.api.npm) {
859
- case "@openrouter/ai-sdk-provider":
860
- return Object.fromEntries(
861
- (model.api.id.startsWith("openai/") || id.includes("gpt")
862
- ? openaiCompatibleReasoningEfforts(model.api.id)
863
- : WIDELY_SUPPORTED_EFFORTS
864
- ).map((effort) => [effort, { reasoning: { effort } }]),
865
- )
866
-
867
- case "ai-gateway-provider": {
868
- // Cloudflare AI Gateway routes every upstream through its OpenAI-compatible
869
- // /v1/compat endpoint, so the body is always OAI-shaped. The gateway
870
- // translates `reasoning_effort` to the upstream provider's native control
871
- // (e.g. Anthropic thinking budgets) when needed. Variants therefore stay
872
- // OAI-style for all upstreams, with an extended effort set for OpenAI
873
- // models that support it.
874
- if (model.api.id.startsWith("openai/")) {
875
- const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
876
- return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
877
- }
878
- return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
879
- }
880
-
881
- case "@ai-sdk/gateway":
882
- if (model.api.id.includes("anthropic")) {
883
- if (adaptiveEfforts) {
884
- return Object.fromEntries(
885
- adaptiveEfforts.map((effort) => [
886
- effort,
887
- {
888
- thinking: {
889
- type: "adaptive",
890
- // Newer adaptive-only models default `display` to "omitted", which
891
- // returns empty thinking blocks. Force "summarized" so summaries
892
- // survive (4.6/Sonnet 4.6 already default to "summarized").
893
- ...(adaptiveThinkingOmitted ? { display: "summarized" } : {}),
894
- },
895
- effort,
896
- },
897
- ]),
898
- )
899
- }
900
- return {
901
- high: {
902
- thinking: {
903
- type: "enabled",
904
- budgetTokens: 16000,
905
- },
906
- },
907
- max: {
908
- thinking: {
909
- type: "enabled",
910
- budgetTokens: 31999,
911
- },
912
- },
913
- }
914
- }
915
- if (model.api.id.includes("google")) {
916
- if (model.api.id.includes("2.5")) {
917
- return {
918
- high: {
919
- thinkingConfig: {
920
- includeThoughts: true,
921
- thinkingBudget: 16000,
922
- },
923
- },
924
- max: {
925
- thinkingConfig: {
926
- includeThoughts: true,
927
- thinkingBudget: googleThinkingBudgetMax(model.api.id.toLowerCase()),
928
- },
929
- },
930
- }
931
- }
932
- return Object.fromEntries(
933
- ["low", "high"].map((effort) => [
934
- effort,
935
- {
936
- includeThoughts: true,
937
- thinkingLevel: effort,
938
- },
939
- ]),
940
- )
941
- }
942
- return Object.fromEntries(
943
- openaiCompatibleReasoningEfforts(model.api.id).map((effort) => [effort, { reasoningEffort: effort }]),
944
- )
945
-
946
- case "@ai-sdk/github-copilot":
947
- if (model.id.includes("gemini")) {
948
- // currently github copilot only returns thinking
949
- return {}
950
- }
951
- if (model.id.includes("claude")) {
952
- return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
953
- }
954
- const copilotEfforts = iife(() => {
955
- if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3"))
956
- return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
957
- const arr = [...WIDELY_SUPPORTED_EFFORTS]
958
- if (id.includes("gpt-5") && model.release_date >= "2025-12-04") arr.push("xhigh")
959
- return arr
960
- })
961
- return Object.fromEntries(
962
- copilotEfforts.map((effort) => [
963
- effort,
964
- {
965
- reasoningEffort: effort,
966
- reasoningSummary: "auto",
967
- include: INCLUDE_ENCRYPTED_REASONING,
968
- },
969
- ]),
970
- )
971
-
972
- case "@ai-sdk/cerebras":
973
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/cerebras
974
- case "@ai-sdk/togetherai":
975
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/togetherai
976
- case "@ai-sdk/xai":
977
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/xai
978
- case "@ai-sdk/deepinfra":
979
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/deepinfra
980
- case "venice-ai-sdk-provider":
981
- // https://docs.venice.ai/overview/guides/reasoning-models#reasoning-effort
982
- case "@ai-sdk/openai-compatible":
983
- if (model.api.id.toLowerCase().includes("north-mini-code")) {
984
- return Object.fromEntries(["none", "high"].map((effort) => [effort, { reasoningEffort: effort }]))
985
- }
986
- const efforts = [...WIDELY_SUPPORTED_EFFORTS]
987
- if (model.api.id.toLowerCase().includes("deepseek-v4")) {
988
- efforts.push("max")
989
- }
990
- return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]))
991
-
992
- case "@ai-sdk/azure":
993
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
994
- if (id === "o1-mini") return {}
995
- return Object.fromEntries(
996
- openaiReasoningEfforts(id, model.release_date).map((effort) => [
997
- effort,
998
- {
999
- reasoningEffort: effort,
1000
- reasoningSummary: "auto",
1001
- include: INCLUDE_ENCRYPTED_REASONING,
1002
- },
1003
- ]),
1004
- )
1005
- case "@ai-sdk/amazon-bedrock/mantle":
1006
- case "@ai-sdk/openai": {
1007
- if (model.providerID === "meta") {
1008
- return Object.fromEntries(
1009
- OPENAI_EFFORTS.map((effort) => [
1010
- effort,
1011
- {
1012
- reasoningEffort: effort,
1013
- reasoningSummary: "auto",
1014
- include: INCLUDE_ENCRYPTED_REASONING,
1015
- },
1016
- ]),
1017
- )
1018
- }
1019
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
1020
- const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
1021
- return Object.fromEntries(
1022
- efforts.map((effort) => [
1023
- effort,
1024
- {
1025
- reasoningEffort: effort,
1026
- reasoningSummary: "auto",
1027
- include: INCLUDE_ENCRYPTED_REASONING,
1028
- },
1029
- ]),
1030
- )
1031
- }
1032
-
1033
- case "@ai-sdk/anthropic":
1034
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
1035
- case "@ai-sdk/google-vertex/anthropic":
1036
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
1037
- if (adaptiveEfforts) {
1038
- let efforts = [...adaptiveEfforts]
1039
- if (model.providerID === "github-copilot") {
1040
- if (model.api.id.includes("opus-4.7")) {
1041
- efforts = ["medium"]
1042
- }
1043
- // Efforts currently supported are: low, medium, high
1044
- efforts = efforts.filter((v) => v !== "max" && v !== "xhigh")
1045
- }
1046
- return Object.fromEntries(
1047
- efforts.map((effort) => [
1048
- effort,
1049
- {
1050
- thinking: {
1051
- type: "adaptive",
1052
- ...(adaptiveThinkingOmitted ? { display: "summarized" } : {}),
1053
- },
1054
- effort,
1055
- },
1056
- ]),
1057
- )
1058
- }
1059
-
1060
- if (anthropicOpus45(model.api.id)) {
1061
- return Object.fromEntries(
1062
- WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, anthropicOpus45Effort(model, effort)]),
1063
- )
1064
- }
1065
-
1066
- return {
1067
- high: {
1068
- thinking: {
1069
- type: "enabled",
1070
- budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
1071
- },
1072
- },
1073
- max: {
1074
- thinking: {
1075
- type: "enabled",
1076
- budgetTokens: Math.min(31_999, model.limit.output - 1),
1077
- },
1078
- },
1079
- }
1080
-
1081
- case "@ai-sdk/amazon-bedrock":
1082
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock
1083
- if (adaptiveEfforts) {
1084
- return Object.fromEntries(
1085
- adaptiveEfforts.map((effort) => [
1086
- effort,
1087
- {
1088
- reasoningConfig: {
1089
- type: "adaptive",
1090
- maxReasoningEffort: effort,
1091
- ...(adaptiveThinkingOmitted ? { display: "summarized" } : {}),
1092
- },
1093
- },
1094
- ]),
1095
- )
1096
- }
1097
- // For Anthropic models on Bedrock, use reasoningConfig with budgetTokens
1098
- if (model.api.id.includes("anthropic")) {
1099
- return {
1100
- high: {
1101
- reasoningConfig: {
1102
- type: "enabled",
1103
- budgetTokens: 16000,
1104
- },
1105
- },
1106
- max: {
1107
- reasoningConfig: {
1108
- type: "enabled",
1109
- budgetTokens: 31999,
1110
- },
1111
- },
1112
- }
1113
- }
1114
-
1115
- // For Amazon Nova models, use reasoningConfig with maxReasoningEffort
1116
- return Object.fromEntries(
1117
- WIDELY_SUPPORTED_EFFORTS.map((effort) => [
1118
- effort,
1119
- {
1120
- reasoningConfig: {
1121
- type: "enabled",
1122
- maxReasoningEffort: effort,
1123
- },
1124
- },
1125
- ]),
1126
- )
1127
-
1128
- case "@ai-sdk/google-vertex":
1129
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex
1130
- case "@ai-sdk/google":
1131
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai
1132
- return googleThinkingVariants(model)
1133
-
1134
- case "@ai-sdk/mistral":
1135
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/mistral
1136
- // https://docs.mistral.ai/capabilities/reasoning/adjustable
1137
- if (!model.capabilities.reasoning) return {}
1138
- // Only Mistral Small 4 and Medium 3.5 support reasoning
1139
- const MISTRAL_REASONING_IDS = [
1140
- "mistral-small-2603",
1141
- "mistral-small-latest",
1142
- "mistral-medium-3.5",
1143
- "mistral-medium-2604",
1144
- ]
1145
- const mistralId = model.api.id.toLowerCase()
1146
- if (!MISTRAL_REASONING_IDS.some((id) => mistralId.includes(id))) return {}
1147
- return {
1148
- high: { reasoningEffort: "high" },
1149
- }
1150
-
1151
- case "@ai-sdk/cohere":
1152
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/cohere
1153
- return {}
1154
-
1155
- case "@ai-sdk/groq":
1156
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/groq
1157
- const groqEffort = ["none", ...WIDELY_SUPPORTED_EFFORTS]
1158
- return Object.fromEntries(
1159
- groqEffort.map((effort) => [
1160
- effort,
1161
- {
1162
- reasoningEffort: effort,
1163
- },
1164
- ]),
1165
- )
1166
-
1167
- case "@ai-sdk/perplexity":
1168
- // https://v5.ai-sdk.dev/providers/ai-sdk-providers/perplexity
1169
- return {}
1170
-
1171
- case "@jerome-benoit/sap-ai-provider-v2": {
1172
- if (id.includes("anthropic")) {
1173
- if (adaptiveEfforts) {
1174
- // Bedrock adaptive splits `effort` out into `output_config` (vs Anthropic
1175
- // native which inlines it). Claude 4.7+ defaults `display` to "omitted".
1176
- return wrapInSapModelParams(
1177
- Object.fromEntries(
1178
- adaptiveEfforts.map((effort) => [
1179
- effort,
1180
- {
1181
- thinking: { type: "adaptive", ...(adaptiveThinkingOmitted ? { display: "summarized" } : {}) },
1182
- output_config: { effort },
1183
- },
1184
- ]),
1185
- ),
1186
- )
1187
- }
1188
- return wrapInSapModelParams({
1189
- high: { thinking: { type: "enabled", budget_tokens: 16000 } },
1190
- max: { thinking: { type: "enabled", budget_tokens: 31999 } },
1191
- })
1192
- }
1193
- if (id.includes("gemini") && id.includes("2.5")) {
1194
- return wrapInSapModelParams(googleThinkingVariants(model))
1195
- }
1196
- if (id.includes("gpt") || /\bo[1-9]/.test(id)) {
1197
- const efforts = openaiReasoningEfforts(id, model.release_date)
1198
- return wrapInSapModelParams(Object.fromEntries(efforts.map((effort) => [effort, { reasoning_effort: effort }])))
1199
- }
1200
- return wrapInSapModelParams(
1201
- Object.fromEntries(["low", "medium", "high"].map((effort) => [effort, { reasoning_effort: effort }])),
1202
- )
1203
- }
1204
- }
1205
- return {}
1206
- }
1207
-
1208
- export function options(input: {
1209
- model: Provider.Model
1210
- sessionID: string
1211
- providerOptions?: Record<string, any>
1212
- }): Record<string, any> {
1213
- const result: Record<string, any> = {}
1214
-
1215
- if (
1216
- input.model.api.npm === "@ai-sdk/google-vertex/anthropic" ||
1217
- (!input.model.api.id.includes("claude") && input.model.api.npm === "@ai-sdk/anthropic")
1218
- ) {
1219
- result["toolStreaming"] = false
1220
- }
1221
-
1222
- // openai and providers using openai package should set store to false by default.
1223
- if (
1224
- input.model.providerID === "openai" ||
1225
- input.model.api.npm === "@ai-sdk/openai" ||
1226
- input.model.api.npm === "@ai-sdk/github-copilot" ||
1227
- input.model.api.npm === "@ai-sdk/amazon-bedrock/mantle" ||
1228
- input.model.api.npm === "@ai-sdk/xai"
1229
- ) {
1230
- result["store"] = false
1231
- }
1232
-
1233
- if (input.model.api.npm === "@ai-sdk/azure") {
1234
- result["store"] = false
1235
- }
1236
-
1237
- if (input.model.api.npm === "@openrouter/ai-sdk-provider" || input.model.api.npm === "@llmgateway/ai-sdk-provider") {
1238
- result["usage"] = {
1239
- include: true,
1240
- }
1241
- if (input.model.api.id.includes("gemini-3")) {
1242
- result["reasoning"] = { effort: "high" }
1243
- }
1244
- }
1245
-
1246
- if (
1247
- input.model.providerID === "baseten" ||
1248
- (input.model.providerID === "opencode" && ["kimi-k2-thinking", "glm-4.6"].includes(input.model.api.id))
1249
- ) {
1250
- result["chat_template_args"] = { enable_thinking: true }
1251
- }
1252
-
1253
- if (
1254
- ["zai", "zhipuai"].some((id) => input.model.providerID.includes(id)) &&
1255
- input.model.api.npm === "@ai-sdk/openai-compatible"
1256
- ) {
1257
- result["thinking"] = {
1258
- type: "enabled",
1259
- clear_thinking: false,
1260
- }
1261
- }
1262
-
1263
- if (input.model.providerID === "meta" && input.model.api.npm === "@ai-sdk/openai") {
1264
- result["reasoningSummary"] = "auto"
1265
- result["include"] = INCLUDE_ENCRYPTED_REASONING
1266
- }
1267
-
1268
- if (input.model.api.npm === "@ai-sdk/google" || input.model.api.npm === "@ai-sdk/google-vertex") {
1269
- if (input.model.capabilities.reasoning) {
1270
- result["thinkingConfig"] = {
1271
- includeThoughts: true,
1272
- }
1273
- if (input.model.api.id.includes("gemini-3")) {
1274
- result["thinkingConfig"]["thinkingLevel"] = "high"
1275
- }
1276
- }
1277
- }
1278
-
1279
- const modelId = input.model.api.id.toLowerCase()
1280
-
1281
- // Atria and other OpenAI-compatible models emit reasoning_content by default,
1282
- // which spends output tokens on thinking even for trivial replies. Unless the
1283
- // model is configured as a reasoning model, pin reasoning off so replies stay
1284
- // short and fast. This applies to any openai-compatible provider that isn't
1285
- // explicitly a reasoning model.
1286
- if (
1287
- input.model.api.npm === "@ai-sdk/openai-compatible" &&
1288
- !input.model.capabilities.reasoning
1289
- ) {
1290
- result["reasoning_effort"] = "none"
1291
- }
1292
-
1293
- // MiniMax's Anthropic interface defaults thinking off, unlike Chat Completions.
1294
- if (modelId.includes("minimax-m3") && input.model.api.npm === "@ai-sdk/anthropic") {
1295
- result["thinking"] = { type: "adaptive" }
1296
- }
1297
-
1298
- // Moonshot's Anthropic-compatible API uses adaptive effort rather than token budgets.
1299
- // Request summaries so thinking content survives replay on subsequent turns.
1300
- if (
1301
- ["@ai-sdk/anthropic", "@ai-sdk/google-vertex/anthropic"].includes(input.model.api.npm) &&
1302
- isKimiFamily(input.model) &&
1303
- input.model.capabilities.reasoning
1304
- ) {
1305
- result["thinking"] = { type: "adaptive", display: "summarized" }
1306
- result["effort"] = "high"
1307
- }
1308
-
1309
- // Enable thinking for reasoning models on alibaba-cn (DashScope).
1310
- // DashScope's OpenAI-compatible API requires `enable_thinking: true` in the request body
1311
- // to return reasoning_content. Without it, models like kimi-k2.5, qwen-plus, qwen3, qwq,
1312
- // deepseek-r1, etc. never output thinking/reasoning tokens.
1313
- // Note: kimi-k2-thinking is excluded as it returns reasoning_content by default.
1314
- if (
1315
- input.model.providerID === "alibaba-cn" &&
1316
- input.model.capabilities.reasoning &&
1317
- input.model.api.npm === "@ai-sdk/openai-compatible" &&
1318
- !modelId.includes("kimi-k2-thinking")
1319
- ) {
1320
- result["enable_thinking"] = true
1321
- }
1322
-
1323
- if (input.providerOptions?.setCacheKey !== false) {
1324
- if (input.model.api.npm === "@ai-sdk/deepinfra" || input.model.api.npm === "@ai-sdk/cerebras") {
1325
- result["prompt_cache_key"] = input.sessionID
1326
- } else if (
1327
- input.model.api.npm === "@ai-sdk/openai" ||
1328
- input.model.api.npm === "@ai-sdk/azure" ||
1329
- input.model.api.npm === "@ai-sdk/xai" ||
1330
- input.model.api.npm === "@ai-sdk/mistral" ||
1331
- input.model.api.npm === "@ai-sdk/openai-compatible" ||
1332
- input.model.api.npm === "venice-ai-sdk-provider" ||
1333
- input.providerOptions?.setCacheKey === true
1334
- ) {
1335
- result["promptCacheKey"] = input.sessionID
1336
- }
1337
- }
1338
-
1339
- if (input.model.api.npm === "@ai-sdk/gateway") {
1340
- result["gateway"] = { caching: "auto" }
1341
- }
1342
-
1343
- // Any gpt version above 5.4 in combination with azure does not support reasoningEffort
1344
- // so we should return early here.
1345
- const [, gptMajorVersion, gptMinorVersion] = input.model.api.id.match(/gpt-(\d+)\.(\d+)/) ?? []
1346
- const isGpt55OrNewer = Number(gptMajorVersion) > 5 || (Number(gptMajorVersion) === 5 && Number(gptMinorVersion) >= 5)
1347
- if (input.model.api.npm === "@ai-sdk/azure" && input.providerOptions?.useCompletionUrls) {
1348
- if (!isGpt55OrNewer) {
1349
- result["reasoningEffort"] = "medium"
1350
- }
1351
- return result
1352
- }
1353
-
1354
- if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) {
1355
- if (!input.model.api.id.includes("gpt-5-pro")) {
1356
- result["reasoningEffort"] = "medium"
1357
- if (
1358
- input.model.api.npm === "@ai-sdk/openai" ||
1359
- input.model.api.npm === "@ai-sdk/azure" ||
1360
- input.model.api.npm === "@ai-sdk/github-copilot" ||
1361
- input.model.api.npm === "@ai-sdk/amazon-bedrock/mantle"
1362
- ) {
1363
- result["reasoningSummary"] = "auto"
1364
- }
1365
- if (input.model.api.npm === "@ai-sdk/openai" || input.model.api.npm === "@ai-sdk/amazon-bedrock/mantle") {
1366
- result["include"] = INCLUDE_ENCRYPTED_REASONING
1367
- }
1368
- }
1369
-
1370
- // Generic OpenAI-compatible APIs do not necessarily support OpenAI's verbosity parameter.
1371
- // Only enable the default for integrations known to implement it.
1372
- if (
1373
- input.model.api.id.includes("gpt-5.") &&
1374
- !input.model.api.id.includes("codex") &&
1375
- !input.model.api.id.includes("-chat") &&
1376
- (input.model.api.npm === "@ai-sdk/openai" || input.model.api.npm === "@ai-sdk/amazon-bedrock/mantle")
1377
- ) {
1378
- result["textVerbosity"] = "low"
1379
- }
1380
-
1381
- if (input.model.providerID.startsWith("opencode") && input.providerOptions?.setCacheKey !== false) {
1382
- result["promptCacheKey"] = input.sessionID
1383
- result["include"] = INCLUDE_ENCRYPTED_REASONING
1384
- result["reasoningSummary"] = "auto"
1385
- }
1386
- }
1387
-
1388
- return result
1389
- }
1390
-
1391
- export function smallOptions(model: Provider.Model) {
1392
- const small = Object.values(model.variants ?? {})[0] ?? {}
1393
- if (
1394
- model.providerID === "openai" ||
1395
- model.api.npm === "@ai-sdk/openai" ||
1396
- model.api.npm === "@ai-sdk/github-copilot" ||
1397
- model.api.npm === "@ai-sdk/xai"
1398
- ) {
1399
- const base = { store: false }
1400
- return mergeDeep(base, small)
1401
- }
1402
- if (model.providerID === "openrouter" || model.providerID === "llmgateway") {
1403
- if (Object.keys(small).length === 0 && model.api.id.includes("google")) {
1404
- return { reasoning: { enabled: false } }
1405
- }
1406
- }
1407
-
1408
- if (model.providerID === "venice") {
1409
- if (Object.keys(small).length > 0) return small
1410
- return { veniceParameters: { disableThinking: true } }
1411
- }
1412
-
1413
- return small
1414
- }
1415
-
1416
- // Maps model ID prefix to provider slug used in providerOptions.
1417
- // Example: "amazon/nova-2-lite" → "bedrock"
1418
- const SLUG_OVERRIDES: Record<string, string> = {
1419
- amazon: "bedrock",
1420
- }
1421
-
1422
- export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
1423
- const usesOpenAIReasoningGate =
1424
- model.api.npm === "@ai-sdk/openai" ||
1425
- model.api.npm === "@ai-sdk/azure" ||
1426
- model.api.npm === "@ai-sdk/amazon-bedrock/mantle"
1427
- const normalized =
1428
- usesOpenAIReasoningGate &&
1429
- (model.capabilities.reasoning || options.reasoningEffort !== undefined || options.reasoningSummary !== undefined)
1430
- ? { ...options, forceReasoning: true }
1431
- : anthropicBlockBinding(model, options)
1432
-
1433
- if (model.api.npm === "@ai-sdk/gateway") {
1434
- // Gateway providerOptions are split across two namespaces:
1435
- // - `gateway`: gateway-native routing/caching controls (order, only, byok, etc.)
1436
- // - `<upstream slug>`: provider-specific model options (anthropic/openai/...)
1437
- // We keep `gateway` as-is and route every other top-level option under the
1438
- // model-derived upstream slug.
1439
- const i = model.api.id.indexOf("/")
1440
- const rawSlug = i > 0 ? model.api.id.slice(0, i) : undefined
1441
- const slug = rawSlug ? (SLUG_OVERRIDES[rawSlug] ?? rawSlug) : undefined
1442
- const gateway = normalized.gateway
1443
- const rest = Object.fromEntries(Object.entries(normalized).filter(([k]) => k !== "gateway"))
1444
- const has = Object.keys(rest).length > 0
1445
-
1446
- const result: Record<string, any> = {}
1447
- if (gateway !== undefined) result.gateway = gateway
1448
-
1449
- if (has) {
1450
- if (slug) {
1451
- // Route model-specific options under the provider slug
1452
- result[slug] = rest
1453
- } else if (gateway && typeof gateway === "object" && !Array.isArray(gateway)) {
1454
- result.gateway = { ...gateway, ...rest }
1455
- } else {
1456
- result.gateway = rest
1457
- }
1458
- }
1459
-
1460
- return result
1461
- }
1462
-
1463
- // AI SDK packages that resolve providerOptionsName by splitting the
1464
- // provider name on "." (e.g. "wafer.ai" -> "wafer") need the same
1465
- // logic here so the key we write matches the key they read.
1466
- // Other SDKs (xai, mistral, groq, cohere, etc.) use hardcoded keys
1467
- // like "xai" or "cohere" - applying .split(".")[0] would break those.
1468
- const usesDotSplitOptions =
1469
- model.api.npm === "@ai-sdk/openai-compatible" ||
1470
- model.api.npm === "@ai-sdk/openai" ||
1471
- model.api.npm === "@ai-sdk/anthropic"
1472
- const key = sdkKey(model.api.npm) ?? (usesDotSplitOptions ? model.providerID.split(".")[0] : model.providerID)
1473
- // @ai-sdk/azure delegates to OpenAIChatLanguageModel which reads from
1474
- // providerOptions["openai"], but OpenAIResponsesLanguageModel checks
1475
- // "azure" first. Pass both so model options work on either code path.
1476
- if (model.api.npm === "@ai-sdk/azure") {
1477
- return { openai: normalized, azure: normalized }
1478
- }
1479
- return { [key]: normalized }
1480
- }
1481
-
1482
- export function maxOutputTokens(model: Provider.Model, outputTokenMax = OUTPUT_TOKEN_MAX): number {
1483
- return Math.min(model.limit.output, outputTokenMax) || outputTokenMax
1484
- }
1485
-
1486
- type JsonRecord = Record<string, unknown>
1487
-
1488
- function isPlainObject(value: unknown): value is JsonRecord {
1489
- return typeof value === "object" && value !== null && !Array.isArray(value)
1490
- }
1491
-
1492
- // Mirrors Codex's Rust JSON schema compatibility lowering for OpenAI tool schemas.
1493
- function sanitizeOpenAISchema(value: unknown): unknown {
1494
- const types = ["string", "number", "boolean", "integer", "object", "array", "null"]
1495
- const compositionKeys = ["anyOf", "oneOf", "allOf"]
1496
-
1497
- // JSON Schema's boolean form (`true`/`false`) is unsupported by OpenAI tool schemas.
1498
- if (typeof value === "boolean") return { type: "string" }
1499
- if (Array.isArray(value)) return value.map(sanitizeOpenAISchema)
1500
- if (!isPlainObject(value)) return value
1501
-
1502
- const result: JsonRecord = {}
1503
-
1504
- if (typeof value.$ref === "string") result.$ref = value.$ref
1505
- if (typeof value.description === "string") result.description = value.description
1506
- if ("const" in value) result.enum = [value.const]
1507
- else if (Array.isArray(value.enum)) result.enum = value.enum
1508
-
1509
- if (isPlainObject(value.properties)) {
1510
- result.properties = Object.fromEntries(
1511
- Object.entries(value.properties).map(([key, item]) => [key, sanitizeOpenAISchema(item)]),
1512
- )
1513
- }
1514
-
1515
- if (Array.isArray(value.required)) {
1516
- result.required = value.required.filter((item) => typeof item === "string")
1517
- }
1518
-
1519
- if ("items" in value) result.items = sanitizeOpenAISchema(value.items)
1520
-
1521
- if ("additionalProperties" in value) {
1522
- result.additionalProperties =
1523
- typeof value.additionalProperties === "boolean"
1524
- ? value.additionalProperties
1525
- : sanitizeOpenAISchema(value.additionalProperties)
1526
- }
1527
-
1528
- for (const key of compositionKeys) {
1529
- if (Array.isArray(value[key])) result[key] = value[key].map(sanitizeOpenAISchema)
1530
- }
1531
-
1532
- for (const key of ["$defs", "definitions"]) {
1533
- if (isPlainObject(value[key])) {
1534
- result[key] = Object.fromEntries(
1535
- Object.entries(value[key]).map(([name, item]) => [name, sanitizeOpenAISchema(item)]),
1536
- )
1537
- }
1538
- }
1539
-
1540
- const schemaTypes =
1541
- typeof value.type === "string"
1542
- ? types.includes(value.type)
1543
- ? [value.type]
1544
- : []
1545
- : Array.isArray(value.type)
1546
- ? value.type.filter((item) => typeof item === "string" && types.includes(item))
1547
- : []
1548
-
1549
- if (schemaTypes.length === 0 && (typeof result.$ref === "string" || compositionKeys.some((key) => key in result))) {
1550
- return result
1551
- }
1552
-
1553
- // MCP schemas may omit `type` while still using keywords that imply one.
1554
- // Keep the schema usable after unsupported keywords are dropped.
1555
- const inferredTypes =
1556
- schemaTypes.length > 0
1557
- ? schemaTypes
1558
- : ["properties", "required", "additionalProperties"].some((key) => key in value)
1559
- ? ["object"]
1560
- : ["items", "prefixItems"].some((key) => key in value)
1561
- ? ["array"]
1562
- : "enum" in result || "format" in value
1563
- ? ["string"]
1564
- : ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"].some((key) => key in value)
1565
- ? ["number"]
1566
- : []
1567
-
1568
- if (inferredTypes.length === 0) return {}
1569
-
1570
- result.type = inferredTypes.length === 1 ? inferredTypes[0] : inferredTypes
1571
- if (inferredTypes.includes("object") && !("properties" in result)) result.properties = {}
1572
- if (inferredTypes.includes("array") && !("items" in result)) result.items = { type: "string" }
1573
- return result
1574
- }
1575
-
1576
- export function schema(model: Provider.Model, schema: JSONSchema7): JSONSchema7 {
1577
- /*
1578
- if (["openai", "azure"].includes(providerID)) {
1579
- if (schema.type === "object" && schema.properties) {
1580
- for (const [key, value] of Object.entries(schema.properties)) {
1581
- if (schema.required?.includes(key)) continue
1582
- schema.properties[key] = {
1583
- anyOf: [
1584
- value as JSONSchema.JSONSchema,
1585
- {
1586
- type: "null",
1587
- },
1588
- ],
1589
- }
1590
- }
1591
- }
1592
- }
1593
- */
1594
-
1595
- if (model.api.npm === "@ai-sdk/openai" || model.api.npm === "@ai-sdk/azure") {
1596
- schema = sanitizeOpenAISchema(schema) as JSONSchema7
1597
- // Codex also applies lossy compaction above 4 KB; defer that until OpenCode needs the same schema budget.
1598
- }
1599
-
1600
- if (model.providerID === "moonshotai" || model.api.id.toLowerCase().includes("kimi")) {
1601
- const sanitizeMoonshot = (obj: unknown): unknown => {
1602
- if (obj === null || typeof obj !== "object") return obj
1603
- if (Array.isArray(obj)) return obj.map(sanitizeMoonshot)
1604
- // Moonshot expands $ref before validation and rejects sibling keywords like description on the same node.
1605
- if ("$ref" in obj && typeof obj.$ref === "string") return { $ref: obj.$ref }
1606
- const result = Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, sanitizeMoonshot(value)]))
1607
- // MFJS does not support tuple-style `items` arrays; it requires one schema object for all array items.
1608
- if (Array.isArray(result.items)) result.items = result.items[0] ?? {}
1609
- return result
1610
- }
1611
-
1612
- const sanitized = sanitizeMoonshot(schema)
1613
- if (typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
1614
- schema = sanitized
1615
- }
1616
- }
1617
-
1618
- // Convert integer enums to string enums for Google/Gemini
1619
- if (model.providerID === "google" || model.api.id.includes("gemini")) {
1620
- const isPlainObject = (node: unknown): node is Record<string, any> =>
1621
- typeof node === "object" && node !== null && !Array.isArray(node)
1622
- const hasCombiner = (node: unknown) =>
1623
- isPlainObject(node) && (Array.isArray(node.anyOf) || Array.isArray(node.oneOf) || Array.isArray(node.allOf))
1624
- const hasSchemaIntent = (node: unknown) => {
1625
- if (!isPlainObject(node)) return false
1626
- if (hasCombiner(node)) return true
1627
- return [
1628
- "type",
1629
- "properties",
1630
- "items",
1631
- "prefixItems",
1632
- "enum",
1633
- "const",
1634
- "$ref",
1635
- "additionalProperties",
1636
- "patternProperties",
1637
- "required",
1638
- "not",
1639
- "if",
1640
- "then",
1641
- "else",
1642
- ].some((key) => key in node)
1643
- }
1644
-
1645
- const sanitizeGemini = (obj: any): any => {
1646
- if (obj === null || typeof obj !== "object") {
1647
- return obj
1648
- }
1649
-
1650
- if (Array.isArray(obj)) {
1651
- return obj.map(sanitizeGemini)
1652
- }
1653
-
1654
- const result: any = {}
1655
- for (const [key, value] of Object.entries(obj)) {
1656
- if (key === "enum" && Array.isArray(value)) {
1657
- // Convert all enum values to strings
1658
- result[key] = value.map((v) => String(v))
1659
- // If we have integer type with enum, change type to string
1660
- if (result.type === "integer" || result.type === "number") {
1661
- result.type = "string"
1662
- }
1663
- } else if (typeof value === "object" && value !== null) {
1664
- result[key] = sanitizeGemini(value)
1665
- } else {
1666
- result[key] = value
1667
- }
1668
- }
1669
-
1670
- // Gemini requires a single `type`, not a JSON Schema type array such as
1671
- // `["number","string"]` (emitted by some MCP servers). Plain `@ai-sdk/google`
1672
- // rewrites these into an `anyOf` of single-type schemas, but OpenAI-compatible
1673
- // transports (e.g. GitHub Copilot proxying to Gemini) forward them verbatim
1674
- // and the backend rejects the array form. Mirror the SDK: split non-null
1675
- // types into `anyOf`, and lift `null` into `nullable`.
1676
- if (Array.isArray(result.type)) {
1677
- const hasNull = result.type.includes("null")
1678
- const nonNull = result.type.filter((entry: unknown) => entry !== "null")
1679
- if (nonNull.length === 0) {
1680
- result.type = "null"
1681
- } else {
1682
- delete result.type
1683
- result.anyOf = nonNull.map((entry: unknown) => ({ type: entry }))
1684
- if (hasNull) result.nullable = true
1685
- }
1686
- }
1687
-
1688
- // Filter required array to only include fields that exist in properties
1689
- if (result.type === "object" && result.properties && Array.isArray(result.required)) {
1690
- result.required = result.required.filter((field: any) => field in result.properties)
1691
- }
1692
-
1693
- if (result.type === "array" && !hasCombiner(result)) {
1694
- if (result.items == null) {
1695
- result.items = {}
1696
- }
1697
- // Ensure items has a type only when it's still schema-empty.
1698
- if (isPlainObject(result.items) && !hasSchemaIntent(result.items)) {
1699
- result.items.type = "string"
1700
- }
1701
- }
1702
-
1703
- // Remove properties/required from non-object types (Gemini rejects these)
1704
- if (result.type && result.type !== "object" && !hasCombiner(result)) {
1705
- delete result.properties
1706
- delete result.required
1707
- }
1708
-
1709
- return result
1710
- }
1711
-
1712
- schema = sanitizeGemini(schema)
1713
- }
1714
-
1715
- return schema
1716
- }
1717
-
1718
- export function reasoningVariants(model: ModelsDev.Model, target: Provider.Model): Provider.Model["variants"] {
1719
- const options = model.reasoning_options
1720
- if (options === undefined) return
1721
- if (options.length === 0) return {}
1722
-
1723
- const effort = options.find((option) => option.type === "effort")
1724
- if (effort) return effortVariants(target, effort.values)
1725
-
1726
- const toggle = options.some((option) => option.type === "toggle")
1727
- const budget = options.find((option) => option.type === "budget_tokens")
1728
- if (!budget) return toggle ? nonEmptyVariants(reasoningToggle(target)) : undefined
1729
-
1730
- return nonEmptyVariants({
1731
- ...(toggle ? reasoningToggle(target) : {}),
1732
- ...budgetVariants(target, budget.min, budget.max),
1733
- })
1734
- }
1735
-
1736
- function effortVariants(model: Provider.Model, values: readonly unknown[]) {
1737
- return Object.fromEntries(
1738
- values.flatMap((value) => {
1739
- const id = (() => {
1740
- if (value === null) return "none"
1741
- if (typeof value === "string") return value
1742
- })()
1743
- if (id === undefined) return []
1744
- const settings = reasoningEffort(model, id)
1745
- return settings ? [[id, settings]] : []
1746
- }),
1747
- )
1748
- }
1749
-
1750
- function budgetVariants(model: Provider.Model, min?: number, max?: number) {
1751
- const maximum = Math.min(max ?? OUTPUT_TOKEN_MAX - 1, model.limit.output - 1, OUTPUT_TOKEN_MAX - 1)
1752
- if (maximum <= 0) return {}
1753
- const high = Math.min(Math.max(min ?? 0, Math.floor((maximum + 1) / 2)), maximum)
1754
- return Object.fromEntries(
1755
- [
1756
- { id: "high", budget: high },
1757
- { id: "max", budget: maximum },
1758
- ].flatMap((item) => {
1759
- const settings = reasoningBudget(model, item.budget)
1760
- return settings ? [[item.id, settings]] : []
1761
- }),
1762
- )
1763
- }
1764
-
1765
- function nonEmptyVariants(variants: NonNullable<Provider.Model["variants"]>): Provider.Model["variants"] {
1766
- return Object.keys(variants).length > 0 ? variants : undefined
1767
- }
1768
-
1769
- function reasoningToggle(model: Provider.Model): NonNullable<Provider.Model["variants"]> {
1770
- if (model.api.npm === "@ai-sdk/alibaba")
1771
- return {
1772
- none: { enableThinking: false },
1773
- high: { enableThinking: true },
1774
- }
1775
- if (model.api.npm === "@ai-sdk/cohere")
1776
- return {
1777
- none: { thinking: { type: "disabled" } },
1778
- high: { thinking: { type: "enabled" } },
1779
- }
1780
- return {}
1781
- }
1782
-
1783
- function reasoningEffort(model: Provider.Model, effort: string) {
1784
- switch (model.api.npm) {
1785
- case "@openrouter/ai-sdk-provider":
1786
- return { reasoning: { effort } }
1787
- case "@ai-sdk/anthropic":
1788
- case "@ai-sdk/google-vertex/anthropic":
1789
- return anthropicEffort(model, effort) ?? { effort }
1790
- case "@ai-sdk/google":
1791
- case "@ai-sdk/google-vertex":
1792
- return { thinkingConfig: { includeThoughts: true, thinkingLevel: effort } }
1793
- case "@ai-sdk/amazon-bedrock":
1794
- if (anthropicAdaptiveEfforts(model.api.id))
1795
- return {
1796
- reasoningConfig: {
1797
- type: "adaptive",
1798
- maxReasoningEffort: effort,
1799
- ...(anthropicOmitsThinking(model.api.id) ? { display: "summarized" } : {}),
1800
- },
1801
- }
1802
- if (anthropicOpus45(model.api.id))
1803
- return {
1804
- reasoningConfig: {
1805
- type: "enabled",
1806
- budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
1807
- maxReasoningEffort: effort,
1808
- },
1809
- }
1810
- if (model.api.id.includes("anthropic")) return
1811
- return { reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }
1812
- case "@ai-sdk/gateway":
1813
- if (model.id.includes("anthropic")) return { thinking: { type: "adaptive", display: "summarized" }, effort }
1814
- if (model.id.includes("google")) return { thinkingConfig: { includeThoughts: true, thinkingLevel: effort } }
1815
- return { reasoningEffort: effort }
1816
- case "@ai-sdk/github-copilot":
1817
- // OAuth discovery replaces these with variants from Copilot's /models capabilities.
1818
- if (model.id.includes("gemini")) return
1819
- if (model.id.includes("claude")) return { reasoningEffort: effort }
1820
- return { reasoningEffort: effort, reasoningSummary: "auto", include: INCLUDE_ENCRYPTED_REASONING }
1821
- case "@ai-sdk/openai":
1822
- case "@ai-sdk/amazon-bedrock/mantle":
1823
- return { reasoningEffort: effort, reasoningSummary: "auto", include: INCLUDE_ENCRYPTED_REASONING }
1824
- case "@ai-sdk/azure":
1825
- return { reasoningEffort: effort, reasoningSummary: "auto", include: INCLUDE_ENCRYPTED_REASONING }
1826
- case "@jerome-benoit/sap-ai-provider-v2":
1827
- if (model.id.includes("anthropic"))
1828
- return { modelParams: { thinking: { type: "adaptive", display: "summarized" }, output_config: { effort } } }
1829
- return { modelParams: { reasoning_effort: effort } }
1830
- case "@ai-sdk/openai-compatible":
1831
- case "@ai-sdk/xai":
1832
- case "@ai-sdk/mistral":
1833
- case "@ai-sdk/groq":
1834
- case "@ai-sdk/cerebras":
1835
- case "@ai-sdk/deepinfra":
1836
- case "@ai-sdk/togetherai":
1837
- case "venice-ai-sdk-provider":
1838
- case "ai-gateway-provider":
1839
- case "merge-gateway-ai-sdk-provider":
1840
- return { reasoningEffort: effort }
1841
- case "gitlab-ai-provider":
1842
- if (model.family?.startsWith("gpt")) return { reasoningEffort: effort }
1843
- if (model.family?.startsWith("claude")) return { thinking: { type: "adaptive", effort } }
1844
- return
1845
- case "@ai-sdk/cohere":
1846
- case "@ai-sdk/perplexity":
1847
- case "@ai-sdk/vercel":
1848
- case "@ai-sdk/alibaba":
1849
- return
1850
- }
1851
- }
1852
-
1853
- function anthropicEffort(model: Provider.Model, effort: string) {
1854
- if (anthropicOpus45(model.api.id)) return anthropicOpus45Effort(model, effort)
1855
- // Kimi defaults to omitting adaptive thinking text unless summarized display is requested.
1856
- if (isKimiFamily(model)) return { thinking: { type: "adaptive", display: "summarized" }, effort }
1857
- if (!anthropicAdaptiveEfforts(model.api.id)) return
1858
- return {
1859
- thinking: {
1860
- type: "adaptive",
1861
- ...(anthropicOmitsThinking(model.api.id) ? { display: "summarized" } : {}),
1862
- },
1863
- effort,
1864
- }
1865
- }
1866
-
1867
- function anthropicOpus45Effort(model: Provider.Model, effort: string) {
1868
- return {
1869
- thinking: {
1870
- type: "enabled",
1871
- budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
1872
- },
1873
- effort,
1874
- }
1875
- }
1876
-
1877
- function reasoningBudget(model: Provider.Model, budget: number) {
1878
- switch (model.api.npm) {
1879
- case "@openrouter/ai-sdk-provider":
1880
- return { reasoning: { max_tokens: budget } }
1881
- case "@ai-sdk/anthropic":
1882
- case "@ai-sdk/google-vertex/anthropic":
1883
- return { thinking: { type: "enabled", budgetTokens: budget } }
1884
- case "@ai-sdk/google":
1885
- case "@ai-sdk/google-vertex":
1886
- return { thinkingConfig: { includeThoughts: true, thinkingBudget: budget } }
1887
- case "@ai-sdk/amazon-bedrock":
1888
- return { reasoningConfig: { type: "enabled", budgetTokens: budget } }
1889
- case "@ai-sdk/gateway":
1890
- if (model.id.includes("anthropic")) return { thinking: { type: "enabled", budgetTokens: budget } }
1891
- if (model.id.includes("google")) return { thinkingConfig: { includeThoughts: true, thinkingBudget: budget } }
1892
- return
1893
- case "@ai-sdk/cohere":
1894
- return { thinking: { type: "enabled", tokenBudget: budget } }
1895
- case "@ai-sdk/alibaba":
1896
- return { enableThinking: true, thinkingBudget: budget }
1897
- case "@jerome-benoit/sap-ai-provider-v2":
1898
- if (model.id.includes("anthropic"))
1899
- return { modelParams: { thinking: { type: "enabled", budget_tokens: budget } } }
1900
- if (model.id.includes("gemini"))
1901
- return { modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: budget } } }
1902
- return
1903
- case "@ai-sdk/amazon-bedrock/mantle":
1904
- case "@ai-sdk/azure":
1905
- case "@ai-sdk/cerebras":
1906
- case "@ai-sdk/deepinfra":
1907
- case "@ai-sdk/github-copilot":
1908
- case "@ai-sdk/groq":
1909
- case "@ai-sdk/mistral":
1910
- case "@ai-sdk/openai":
1911
- case "@ai-sdk/openai-compatible":
1912
- case "@ai-sdk/perplexity":
1913
- case "@ai-sdk/togetherai":
1914
- case "@ai-sdk/vercel":
1915
- case "@ai-sdk/xai":
1916
- case "ai-gateway-provider":
1917
- case "gitlab-ai-provider":
1918
- case "venice-ai-sdk-provider":
1919
- return
1920
- }
1921
- }
1922
-
1923
- export * as ProviderTransform from "./transform"