aisdk-native.test.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. import { describe, expect, test } from "bun:test"
  2. import { AISDKNative } from "@opencode-ai/core/aisdk-native"
  3. const map = (packageName: string, settings: Readonly<Record<string, unknown>>, modelID = "test-model") =>
  4. AISDKNative.map({ packageName, settings, modelID })
  5. describe("AISDKNative", () => {
  6. test("maps both models.dev Bedrock packages to native providers", () => {
  7. expect(map("@ai-sdk/amazon-bedrock", { region: "us-east-1" })).toEqual({
  8. package: "@opencode-ai/ai/providers/amazon-bedrock",
  9. settings: { region: "us-east-1" },
  10. })
  11. expect(map("@ai-sdk/amazon-bedrock/mantle", { region: "us-east-1" }, "openai.gpt-oss-120b")).toEqual({
  12. package: "@opencode-ai/ai/providers/amazon-bedrock/mantle/responses",
  13. settings: { region: "us-east-1" },
  14. })
  15. })
  16. test("maps Azure deployments and settings to native routes", () => {
  17. const settings = {
  18. apiKey: "secret",
  19. resourceName: "resource",
  20. apiVersion: "2025-01-01-preview",
  21. queryParams: { feature: "enabled" },
  22. useDeploymentBasedUrls: true,
  23. reasoningEffort: "high",
  24. }
  25. expect(map("@ai-sdk/azure", settings, "deployment")).toEqual({
  26. package: "@opencode-ai/ai/providers/azure/responses",
  27. settings: {
  28. apiKey: "secret",
  29. resourceName: "resource",
  30. apiVersion: "2025-01-01-preview",
  31. queryParams: { feature: "enabled" },
  32. useDeploymentBasedUrls: true,
  33. providerOptions: { openai: { reasoningEffort: "high" } },
  34. },
  35. })
  36. expect(map("@ai-sdk/azure", { ...settings, useCompletionUrls: true }, "custom-deployment")?.package).toBe(
  37. "@opencode-ai/ai/providers/azure/chat",
  38. )
  39. })
  40. test("maps Bedrock provider and request options", () => {
  41. expect(
  42. map(
  43. "@ai-sdk/amazon-bedrock",
  44. {
  45. region: "us-east-1",
  46. topP: 0.8,
  47. headers: { "x-test": "value" },
  48. additionalModelRequestFields: {
  49. existing: true,
  50. anthropic_beta: ["existing-beta"],
  51. output_config: { format: "text" },
  52. },
  53. reasoningConfig: { type: "adaptive", display: "summarized", maxReasoningEffort: "high" },
  54. anthropicBeta: ["context-1m-2025-08-07"],
  55. serviceTier: "priority",
  56. },
  57. "anthropic.claude-sonnet-4-6-v1",
  58. ),
  59. ).toEqual({
  60. package: "@opencode-ai/ai/providers/amazon-bedrock",
  61. settings: { region: "us-east-1", topP: 0.8 },
  62. headers: { "x-test": "value" },
  63. body: {
  64. additionalModelRequestFields: {
  65. existing: true,
  66. anthropic_beta: ["existing-beta", "context-1m-2025-08-07"],
  67. thinking: { type: "adaptive", display: "summarized" },
  68. output_config: { format: "text", effort: "high" },
  69. },
  70. serviceTier: { type: "priority" },
  71. },
  72. })
  73. expect(
  74. map(
  75. "@ai-sdk/amazon-bedrock",
  76. { reasoningConfig: { type: "enabled", maxReasoningEffort: "max" } },
  77. "amazon.nova-2-lite-v1:0",
  78. )?.body,
  79. ).toEqual({
  80. additionalModelRequestFields: {
  81. reasoningConfig: { type: "enabled", maxReasoningEffort: "max" },
  82. },
  83. })
  84. expect(
  85. map("@ai-sdk/amazon-bedrock", { reasoningConfig: { maxReasoningEffort: "high" } }, "openai.gpt-oss-120b-1:0")
  86. ?.body,
  87. ).toEqual({ additionalModelRequestFields: { reasoning_effort: "high" } })
  88. })
  89. test("maps Bedrock Mantle models to their supported native APIs", () => {
  90. const settings = {
  91. bearerToken: "token",
  92. region: "us-west-2",
  93. baseURL: "https://mantle.test/v1",
  94. headers: { "x-test": "value" },
  95. reasoningEffort: "high",
  96. reasoningSummary: "auto",
  97. include: ["reasoning.encrypted_content"],
  98. }
  99. expect(map("@ai-sdk/amazon-bedrock/mantle", settings, "openai.gpt-oss-120b")).toEqual({
  100. package: "@opencode-ai/ai/providers/amazon-bedrock/mantle/responses",
  101. settings: {
  102. apiKey: "token",
  103. baseURL: "https://mantle.test/v1",
  104. region: "us-west-2",
  105. providerOptions: {
  106. openai: {
  107. reasoningEffort: "high",
  108. reasoningSummary: "auto",
  109. include: ["reasoning.encrypted_content"],
  110. },
  111. },
  112. },
  113. headers: { "x-test": "value" },
  114. })
  115. expect(map("@ai-sdk/amazon-bedrock/mantle", settings, "openai.gpt-oss-safeguard-20b")?.package).toBe(
  116. "@opencode-ai/ai/providers/amazon-bedrock/mantle/chat",
  117. )
  118. })
  119. test("maps static Bedrock Mantle credentials without leaking connection options", () => {
  120. expect(
  121. map(
  122. "@ai-sdk/amazon-bedrock/mantle",
  123. {
  124. credentials: {
  125. accessKeyId: "key",
  126. secretAccessKey: "secret",
  127. sessionToken: "session",
  128. },
  129. region: "eu-west-1",
  130. profile: "ignored",
  131. credentialProvider: "ignored",
  132. fetch: "ignored",
  133. store: false,
  134. },
  135. "openai.gpt-oss-120b",
  136. ),
  137. ).toEqual({
  138. package: "@opencode-ai/ai/providers/amazon-bedrock/mantle/responses",
  139. settings: {
  140. credentials: {
  141. accessKeyId: "key",
  142. secretAccessKey: "secret",
  143. sessionToken: "session",
  144. region: "eu-west-1",
  145. },
  146. region: "eu-west-1",
  147. providerOptions: { openai: { store: false } },
  148. },
  149. })
  150. })
  151. test("maps the legacy Bedrock endpoint override", () => {
  152. expect(
  153. map(
  154. "@ai-sdk/amazon-bedrock/mantle",
  155. { bearerToken: "token", endpoint: "https://mantle.private/v1", region: "us-east-1" },
  156. "openai.gpt-oss-120b",
  157. ),
  158. ).toMatchObject({ settings: { baseURL: "https://mantle.private/v1" } })
  159. })
  160. test("maps OpenRouter settings to native destinations", () => {
  161. expect(
  162. map("@openrouter/ai-sdk-provider", {
  163. appName: "OpenCode",
  164. appUrl: "https://opencode.ai",
  165. headers: { "x-openrouter-title": "Configured", "x-provider-api-keys": "Configured BYOK" },
  166. api_keys: { anthropic: "provider-key" },
  167. extraBody: { transforms: ["middle-out"] },
  168. models: ["anthropic/claude-sonnet-4.6"],
  169. provider: { only: ["anthropic"], require_parameters: true },
  170. reasoning: { effort: "high" },
  171. future_option: { enabled: true },
  172. }),
  173. ).toEqual({
  174. package: "@opencode-ai/ai/providers/openrouter",
  175. settings: {
  176. providerOptions: {
  177. openrouter: {
  178. models: ["anthropic/claude-sonnet-4.6"],
  179. provider: { only: ["anthropic"], require_parameters: true },
  180. reasoning: { effort: "high" },
  181. future_option: { enabled: true },
  182. },
  183. },
  184. },
  185. headers: {
  186. "x-openrouter-title": "Configured",
  187. "HTTP-Referer": "https://opencode.ai",
  188. "x-provider-api-keys": "Configured BYOK",
  189. },
  190. body: { transforms: ["middle-out"] },
  191. })
  192. })
  193. test("maps every Google thinking setting", () => {
  194. expect(
  195. map("@ai-sdk/google", {
  196. cachedContent: "cachedContents/example",
  197. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  198. serviceTier: "flex",
  199. thinkingConfig: {
  200. thinkingBudget: 0,
  201. includeThoughts: false,
  202. thinkingLevel: "high",
  203. unknown: true,
  204. },
  205. }),
  206. ).toEqual({
  207. package: "@opencode-ai/ai/providers/google",
  208. settings: {
  209. providerOptions: {
  210. gemini: {
  211. cachedContent: "cachedContents/example",
  212. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  213. serviceTier: "flex",
  214. thinkingConfig: {
  215. thinkingBudget: 0,
  216. includeThoughts: false,
  217. thinkingLevel: "high",
  218. },
  219. },
  220. },
  221. },
  222. })
  223. })
  224. test("maps Google thinking settings independently", () => {
  225. for (const thinkingConfig of [{ thinkingBudget: -1 }, { includeThoughts: true }, { thinkingLevel: "medium" }]) {
  226. expect(map("@ai-sdk/google", { thinkingConfig })).toMatchObject({
  227. settings: { providerOptions: { gemini: { thinkingConfig } } },
  228. })
  229. }
  230. })
  231. test("maps Google request options without thinking settings", () => {
  232. expect(
  233. map("@ai-sdk/google", {
  234. cachedContent: "cachedContents/example",
  235. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  236. serviceTier: "future-tier",
  237. }),
  238. ).toMatchObject({
  239. settings: {
  240. providerOptions: {
  241. gemini: {
  242. cachedContent: "cachedContents/example",
  243. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  244. serviceTier: "future-tier",
  245. },
  246. },
  247. },
  248. })
  249. })
  250. test("maps supported xAI settings", () => {
  251. expect(
  252. map("@ai-sdk/xai", {
  253. apiKey: "secret",
  254. baseURL: "https://xai.example/v1",
  255. reasoningEffort: "custom",
  256. store: true,
  257. }),
  258. ).toEqual({
  259. package: "@opencode-ai/ai/providers/xai",
  260. settings: {
  261. apiKey: "secret",
  262. baseURL: "https://xai.example/v1",
  263. providerOptions: {
  264. xai: {
  265. reasoningEffort: "custom",
  266. store: true,
  267. },
  268. },
  269. },
  270. })
  271. })
  272. test("omits invalid and unsupported xAI settings", () => {
  273. expect(
  274. map("@ai-sdk/xai", {
  275. reasoningEffort: 10,
  276. store: "yes",
  277. include: ["unknown"],
  278. logprobs: true,
  279. topLogprobs: 8,
  280. previousResponseId: "response-id",
  281. searchParameters: { mode: "auto" },
  282. }),
  283. ).toEqual({
  284. package: "@opencode-ai/ai/providers/xai",
  285. settings: {},
  286. })
  287. })
  288. })