aisdk-native.test.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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. promptCacheKey: "session_123",
  172. future_option: { enabled: true },
  173. }),
  174. ).toEqual({
  175. package: "@opencode-ai/ai/providers/openrouter",
  176. settings: {
  177. providerOptions: {
  178. openrouter: {
  179. models: ["anthropic/claude-sonnet-4.6"],
  180. provider: { only: ["anthropic"], require_parameters: true },
  181. reasoning: { effort: "high" },
  182. promptCacheKey: "session_123",
  183. future_option: { enabled: true },
  184. },
  185. },
  186. },
  187. headers: {
  188. "x-openrouter-title": "Configured",
  189. "HTTP-Referer": "https://opencode.ai",
  190. "x-provider-api-keys": "Configured BYOK",
  191. },
  192. body: { transforms: ["middle-out"] },
  193. })
  194. })
  195. test("maps every Google thinking setting", () => {
  196. expect(
  197. map("@ai-sdk/google", {
  198. cachedContent: "cachedContents/example",
  199. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  200. serviceTier: "flex",
  201. thinkingConfig: {
  202. thinkingBudget: 0,
  203. includeThoughts: false,
  204. thinkingLevel: "high",
  205. unknown: true,
  206. },
  207. }),
  208. ).toEqual({
  209. package: "@opencode-ai/ai/providers/google",
  210. settings: {
  211. providerOptions: {
  212. gemini: {
  213. cachedContent: "cachedContents/example",
  214. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  215. serviceTier: "flex",
  216. thinkingConfig: {
  217. thinkingBudget: 0,
  218. includeThoughts: false,
  219. thinkingLevel: "high",
  220. },
  221. },
  222. },
  223. },
  224. })
  225. })
  226. test("maps Google thinking settings independently", () => {
  227. for (const thinkingConfig of [{ thinkingBudget: -1 }, { includeThoughts: true }, { thinkingLevel: "medium" }]) {
  228. expect(map("@ai-sdk/google", { thinkingConfig })).toMatchObject({
  229. settings: { providerOptions: { gemini: { thinkingConfig } } },
  230. })
  231. }
  232. })
  233. test("maps Google request options without thinking settings", () => {
  234. expect(
  235. map("@ai-sdk/google", {
  236. cachedContent: "cachedContents/example",
  237. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  238. serviceTier: "future-tier",
  239. }),
  240. ).toMatchObject({
  241. settings: {
  242. providerOptions: {
  243. gemini: {
  244. cachedContent: "cachedContents/example",
  245. safetySettings: [{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }],
  246. serviceTier: "future-tier",
  247. },
  248. },
  249. },
  250. })
  251. })
  252. test("maps supported xAI settings", () => {
  253. expect(
  254. map("@ai-sdk/xai", {
  255. apiKey: "secret",
  256. baseURL: "https://xai.example/v1",
  257. reasoningEffort: "custom",
  258. store: true,
  259. promptCacheKey: "cache-key",
  260. }),
  261. ).toEqual({
  262. package: "@opencode-ai/ai/providers/xai",
  263. settings: {
  264. apiKey: "secret",
  265. baseURL: "https://xai.example/v1",
  266. providerOptions: {
  267. xai: {
  268. reasoningEffort: "custom",
  269. store: true,
  270. promptCacheKey: "cache-key",
  271. },
  272. },
  273. },
  274. })
  275. })
  276. test("omits invalid and unsupported xAI settings", () => {
  277. expect(
  278. map("@ai-sdk/xai", {
  279. reasoningEffort: 10,
  280. store: "yes",
  281. include: ["unknown"],
  282. logprobs: true,
  283. topLogprobs: 8,
  284. previousResponseId: "response-id",
  285. searchParameters: { mode: "auto" },
  286. }),
  287. ).toEqual({
  288. package: "@opencode-ai/ai/providers/xai",
  289. settings: {},
  290. })
  291. })
  292. })