innocode 1.0.0

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 (434) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/innocode +84 -0
  5. package/bin/opencode +84 -0
  6. package/bunfig.toml +5 -0
  7. package/package.json +126 -0
  8. package/parsers-config.ts +253 -0
  9. package/script/build.ts +198 -0
  10. package/script/postinstall.mjs +125 -0
  11. package/script/publish.ts +186 -0
  12. package/script/schema.ts +47 -0
  13. package/script/seed-e2e.ts +50 -0
  14. package/src/acp/README.md +164 -0
  15. package/src/acp/agent.ts +1676 -0
  16. package/src/acp/session.ts +117 -0
  17. package/src/acp/types.ts +23 -0
  18. package/src/agent/agent.ts +338 -0
  19. package/src/agent/generate.txt +75 -0
  20. package/src/agent/prompt/compaction.txt +14 -0
  21. package/src/agent/prompt/explore.txt +18 -0
  22. package/src/agent/prompt/summary.txt +11 -0
  23. package/src/agent/prompt/title.txt +44 -0
  24. package/src/auth/index.ts +70 -0
  25. package/src/bun/index.ts +137 -0
  26. package/src/bun/registry.ts +48 -0
  27. package/src/bus/bus-event.ts +43 -0
  28. package/src/bus/global.ts +10 -0
  29. package/src/bus/index.ts +105 -0
  30. package/src/cli/bootstrap.ts +17 -0
  31. package/src/cli/cmd/acp.ts +70 -0
  32. package/src/cli/cmd/agent.ts +257 -0
  33. package/src/cli/cmd/auth.ts +400 -0
  34. package/src/cli/cmd/cmd.ts +7 -0
  35. package/src/cli/cmd/debug/agent.ts +167 -0
  36. package/src/cli/cmd/debug/config.ts +16 -0
  37. package/src/cli/cmd/debug/file.ts +97 -0
  38. package/src/cli/cmd/debug/index.ts +48 -0
  39. package/src/cli/cmd/debug/lsp.ts +52 -0
  40. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  41. package/src/cli/cmd/debug/scrap.ts +16 -0
  42. package/src/cli/cmd/debug/skill.ts +16 -0
  43. package/src/cli/cmd/debug/snapshot.ts +52 -0
  44. package/src/cli/cmd/export.ts +88 -0
  45. package/src/cli/cmd/generate.ts +38 -0
  46. package/src/cli/cmd/github.ts +1540 -0
  47. package/src/cli/cmd/import.ts +147 -0
  48. package/src/cli/cmd/mcp.ts +765 -0
  49. package/src/cli/cmd/models.ts +77 -0
  50. package/src/cli/cmd/pr.ts +113 -0
  51. package/src/cli/cmd/run.ts +598 -0
  52. package/src/cli/cmd/serve.ts +20 -0
  53. package/src/cli/cmd/session.ts +135 -0
  54. package/src/cli/cmd/stats.ts +426 -0
  55. package/src/cli/cmd/tui/app.tsx +812 -0
  56. package/src/cli/cmd/tui/attach.ts +60 -0
  57. package/src/cli/cmd/tui/component/border.tsx +21 -0
  58. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  59. package/src/cli/cmd/tui/component/dialog-command.tsx +148 -0
  60. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  61. package/src/cli/cmd/tui/component/dialog-model.tsx +165 -0
  62. package/src/cli/cmd/tui/component/dialog-provider.tsx +243 -0
  63. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  64. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  65. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  66. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  67. package/src/cli/cmd/tui/component/dialog-status.tsx +167 -0
  68. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  69. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  70. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  71. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +666 -0
  72. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  73. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  74. package/src/cli/cmd/tui/component/prompt/index.tsx +1153 -0
  75. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  76. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  77. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  78. package/src/cli/cmd/tui/component/tips.tsx +153 -0
  79. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  80. package/src/cli/cmd/tui/context/args.tsx +15 -0
  81. package/src/cli/cmd/tui/context/directory.ts +13 -0
  82. package/src/cli/cmd/tui/context/exit.tsx +54 -0
  83. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  84. package/src/cli/cmd/tui/context/keybind.tsx +100 -0
  85. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  86. package/src/cli/cmd/tui/context/local.tsx +409 -0
  87. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  88. package/src/cli/cmd/tui/context/route.tsx +46 -0
  89. package/src/cli/cmd/tui/context/sdk.tsx +101 -0
  90. package/src/cli/cmd/tui/context/sync.tsx +470 -0
  91. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  92. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  93. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  95. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  96. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  97. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  98. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  99. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  100. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  101. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  102. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  103. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  104. package/src/cli/cmd/tui/context/theme/innocode.json +245 -0
  105. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  106. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  107. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  108. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  109. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  110. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  111. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  112. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  113. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  114. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  115. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  116. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  117. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  118. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  119. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  120. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  121. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  122. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  123. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  124. package/src/cli/cmd/tui/context/theme.tsx +1154 -0
  125. package/src/cli/cmd/tui/event.ts +48 -0
  126. package/src/cli/cmd/tui/routes/home.tsx +145 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  128. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  129. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  130. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  131. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  132. package/src/cli/cmd/tui/routes/session/header.tsx +135 -0
  133. package/src/cli/cmd/tui/routes/session/index.tsx +2139 -0
  134. package/src/cli/cmd/tui/routes/session/permission.tsx +508 -0
  135. package/src/cli/cmd/tui/routes/session/question.tsx +466 -0
  136. package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
  137. package/src/cli/cmd/tui/thread.ts +188 -0
  138. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  139. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +85 -0
  140. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +207 -0
  141. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  142. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +80 -0
  143. package/src/cli/cmd/tui/ui/dialog-select.tsx +401 -0
  144. package/src/cli/cmd/tui/ui/dialog.tsx +167 -0
  145. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  146. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  147. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  148. package/src/cli/cmd/tui/util/clipboard.ts +159 -0
  149. package/src/cli/cmd/tui/util/editor.ts +32 -0
  150. package/src/cli/cmd/tui/util/signal.ts +7 -0
  151. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  152. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  153. package/src/cli/cmd/tui/win32.ts +129 -0
  154. package/src/cli/cmd/tui/worker.ts +152 -0
  155. package/src/cli/cmd/uninstall.ts +363 -0
  156. package/src/cli/cmd/upgrade.ts +73 -0
  157. package/src/cli/cmd/web.ts +81 -0
  158. package/src/cli/error.ts +57 -0
  159. package/src/cli/logo.ts +6 -0
  160. package/src/cli/network.ts +60 -0
  161. package/src/cli/ui.ts +113 -0
  162. package/src/cli/upgrade.ts +25 -0
  163. package/src/command/index.ts +150 -0
  164. package/src/command/template/initialize.txt +10 -0
  165. package/src/command/template/review.txt +101 -0
  166. package/src/config/config.ts +1517 -0
  167. package/src/config/markdown.ts +98 -0
  168. package/src/env/index.ts +28 -0
  169. package/src/file/ignore.ts +83 -0
  170. package/src/file/index.ts +583 -0
  171. package/src/file/ripgrep.ts +375 -0
  172. package/src/file/time.ts +69 -0
  173. package/src/file/watcher.ts +127 -0
  174. package/src/flag/flag.ts +148 -0
  175. package/src/format/formatter.ts +366 -0
  176. package/src/format/index.ts +137 -0
  177. package/src/global/index.ts +80 -0
  178. package/src/id/id.ts +83 -0
  179. package/src/ide/index.ts +76 -0
  180. package/src/index.ts +160 -0
  181. package/src/installation/index.ts +268 -0
  182. package/src/lsp/client.ts +252 -0
  183. package/src/lsp/index.ts +485 -0
  184. package/src/lsp/language.ts +119 -0
  185. package/src/lsp/server.ts +2046 -0
  186. package/src/mcp/auth.ts +132 -0
  187. package/src/mcp/index.ts +937 -0
  188. package/src/mcp/oauth-callback.ts +200 -0
  189. package/src/mcp/oauth-provider.ts +154 -0
  190. package/src/patch/index.ts +680 -0
  191. package/src/permission/arity.ts +163 -0
  192. package/src/permission/index.ts +210 -0
  193. package/src/permission/next.ts +280 -0
  194. package/src/plugin/codex.ts +624 -0
  195. package/src/plugin/copilot.ts +327 -0
  196. package/src/plugin/index.ts +138 -0
  197. package/src/project/bootstrap.ts +35 -0
  198. package/src/project/instance.ts +114 -0
  199. package/src/project/project.ts +371 -0
  200. package/src/project/state.ts +70 -0
  201. package/src/project/vcs.ts +76 -0
  202. package/src/provider/auth.ts +147 -0
  203. package/src/provider/error.ts +189 -0
  204. package/src/provider/models.ts +133 -0
  205. package/src/provider/provider.ts +1370 -0
  206. package/src/provider/sdk/copilot/README.md +5 -0
  207. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +164 -0
  208. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  209. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +17 -0
  210. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  211. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +780 -0
  212. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  213. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  214. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +87 -0
  215. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  216. package/src/provider/sdk/copilot/index.ts +2 -0
  217. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  218. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +303 -0
  219. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  220. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  221. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  222. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +207 -0
  223. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1732 -0
  224. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +177 -0
  225. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  226. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +88 -0
  227. package/src/provider/sdk/copilot/responses/tool/file-search.ts +128 -0
  228. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +115 -0
  229. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +65 -0
  230. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +104 -0
  231. package/src/provider/sdk/copilot/responses/tool/web-search.ts +103 -0
  232. package/src/provider/transform.ts +806 -0
  233. package/src/pty/index.ts +286 -0
  234. package/src/question/index.ts +171 -0
  235. package/src/scheduler/index.ts +61 -0
  236. package/src/server/error.ts +36 -0
  237. package/src/server/event.ts +7 -0
  238. package/src/server/mdns.ts +60 -0
  239. package/src/server/routes/config.ts +92 -0
  240. package/src/server/routes/experimental.ts +208 -0
  241. package/src/server/routes/file.ts +197 -0
  242. package/src/server/routes/global.ts +183 -0
  243. package/src/server/routes/mcp.ts +225 -0
  244. package/src/server/routes/permission.ts +68 -0
  245. package/src/server/routes/project.ts +82 -0
  246. package/src/server/routes/provider.ts +179 -0
  247. package/src/server/routes/pty.ts +176 -0
  248. package/src/server/routes/question.ts +98 -0
  249. package/src/server/routes/session.ts +939 -0
  250. package/src/server/routes/tui.ts +379 -0
  251. package/src/server/server.ts +621 -0
  252. package/src/session/compaction.ts +261 -0
  253. package/src/session/index.ts +543 -0
  254. package/src/session/instruction.ts +197 -0
  255. package/src/session/llm.ts +283 -0
  256. package/src/session/message-v2.ts +841 -0
  257. package/src/session/message.ts +189 -0
  258. package/src/session/processor.ts +410 -0
  259. package/src/session/prompt/anthropic-20250930.txt +166 -0
  260. package/src/session/prompt/anthropic.txt +105 -0
  261. package/src/session/prompt/beast.txt +147 -0
  262. package/src/session/prompt/build-switch.txt +5 -0
  263. package/src/session/prompt/codex_header.txt +79 -0
  264. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  265. package/src/session/prompt/gemini.txt +155 -0
  266. package/src/session/prompt/max-steps.txt +16 -0
  267. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  268. package/src/session/prompt/plan.txt +26 -0
  269. package/src/session/prompt/qwen.txt +109 -0
  270. package/src/session/prompt/trinity.txt +97 -0
  271. package/src/session/prompt.ts +1964 -0
  272. package/src/session/retry.ts +101 -0
  273. package/src/session/revert.ts +121 -0
  274. package/src/session/status.ts +76 -0
  275. package/src/session/summary.ts +203 -0
  276. package/src/session/system.ts +54 -0
  277. package/src/session/todo.ts +37 -0
  278. package/src/share/share-next.ts +200 -0
  279. package/src/share/share.ts +92 -0
  280. package/src/shell/shell.ts +67 -0
  281. package/src/skill/discovery.ts +97 -0
  282. package/src/skill/index.ts +1 -0
  283. package/src/skill/skill.ts +188 -0
  284. package/src/snapshot/index.ts +255 -0
  285. package/src/storage/storage.ts +227 -0
  286. package/src/tool/apply_patch.ts +281 -0
  287. package/src/tool/apply_patch.txt +33 -0
  288. package/src/tool/bash.ts +269 -0
  289. package/src/tool/bash.txt +115 -0
  290. package/src/tool/batch.ts +175 -0
  291. package/src/tool/batch.txt +24 -0
  292. package/src/tool/codesearch.ts +132 -0
  293. package/src/tool/codesearch.txt +12 -0
  294. package/src/tool/edit.ts +655 -0
  295. package/src/tool/edit.txt +10 -0
  296. package/src/tool/external-directory.ts +32 -0
  297. package/src/tool/glob.ts +80 -0
  298. package/src/tool/glob.txt +6 -0
  299. package/src/tool/grep.ts +150 -0
  300. package/src/tool/grep.txt +8 -0
  301. package/src/tool/invalid.ts +17 -0
  302. package/src/tool/ls.ts +121 -0
  303. package/src/tool/ls.txt +1 -0
  304. package/src/tool/lsp.ts +96 -0
  305. package/src/tool/lsp.txt +19 -0
  306. package/src/tool/multiedit.ts +46 -0
  307. package/src/tool/multiedit.txt +41 -0
  308. package/src/tool/plan-enter.txt +14 -0
  309. package/src/tool/plan-exit.txt +13 -0
  310. package/src/tool/plan.ts +130 -0
  311. package/src/tool/question.ts +33 -0
  312. package/src/tool/question.txt +10 -0
  313. package/src/tool/read.ts +261 -0
  314. package/src/tool/read.txt +14 -0
  315. package/src/tool/registry.ts +160 -0
  316. package/src/tool/skill.ts +123 -0
  317. package/src/tool/task.ts +165 -0
  318. package/src/tool/task.txt +60 -0
  319. package/src/tool/todo.ts +53 -0
  320. package/src/tool/todoread.txt +14 -0
  321. package/src/tool/todowrite.txt +167 -0
  322. package/src/tool/tool.ts +89 -0
  323. package/src/tool/truncation.ts +106 -0
  324. package/src/tool/webfetch.ts +186 -0
  325. package/src/tool/webfetch.txt +13 -0
  326. package/src/tool/websearch.ts +150 -0
  327. package/src/tool/websearch.txt +14 -0
  328. package/src/tool/write.ts +85 -0
  329. package/src/tool/write.txt +8 -0
  330. package/src/util/abort.ts +35 -0
  331. package/src/util/archive.ts +16 -0
  332. package/src/util/color.ts +19 -0
  333. package/src/util/context.ts +25 -0
  334. package/src/util/defer.ts +12 -0
  335. package/src/util/eventloop.ts +20 -0
  336. package/src/util/filesystem.ts +93 -0
  337. package/src/util/fn.ts +11 -0
  338. package/src/util/format.ts +20 -0
  339. package/src/util/iife.ts +3 -0
  340. package/src/util/keybind.ts +103 -0
  341. package/src/util/lazy.ts +18 -0
  342. package/src/util/locale.ts +81 -0
  343. package/src/util/lock.ts +98 -0
  344. package/src/util/log.ts +180 -0
  345. package/src/util/proxied.ts +3 -0
  346. package/src/util/queue.ts +32 -0
  347. package/src/util/rpc.ts +66 -0
  348. package/src/util/scrap.ts +10 -0
  349. package/src/util/signal.ts +12 -0
  350. package/src/util/timeout.ts +14 -0
  351. package/src/util/token.ts +7 -0
  352. package/src/util/wildcard.ts +56 -0
  353. package/src/worktree/index.ts +612 -0
  354. package/sst-env.d.ts +9 -0
  355. package/test/acp/agent-interface.test.ts +51 -0
  356. package/test/acp/event-subscription.test.ts +436 -0
  357. package/test/agent/agent.test.ts +675 -0
  358. package/test/bun.test.ts +53 -0
  359. package/test/cli/github-action.test.ts +161 -0
  360. package/test/cli/github-remote.test.ts +80 -0
  361. package/test/cli/import.test.ts +38 -0
  362. package/test/cli/tui/transcript.test.ts +322 -0
  363. package/test/config/agent-color.test.ts +71 -0
  364. package/test/config/config.test.ts +1802 -0
  365. package/test/config/fixtures/empty-frontmatter.md +4 -0
  366. package/test/config/fixtures/frontmatter.md +28 -0
  367. package/test/config/fixtures/markdown-header.md +11 -0
  368. package/test/config/fixtures/no-frontmatter.md +1 -0
  369. package/test/config/fixtures/weird-model-id.md +13 -0
  370. package/test/config/markdown.test.ts +228 -0
  371. package/test/file/ignore.test.ts +10 -0
  372. package/test/file/path-traversal.test.ts +198 -0
  373. package/test/file/ripgrep.test.ts +39 -0
  374. package/test/fixture/fixture.ts +45 -0
  375. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  376. package/test/ide/ide.test.ts +82 -0
  377. package/test/keybind.test.ts +421 -0
  378. package/test/lsp/client.test.ts +95 -0
  379. package/test/mcp/headers.test.ts +153 -0
  380. package/test/mcp/oauth-browser.test.ts +249 -0
  381. package/test/memory/abort-leak.test.ts +136 -0
  382. package/test/patch/patch.test.ts +348 -0
  383. package/test/permission/arity.test.ts +33 -0
  384. package/test/permission/next.test.ts +690 -0
  385. package/test/permission-task.test.ts +319 -0
  386. package/test/plugin/auth-override.test.ts +44 -0
  387. package/test/plugin/codex.test.ts +123 -0
  388. package/test/preload.ts +63 -0
  389. package/test/project/project.test.ts +120 -0
  390. package/test/provider/amazon-bedrock.test.ts +445 -0
  391. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  392. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  393. package/test/provider/gitlab-duo.test.ts +262 -0
  394. package/test/provider/provider.test.ts +2129 -0
  395. package/test/provider/transform.test.ts +1928 -0
  396. package/test/question/question.test.ts +300 -0
  397. package/test/scheduler.test.ts +73 -0
  398. package/test/server/session-list.test.ts +39 -0
  399. package/test/server/session-select.test.ts +78 -0
  400. package/test/session/compaction.test.ts +423 -0
  401. package/test/session/instruction.test.ts +170 -0
  402. package/test/session/llm.test.ts +667 -0
  403. package/test/session/message-v2.test.ts +924 -0
  404. package/test/session/prompt-missing-file.test.ts +53 -0
  405. package/test/session/prompt-special-chars.test.ts +56 -0
  406. package/test/session/prompt-variant.test.ts +68 -0
  407. package/test/session/retry.test.ts +188 -0
  408. package/test/session/revert-compact.test.ts +285 -0
  409. package/test/session/session.test.ts +71 -0
  410. package/test/session/structured-output-integration.test.ts +233 -0
  411. package/test/session/structured-output.test.ts +385 -0
  412. package/test/skill/discovery.test.ts +60 -0
  413. package/test/skill/skill.test.ts +388 -0
  414. package/test/snapshot/snapshot.test.ts +1040 -0
  415. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  416. package/test/tool/apply_patch.test.ts +559 -0
  417. package/test/tool/bash.test.ts +399 -0
  418. package/test/tool/external-directory.test.ts +127 -0
  419. package/test/tool/fixtures/large-image.png +0 -0
  420. package/test/tool/fixtures/models-api.json +38413 -0
  421. package/test/tool/grep.test.ts +110 -0
  422. package/test/tool/question.test.ts +107 -0
  423. package/test/tool/read.test.ts +421 -0
  424. package/test/tool/registry.test.ts +122 -0
  425. package/test/tool/skill.test.ts +112 -0
  426. package/test/tool/truncation.test.ts +159 -0
  427. package/test/util/filesystem.test.ts +39 -0
  428. package/test/util/format.test.ts +59 -0
  429. package/test/util/iife.test.ts +36 -0
  430. package/test/util/lazy.test.ts +50 -0
  431. package/test/util/lock.test.ts +72 -0
  432. package/test/util/timeout.test.ts +21 -0
  433. package/test/util/wildcard.test.ts +75 -0
  434. package/tsconfig.json +16 -0
@@ -0,0 +1,806 @@
1
+ import type { ModelMessage } from "ai"
2
+ import { mergeDeep, unique } from "remeda"
3
+ import type { JSONSchema7 } from "@ai-sdk/provider"
4
+ import type { JSONSchema } from "zod/v4/core"
5
+ import type { Provider } from "./provider"
6
+ import type { ModelsDev } from "./models"
7
+ import { iife } from "@/util/iife"
8
+ import { Flag } from "@/flag/flag"
9
+
10
+ type Modality = NonNullable<ModelsDev.Model["modalities"]>["input"][number]
11
+
12
+ function mimeToModality(mime: string): Modality | undefined {
13
+ if (mime.startsWith("image/")) return "image"
14
+ if (mime.startsWith("audio/")) return "audio"
15
+ if (mime.startsWith("video/")) return "video"
16
+ if (mime === "application/pdf") return "pdf"
17
+ return undefined
18
+ }
19
+
20
+ export namespace ProviderTransform {
21
+ export const OUTPUT_TOKEN_MAX = Flag.OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000
22
+
23
+ // Maps npm package to the key the AI SDK expects for providerOptions
24
+ function sdkKey(npm: string): string | undefined {
25
+ switch (npm) {
26
+ case "@ai-sdk/github-copilot":
27
+ return "copilot"
28
+ case "@ai-sdk/openai":
29
+ case "@ai-sdk/azure":
30
+ return "openai"
31
+ case "@ai-sdk/amazon-bedrock":
32
+ return "bedrock"
33
+ case "@ai-sdk/anthropic":
34
+ case "@ai-sdk/google-vertex/anthropic":
35
+ return "anthropic"
36
+ case "@ai-sdk/google-vertex":
37
+ case "@ai-sdk/google":
38
+ return "google"
39
+ case "@ai-sdk/gateway":
40
+ return "gateway"
41
+ case "@openrouter/ai-sdk-provider":
42
+ return "openrouter"
43
+ }
44
+ return undefined
45
+ }
46
+
47
+ function normalizeMessages(
48
+ msgs: ModelMessage[],
49
+ model: Provider.Model,
50
+ options: Record<string, unknown>,
51
+ ): ModelMessage[] {
52
+ // Anthropic rejects messages with empty content - filter out empty string messages
53
+ // and remove empty text/reasoning parts from array content
54
+ if (model.api.npm === "@ai-sdk/anthropic") {
55
+ msgs = msgs
56
+ .map((msg) => {
57
+ if (typeof msg.content === "string") {
58
+ if (msg.content === "") return undefined
59
+ return msg
60
+ }
61
+ if (!Array.isArray(msg.content)) return msg
62
+ const filtered = msg.content.filter((part) => {
63
+ if (part.type === "text" || part.type === "reasoning") {
64
+ return part.text !== ""
65
+ }
66
+ return true
67
+ })
68
+ if (filtered.length === 0) return undefined
69
+ return { ...msg, content: filtered }
70
+ })
71
+ .filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "")
72
+ }
73
+
74
+ if (model.api.id.includes("claude")) {
75
+ return msgs.map((msg) => {
76
+ if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
77
+ msg.content = msg.content.map((part) => {
78
+ if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
79
+ return {
80
+ ...part,
81
+ toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"),
82
+ }
83
+ }
84
+ return part
85
+ })
86
+ }
87
+ return msg
88
+ })
89
+ }
90
+ if (
91
+ model.providerID === "mistral" ||
92
+ model.api.id.toLowerCase().includes("mistral") ||
93
+ model.api.id.toLocaleLowerCase().includes("devstral")
94
+ ) {
95
+ const result: ModelMessage[] = []
96
+ for (let i = 0; i < msgs.length; i++) {
97
+ const msg = msgs[i]
98
+ const nextMsg = msgs[i + 1]
99
+
100
+ if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
101
+ msg.content = msg.content.map((part) => {
102
+ if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
103
+ // Mistral requires alphanumeric tool call IDs with exactly 9 characters
104
+ const normalizedId = part.toolCallId
105
+ .replace(/[^a-zA-Z0-9]/g, "") // Remove non-alphanumeric characters
106
+ .substring(0, 9) // Take first 9 characters
107
+ .padEnd(9, "0") // Pad with zeros if less than 9 characters
108
+
109
+ return {
110
+ ...part,
111
+ toolCallId: normalizedId,
112
+ }
113
+ }
114
+ return part
115
+ })
116
+ }
117
+
118
+ result.push(msg)
119
+
120
+ // Fix message sequence: tool messages cannot be followed by user messages
121
+ if (msg.role === "tool" && nextMsg?.role === "user") {
122
+ result.push({
123
+ role: "assistant",
124
+ content: [
125
+ {
126
+ type: "text",
127
+ text: "Done.",
128
+ },
129
+ ],
130
+ })
131
+ }
132
+ }
133
+ return result
134
+ }
135
+
136
+ if (typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field) {
137
+ const field = model.capabilities.interleaved.field
138
+ return msgs.map((msg) => {
139
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
140
+ const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning")
141
+ const reasoningText = reasoningParts.map((part: any) => part.text).join("")
142
+
143
+ // Filter out reasoning parts from content
144
+ const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning")
145
+
146
+ // Include reasoning_content | reasoning_details directly on the message for all assistant messages
147
+ if (reasoningText) {
148
+ return {
149
+ ...msg,
150
+ content: filteredContent,
151
+ providerOptions: {
152
+ ...msg.providerOptions,
153
+ openaiCompatible: {
154
+ ...(msg.providerOptions as any)?.openaiCompatible,
155
+ [field]: reasoningText,
156
+ },
157
+ },
158
+ }
159
+ }
160
+
161
+ return {
162
+ ...msg,
163
+ content: filteredContent,
164
+ }
165
+ }
166
+
167
+ return msg
168
+ })
169
+ }
170
+
171
+ return msgs
172
+ }
173
+
174
+ function applyCaching(msgs: ModelMessage[], providerID: string): ModelMessage[] {
175
+ const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
176
+ const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
177
+
178
+ const providerOptions = {
179
+ anthropic: {
180
+ cacheControl: { type: "ephemeral" },
181
+ },
182
+ openrouter: {
183
+ cacheControl: { type: "ephemeral" },
184
+ },
185
+ bedrock: {
186
+ cachePoint: { type: "default" },
187
+ },
188
+ openaiCompatible: {
189
+ cache_control: { type: "ephemeral" },
190
+ },
191
+ copilot: {
192
+ copilot_cache_control: { type: "ephemeral" },
193
+ },
194
+ }
195
+
196
+ for (const msg of unique([...system, ...final])) {
197
+ const useMessageLevelOptions = providerID === "anthropic" || providerID.includes("bedrock")
198
+ const shouldUseContentOptions = !useMessageLevelOptions && Array.isArray(msg.content) && msg.content.length > 0
199
+
200
+ if (shouldUseContentOptions) {
201
+ const lastContent = msg.content[msg.content.length - 1]
202
+ if (lastContent && typeof lastContent === "object") {
203
+ lastContent.providerOptions = mergeDeep(lastContent.providerOptions ?? {}, providerOptions)
204
+ continue
205
+ }
206
+ }
207
+
208
+ msg.providerOptions = mergeDeep(msg.providerOptions ?? {}, providerOptions)
209
+ }
210
+
211
+ return msgs
212
+ }
213
+
214
+ function unsupportedParts(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] {
215
+ return msgs.map((msg) => {
216
+ if (msg.role !== "user" || !Array.isArray(msg.content)) return msg
217
+
218
+ const filtered = msg.content.map((part) => {
219
+ if (part.type !== "file" && part.type !== "image") return part
220
+
221
+ // Check for empty base64 image data
222
+ if (part.type === "image") {
223
+ const imageStr = part.image.toString()
224
+ if (imageStr.startsWith("data:")) {
225
+ const match = imageStr.match(/^data:([^;]+);base64,(.*)$/)
226
+ if (match && (!match[2] || match[2].length === 0)) {
227
+ return {
228
+ type: "text" as const,
229
+ text: "ERROR: Image file is empty or corrupted. Please provide a valid image.",
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ const mime = part.type === "image" ? part.image.toString().split(";")[0].replace("data:", "") : part.mediaType
236
+ const filename = part.type === "file" ? part.filename : undefined
237
+ const modality = mimeToModality(mime)
238
+ if (!modality) return part
239
+ if (model.capabilities.input[modality]) return part
240
+
241
+ const name = filename ? `"${filename}"` : modality
242
+ return {
243
+ type: "text" as const,
244
+ text: `ERROR: Cannot read ${name} (this model does not support ${modality} input). Inform the user.`,
245
+ }
246
+ })
247
+
248
+ return { ...msg, content: filtered }
249
+ })
250
+ }
251
+
252
+ export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
253
+ msgs = unsupportedParts(msgs, model)
254
+ msgs = normalizeMessages(msgs, model, options)
255
+ if (
256
+ model.providerID === "anthropic" ||
257
+ model.api.id.includes("anthropic") ||
258
+ model.api.id.includes("claude") ||
259
+ model.id.includes("anthropic") ||
260
+ model.id.includes("claude") ||
261
+ model.api.npm === "@ai-sdk/anthropic"
262
+ ) {
263
+ msgs = applyCaching(msgs, model.providerID)
264
+ }
265
+
266
+ // Remap providerOptions keys from stored providerID to expected SDK key
267
+ const key = sdkKey(model.api.npm)
268
+ if (key && key !== model.providerID && model.api.npm !== "@ai-sdk/azure") {
269
+ const remap = (opts: Record<string, any> | undefined) => {
270
+ if (!opts) return opts
271
+ if (!(model.providerID in opts)) return opts
272
+ const result = { ...opts }
273
+ result[key] = result[model.providerID]
274
+ delete result[model.providerID]
275
+ return result
276
+ }
277
+
278
+ msgs = msgs.map((msg) => {
279
+ if (!Array.isArray(msg.content)) return { ...msg, providerOptions: remap(msg.providerOptions) }
280
+ return {
281
+ ...msg,
282
+ providerOptions: remap(msg.providerOptions),
283
+ content: msg.content.map((part) => ({ ...part, providerOptions: remap(part.providerOptions) })),
284
+ } as typeof msg
285
+ })
286
+ }
287
+
288
+ return msgs
289
+ }
290
+
291
+ export function temperature(model: Provider.Model) {
292
+ const id = model.id.toLowerCase()
293
+ if (id.includes("qwen")) return 0.55
294
+ if (id.includes("claude")) return undefined
295
+ if (id.includes("gemini")) return 1.0
296
+ if (id.includes("glm-4.6")) return 1.0
297
+ if (id.includes("glm-4.7")) return 1.0
298
+ if (id.includes("minimax-m2")) return 1.0
299
+ if (id.includes("kimi-k2")) {
300
+ // kimi-k2-thinking & kimi-k2.5 && kimi-k2p5
301
+ if (id.includes("thinking") || id.includes("k2.") || id.includes("k2p")) {
302
+ return 1.0
303
+ }
304
+ return 0.6
305
+ }
306
+ return undefined
307
+ }
308
+
309
+ export function topP(model: Provider.Model) {
310
+ const id = model.id.toLowerCase()
311
+ if (id.includes("qwen")) return 1
312
+ if (id.includes("minimax-m2") || id.includes("kimi-k2.5") || id.includes("kimi-k2p5") || id.includes("gemini")) {
313
+ return 0.95
314
+ }
315
+ return undefined
316
+ }
317
+
318
+ export function topK(model: Provider.Model) {
319
+ const id = model.id.toLowerCase()
320
+ if (id.includes("minimax-m2")) {
321
+ if (id.includes("m2.1")) return 40
322
+ return 20
323
+ }
324
+ if (id.includes("gemini")) return 64
325
+ return undefined
326
+ }
327
+
328
+ const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
329
+ const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
330
+
331
+ export function variants(model: Provider.Model): Record<string, Record<string, any>> {
332
+ if (!model.capabilities.reasoning) return {}
333
+
334
+ const id = model.id.toLowerCase()
335
+ if (
336
+ id.includes("deepseek") ||
337
+ id.includes("minimax") ||
338
+ id.includes("glm") ||
339
+ id.includes("mistral") ||
340
+ id.includes("kimi") ||
341
+ // TODO: Remove this after models.dev data is fixed to use "kimi-k2.5" instead of "k2p5"
342
+ id.includes("k2p5")
343
+ )
344
+ return {}
345
+
346
+ // see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks
347
+ if (id.includes("grok") && id.includes("grok-3-mini")) {
348
+ if (model.api.npm === "@openrouter/ai-sdk-provider") {
349
+ return {
350
+ low: { reasoning: { effort: "low" } },
351
+ high: { reasoning: { effort: "high" } },
352
+ }
353
+ }
354
+ return {
355
+ low: { reasoningEffort: "low" },
356
+ high: { reasoningEffort: "high" },
357
+ }
358
+ }
359
+ if (id.includes("grok")) return {}
360
+
361
+ switch (model.api.npm) {
362
+ case "@openrouter/ai-sdk-provider":
363
+ if (!model.id.includes("gpt") && !model.id.includes("gemini-3")) return {}
364
+ return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoning: { effort } }]))
365
+
366
+ // TODO: YOU CANNOT SET max_tokens if this is set!!!
367
+ case "@ai-sdk/gateway":
368
+ return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
369
+
370
+ case "@ai-sdk/github-copilot":
371
+ if (model.id.includes("gemini")) {
372
+ // currently github copilot only returns thinking
373
+ return {}
374
+ }
375
+ if (model.id.includes("claude")) {
376
+ return {
377
+ thinking: { thinking_budget: 4000 },
378
+ }
379
+ }
380
+ const copilotEfforts = iife(() => {
381
+ if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3"))
382
+ return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
383
+ return WIDELY_SUPPORTED_EFFORTS
384
+ })
385
+ return Object.fromEntries(
386
+ copilotEfforts.map((effort) => [
387
+ effort,
388
+ {
389
+ reasoningEffort: effort,
390
+ reasoningSummary: "auto",
391
+ include: ["reasoning.encrypted_content"],
392
+ },
393
+ ]),
394
+ )
395
+
396
+ case "@ai-sdk/cerebras":
397
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/cerebras
398
+ case "@ai-sdk/togetherai":
399
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/togetherai
400
+ case "@ai-sdk/xai":
401
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/xai
402
+ case "@ai-sdk/deepinfra":
403
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/deepinfra
404
+ case "venice-ai-sdk-provider":
405
+ // https://docs.venice.ai/overview/guides/reasoning-models#reasoning-effort
406
+ case "@ai-sdk/openai-compatible":
407
+ return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
408
+
409
+ case "@ai-sdk/azure":
410
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
411
+ if (id === "o1-mini") return {}
412
+ const azureEfforts = ["low", "medium", "high"]
413
+ if (id.includes("gpt-5-") || id === "gpt-5") {
414
+ azureEfforts.unshift("minimal")
415
+ }
416
+ return Object.fromEntries(
417
+ azureEfforts.map((effort) => [
418
+ effort,
419
+ {
420
+ reasoningEffort: effort,
421
+ reasoningSummary: "auto",
422
+ include: ["reasoning.encrypted_content"],
423
+ },
424
+ ]),
425
+ )
426
+ case "@ai-sdk/openai":
427
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
428
+ if (id === "gpt-5-pro") return {}
429
+ const openaiEfforts = iife(() => {
430
+ if (id.includes("codex")) {
431
+ if (id.includes("5.2") || id.includes("5.3")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
432
+ return WIDELY_SUPPORTED_EFFORTS
433
+ }
434
+ const arr = [...WIDELY_SUPPORTED_EFFORTS]
435
+ if (id.includes("gpt-5-") || id === "gpt-5") {
436
+ arr.unshift("minimal")
437
+ }
438
+ if (model.release_date >= "2025-11-13") {
439
+ arr.unshift("none")
440
+ }
441
+ if (model.release_date >= "2025-12-04") {
442
+ arr.push("xhigh")
443
+ }
444
+ return arr
445
+ })
446
+ return Object.fromEntries(
447
+ openaiEfforts.map((effort) => [
448
+ effort,
449
+ {
450
+ reasoningEffort: effort,
451
+ reasoningSummary: "auto",
452
+ include: ["reasoning.encrypted_content"],
453
+ },
454
+ ]),
455
+ )
456
+
457
+ case "@ai-sdk/anthropic":
458
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
459
+ case "@ai-sdk/google-vertex/anthropic":
460
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
461
+ return {
462
+ high: {
463
+ thinking: {
464
+ type: "enabled",
465
+ budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
466
+ },
467
+ },
468
+ max: {
469
+ thinking: {
470
+ type: "enabled",
471
+ budgetTokens: Math.min(31_999, model.limit.output - 1),
472
+ },
473
+ },
474
+ }
475
+
476
+ case "@ai-sdk/amazon-bedrock":
477
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock
478
+ // For Anthropic models on Bedrock, use reasoningConfig with budgetTokens
479
+ if (model.api.id.includes("anthropic")) {
480
+ return {
481
+ high: {
482
+ reasoningConfig: {
483
+ type: "enabled",
484
+ budgetTokens: 16000,
485
+ },
486
+ },
487
+ max: {
488
+ reasoningConfig: {
489
+ type: "enabled",
490
+ budgetTokens: 31999,
491
+ },
492
+ },
493
+ }
494
+ }
495
+
496
+ // For Amazon Nova models, use reasoningConfig with maxReasoningEffort
497
+ return Object.fromEntries(
498
+ WIDELY_SUPPORTED_EFFORTS.map((effort) => [
499
+ effort,
500
+ {
501
+ reasoningConfig: {
502
+ type: "enabled",
503
+ maxReasoningEffort: effort,
504
+ },
505
+ },
506
+ ]),
507
+ )
508
+
509
+ case "@ai-sdk/google-vertex":
510
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex
511
+ case "@ai-sdk/google":
512
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai
513
+ if (id.includes("2.5")) {
514
+ return {
515
+ high: {
516
+ thinkingConfig: {
517
+ includeThoughts: true,
518
+ thinkingBudget: 16000,
519
+ },
520
+ },
521
+ max: {
522
+ thinkingConfig: {
523
+ includeThoughts: true,
524
+ thinkingBudget: 24576,
525
+ },
526
+ },
527
+ }
528
+ }
529
+ return Object.fromEntries(
530
+ ["low", "high"].map((effort) => [
531
+ effort,
532
+ {
533
+ includeThoughts: true,
534
+ thinkingLevel: effort,
535
+ },
536
+ ]),
537
+ )
538
+
539
+ case "@ai-sdk/mistral":
540
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/mistral
541
+ return {}
542
+
543
+ case "@ai-sdk/cohere":
544
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/cohere
545
+ return {}
546
+
547
+ case "@ai-sdk/groq":
548
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/groq
549
+ const groqEffort = ["none", ...WIDELY_SUPPORTED_EFFORTS]
550
+ return Object.fromEntries(
551
+ groqEffort.map((effort) => [
552
+ effort,
553
+ {
554
+ includeThoughts: true,
555
+ thinkingLevel: effort,
556
+ },
557
+ ]),
558
+ )
559
+
560
+ case "@ai-sdk/perplexity":
561
+ // https://v5.ai-sdk.dev/providers/ai-sdk-providers/perplexity
562
+ return {}
563
+
564
+ case "@mymediset/sap-ai-provider":
565
+ case "@jerome-benoit/sap-ai-provider-v2":
566
+ if (model.api.id.includes("anthropic")) {
567
+ return {
568
+ high: {
569
+ thinking: {
570
+ type: "enabled",
571
+ budgetTokens: 16000,
572
+ },
573
+ },
574
+ max: {
575
+ thinking: {
576
+ type: "enabled",
577
+ budgetTokens: 31999,
578
+ },
579
+ },
580
+ }
581
+ }
582
+ return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
583
+ }
584
+ return {}
585
+ }
586
+
587
+ export function options(input: {
588
+ model: Provider.Model
589
+ sessionID: string
590
+ providerOptions?: Record<string, any>
591
+ }): Record<string, any> {
592
+ const result: Record<string, any> = {}
593
+
594
+ // openai and providers using openai package should set store to false by default.
595
+ if (
596
+ input.model.providerID === "openai" ||
597
+ input.model.api.npm === "@ai-sdk/openai" ||
598
+ input.model.api.npm === "@ai-sdk/github-copilot"
599
+ ) {
600
+ result["store"] = false
601
+ }
602
+
603
+ if (input.model.api.npm === "@openrouter/ai-sdk-provider") {
604
+ result["usage"] = {
605
+ include: true,
606
+ }
607
+ if (input.model.api.id.includes("gemini-3")) {
608
+ result["reasoning"] = { effort: "high" }
609
+ }
610
+ }
611
+
612
+ if (input.model.providerID === "baseten") {
613
+ result["chat_template_args"] = { enable_thinking: true }
614
+ }
615
+
616
+ if (["zai", "zhipuai"].includes(input.model.providerID) && input.model.api.npm === "@ai-sdk/openai-compatible") {
617
+ result["thinking"] = {
618
+ type: "enabled",
619
+ clear_thinking: false,
620
+ }
621
+ }
622
+
623
+ if (input.model.providerID === "openai" || input.providerOptions?.setCacheKey) {
624
+ result["promptCacheKey"] = input.sessionID
625
+ }
626
+
627
+ if (input.model.api.npm === "@ai-sdk/google" || input.model.api.npm === "@ai-sdk/google-vertex") {
628
+ result["thinkingConfig"] = {
629
+ includeThoughts: true,
630
+ }
631
+ if (input.model.api.id.includes("gemini-3")) {
632
+ result["thinkingConfig"]["thinkingLevel"] = "high"
633
+ }
634
+ }
635
+
636
+ // Enable thinking by default for kimi-k2.5/k2p5 models using anthropic SDK
637
+ const modelId = input.model.api.id.toLowerCase()
638
+ if (
639
+ (input.model.api.npm === "@ai-sdk/anthropic" || input.model.api.npm === "@ai-sdk/google-vertex/anthropic") &&
640
+ (modelId.includes("k2p5") || modelId.includes("kimi-k2.5") || modelId.includes("kimi-k2p5"))
641
+ ) {
642
+ result["thinking"] = {
643
+ type: "enabled",
644
+ budgetTokens: Math.min(16_000, Math.floor(input.model.limit.output / 2 - 1)),
645
+ }
646
+ }
647
+
648
+ // Enable thinking for reasoning models on alibaba-cn (DashScope).
649
+ // DashScope's OpenAI-compatible API requires `enable_thinking: true` in the request body
650
+ // to return reasoning_content. Without it, models like kimi-k2.5, qwen-plus, qwen3, qwq,
651
+ // deepseek-r1, etc. never output thinking/reasoning tokens.
652
+ // Note: kimi-k2-thinking is excluded as it returns reasoning_content by default.
653
+ if (
654
+ input.model.providerID === "alibaba-cn" &&
655
+ input.model.capabilities.reasoning &&
656
+ input.model.api.npm === "@ai-sdk/openai-compatible" &&
657
+ !modelId.includes("kimi-k2-thinking")
658
+ ) {
659
+ result["enable_thinking"] = true
660
+ }
661
+
662
+ if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) {
663
+ if (!input.model.api.id.includes("gpt-5-pro")) {
664
+ result["reasoningEffort"] = "medium"
665
+ result["reasoningSummary"] = "auto"
666
+ }
667
+
668
+ // Only set textVerbosity for non-chat gpt-5.x models
669
+ // Chat models (e.g. gpt-5.2-chat-latest) only support "medium" verbosity
670
+ if (
671
+ input.model.api.id.includes("gpt-5.") &&
672
+ !input.model.api.id.includes("codex") &&
673
+ !input.model.api.id.includes("-chat") &&
674
+ input.model.providerID !== "azure"
675
+ ) {
676
+ result["textVerbosity"] = "low"
677
+ }
678
+
679
+ if (input.model.providerID.startsWith("innogpt")) {
680
+ result["promptCacheKey"] = input.sessionID
681
+ }
682
+ }
683
+
684
+ if (input.model.providerID === "venice") {
685
+ result["promptCacheKey"] = input.sessionID
686
+ }
687
+
688
+ return result
689
+ }
690
+
691
+ export function smallOptions(model: Provider.Model) {
692
+ if (
693
+ model.providerID === "openai" ||
694
+ model.api.npm === "@ai-sdk/openai" ||
695
+ model.api.npm === "@ai-sdk/github-copilot"
696
+ ) {
697
+ if (model.api.id.includes("gpt-5")) {
698
+ if (model.api.id.includes("5.")) {
699
+ return { store: false, reasoningEffort: "low" }
700
+ }
701
+ return { store: false, reasoningEffort: "minimal" }
702
+ }
703
+ return { store: false }
704
+ }
705
+ if (model.providerID === "google") {
706
+ // gemini-3 uses thinkingLevel, gemini-2.5 uses thinkingBudget
707
+ if (model.api.id.includes("gemini-3")) {
708
+ return { thinkingConfig: { thinkingLevel: "minimal" } }
709
+ }
710
+ return { thinkingConfig: { thinkingBudget: 0 } }
711
+ }
712
+ if (model.providerID === "openrouter") {
713
+ if (model.api.id.includes("google")) {
714
+ return { reasoning: { enabled: false } }
715
+ }
716
+ return { reasoningEffort: "minimal" }
717
+ }
718
+ return {}
719
+ }
720
+
721
+ export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
722
+ const key = sdkKey(model.api.npm) ?? model.providerID
723
+ return { [key]: options }
724
+ }
725
+
726
+ export function maxOutputTokens(model: Provider.Model): number {
727
+ return Math.min(model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
728
+ }
729
+
730
+ export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JSONSchema7): JSONSchema7 {
731
+ /*
732
+ if (["openai", "azure"].includes(providerID)) {
733
+ if (schema.type === "object" && schema.properties) {
734
+ for (const [key, value] of Object.entries(schema.properties)) {
735
+ if (schema.required?.includes(key)) continue
736
+ schema.properties[key] = {
737
+ anyOf: [
738
+ value as JSONSchema.JSONSchema,
739
+ {
740
+ type: "null",
741
+ },
742
+ ],
743
+ }
744
+ }
745
+ }
746
+ }
747
+ */
748
+
749
+ // Convert integer enums to string enums for Google/Gemini
750
+ if (model.providerID === "google" || model.api.id.includes("gemini")) {
751
+ const sanitizeGemini = (obj: any): any => {
752
+ if (obj === null || typeof obj !== "object") {
753
+ return obj
754
+ }
755
+
756
+ if (Array.isArray(obj)) {
757
+ return obj.map(sanitizeGemini)
758
+ }
759
+
760
+ const result: any = {}
761
+ for (const [key, value] of Object.entries(obj)) {
762
+ if (key === "enum" && Array.isArray(value)) {
763
+ // Convert all enum values to strings
764
+ result[key] = value.map((v) => String(v))
765
+ // If we have integer type with enum, change type to string
766
+ if (result.type === "integer" || result.type === "number") {
767
+ result.type = "string"
768
+ }
769
+ } else if (typeof value === "object" && value !== null) {
770
+ result[key] = sanitizeGemini(value)
771
+ } else {
772
+ result[key] = value
773
+ }
774
+ }
775
+
776
+ // Filter required array to only include fields that exist in properties
777
+ if (result.type === "object" && result.properties && Array.isArray(result.required)) {
778
+ result.required = result.required.filter((field: any) => field in result.properties)
779
+ }
780
+
781
+ if (result.type === "array") {
782
+ if (result.items == null) {
783
+ result.items = {}
784
+ }
785
+ // Ensure items has at least a type if it's an empty object
786
+ // This handles nested arrays like { type: "array", items: { type: "array", items: {} } }
787
+ if (typeof result.items === "object" && !Array.isArray(result.items) && !result.items.type) {
788
+ result.items.type = "string"
789
+ }
790
+ }
791
+
792
+ // Remove properties/required from non-object types (Gemini rejects these)
793
+ if (result.type && result.type !== "object") {
794
+ delete result.properties
795
+ delete result.required
796
+ }
797
+
798
+ return result
799
+ }
800
+
801
+ schema = sanitizeGemini(schema)
802
+ }
803
+
804
+ return schema as JSONSchema7
805
+ }
806
+ }