provider-amazon-bedrock.test.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { Catalog } from "@opencode-ai/core/catalog"
  4. import { PluginV2 } from "@opencode-ai/core/plugin"
  5. import { AmazonBedrockPlugin } from "@opencode-ai/core/plugin/provider/amazon-bedrock"
  6. import { ProviderV2 } from "@opencode-ai/core/provider"
  7. import { fakeSelectorSdk, it, model, provider, withEnv } from "./provider-helper"
  8. function bedrockBaseURL(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") {
  9. const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID)
  10. return (language as { config: { baseUrl: () => string } }).config.baseUrl()
  11. }
  12. function bedrockFetch(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") {
  13. const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID)
  14. return (
  15. language as { config: { fetch: (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response> } }
  16. ).config.fetch
  17. }
  18. function openAIUrl(language: unknown, path: string, modelId: string) {
  19. return (language as { config: { url: (input: { path: string; modelId: string }) => string } }).config.url({
  20. path,
  21. modelId,
  22. })
  23. }
  24. describe("AmazonBedrockPlugin", () => {
  25. it.effect("moves endpoint option to api URL", () =>
  26. Effect.gen(function* () {
  27. const plugin = yield* PluginV2.Service
  28. const catalog = yield* Catalog.Service
  29. yield* plugin.add(AmazonBedrockPlugin)
  30. const transform = yield* catalog.transform()
  31. yield* transform((catalog) => {
  32. const bedrock = provider("amazon-bedrock", {
  33. api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
  34. request: {
  35. headers: {},
  36. body: { endpoint: "https://bedrock.example" },
  37. },
  38. })
  39. catalog.provider.update(bedrock.id, (item) => {
  40. item.api = bedrock.api
  41. item.request = bedrock.request
  42. })
  43. })
  44. const result = yield* catalog.provider.get(ProviderV2.ID.amazonBedrock)
  45. expect(result.api).toEqual({
  46. type: "aisdk",
  47. package: "@ai-sdk/amazon-bedrock",
  48. url: "https://bedrock.example",
  49. })
  50. expect(result.request.body.endpoint).toBeUndefined()
  51. }),
  52. )
  53. it.effect("prefers endpoint over baseURL for SDK base URL", () =>
  54. withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
  55. Effect.gen(function* () {
  56. const plugin = yield* PluginV2.Service
  57. yield* plugin.add(AmazonBedrockPlugin)
  58. const result = yield* plugin.trigger(
  59. "aisdk.sdk",
  60. {
  61. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  62. package: "@ai-sdk/amazon-bedrock",
  63. options: {
  64. name: "amazon-bedrock",
  65. bearerToken: "token",
  66. baseURL: "https://base.example",
  67. endpoint: "https://endpoint.example",
  68. region: "us-east-1",
  69. },
  70. },
  71. {},
  72. )
  73. expect(bedrockBaseURL(result.sdk)).toBe("https://endpoint.example")
  74. }),
  75. ),
  76. )
  77. it.effect("uses baseURL as SDK base URL", () =>
  78. withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
  79. Effect.gen(function* () {
  80. const plugin = yield* PluginV2.Service
  81. yield* plugin.add(AmazonBedrockPlugin)
  82. const result = yield* plugin.trigger(
  83. "aisdk.sdk",
  84. {
  85. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  86. package: "@ai-sdk/amazon-bedrock",
  87. options: {
  88. name: "amazon-bedrock",
  89. bearerToken: "token",
  90. baseURL: "https://base.example",
  91. region: "us-east-1",
  92. },
  93. },
  94. {},
  95. )
  96. expect(bedrockBaseURL(result.sdk)).toBe("https://base.example")
  97. }),
  98. ),
  99. )
  100. it.effect("creates SDK without explicit credential env so the default AWS chain can resolve credentials", () =>
  101. withEnv(
  102. {
  103. AWS_ACCESS_KEY_ID: undefined,
  104. AWS_BEARER_TOKEN_BEDROCK: undefined,
  105. AWS_CONTAINER_CREDENTIALS_FULL_URI: undefined,
  106. AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: undefined,
  107. AWS_PROFILE: undefined,
  108. AWS_REGION: undefined,
  109. AWS_WEB_IDENTITY_TOKEN_FILE: undefined,
  110. },
  111. () =>
  112. Effect.gen(function* () {
  113. const plugin = yield* PluginV2.Service
  114. yield* plugin.add(AmazonBedrockPlugin)
  115. const result = yield* plugin.trigger(
  116. "aisdk.sdk",
  117. {
  118. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  119. package: "@ai-sdk/amazon-bedrock",
  120. options: { name: "amazon-bedrock" },
  121. },
  122. {},
  123. )
  124. expect(result.sdk).toBeDefined()
  125. expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.us-east-1.amazonaws.com")
  126. }),
  127. ),
  128. )
  129. it.effect("uses config region over AWS_REGION for SDK base URL", () =>
  130. withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "us-east-1" }, () =>
  131. Effect.gen(function* () {
  132. const plugin = yield* PluginV2.Service
  133. yield* plugin.add(AmazonBedrockPlugin)
  134. const result = yield* plugin.trigger(
  135. "aisdk.sdk",
  136. {
  137. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  138. package: "@ai-sdk/amazon-bedrock",
  139. options: { name: "amazon-bedrock", region: "eu-west-1" },
  140. },
  141. {},
  142. )
  143. expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.eu-west-1.amazonaws.com")
  144. }),
  145. ),
  146. )
  147. it.effect("uses AWS_REGION for SDK base URL when config region is absent", () =>
  148. withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "eu-west-1" }, () =>
  149. Effect.gen(function* () {
  150. const plugin = yield* PluginV2.Service
  151. yield* plugin.add(AmazonBedrockPlugin)
  152. const result = yield* plugin.trigger(
  153. "aisdk.sdk",
  154. {
  155. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  156. package: "@ai-sdk/amazon-bedrock",
  157. options: { name: "amazon-bedrock" },
  158. },
  159. {},
  160. )
  161. expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.eu-west-1.amazonaws.com")
  162. }),
  163. ),
  164. )
  165. it.effect("defaults SDK region to us-east-1", () =>
  166. withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: undefined }, () =>
  167. Effect.gen(function* () {
  168. const plugin = yield* PluginV2.Service
  169. yield* plugin.add(AmazonBedrockPlugin)
  170. const result = yield* plugin.trigger(
  171. "aisdk.sdk",
  172. {
  173. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  174. package: "@ai-sdk/amazon-bedrock",
  175. options: { name: "amazon-bedrock" },
  176. },
  177. {},
  178. )
  179. expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.us-east-1.amazonaws.com")
  180. }),
  181. ),
  182. )
  183. it.effect("loads bearer token option into env and uses bearer auth", () =>
  184. withEnv({ AWS_ACCESS_KEY_ID: undefined, AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined }, () =>
  185. Effect.gen(function* () {
  186. const plugin = yield* PluginV2.Service
  187. const headers: Array<string | null> = []
  188. yield* plugin.add(AmazonBedrockPlugin)
  189. const result = yield* plugin.trigger(
  190. "aisdk.sdk",
  191. {
  192. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  193. package: "@ai-sdk/amazon-bedrock",
  194. options: {
  195. name: "amazon-bedrock",
  196. bearerToken: "option-token",
  197. fetch: async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
  198. headers.push(new Headers(init?.headers).get("Authorization"))
  199. return new Response("{}")
  200. },
  201. },
  202. },
  203. {},
  204. )
  205. yield* Effect.promise(() => bedrockFetch(result.sdk)("https://bedrock.example", { method: "POST" }))
  206. expect(process.env.AWS_BEARER_TOKEN_BEDROCK).toBe("option-token")
  207. expect(headers).toEqual(["Bearer option-token"])
  208. }),
  209. ),
  210. )
  211. it.effect("prefers bearer token env over bearer token option", () =>
  212. withEnv({ AWS_BEARER_TOKEN_BEDROCK: "env-token" }, () =>
  213. Effect.gen(function* () {
  214. const plugin = yield* PluginV2.Service
  215. const headers: Array<string | null> = []
  216. yield* plugin.add(AmazonBedrockPlugin)
  217. const result = yield* plugin.trigger(
  218. "aisdk.sdk",
  219. {
  220. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  221. package: "@ai-sdk/amazon-bedrock",
  222. options: {
  223. name: "amazon-bedrock",
  224. bearerToken: "option-token",
  225. fetch: async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
  226. headers.push(new Headers(init?.headers).get("Authorization"))
  227. return new Response("{}")
  228. },
  229. },
  230. },
  231. {},
  232. )
  233. yield* Effect.promise(() => bedrockFetch(result.sdk)("https://bedrock.example", { method: "POST" }))
  234. expect(process.env.AWS_BEARER_TOKEN_BEDROCK).toBe("env-token")
  235. expect(headers).toEqual(["Bearer env-token"])
  236. }),
  237. ),
  238. )
  239. it.effect("creates Mantle SDK with GPT-5 OpenAI base path", () =>
  240. withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
  241. Effect.gen(function* () {
  242. const plugin = yield* PluginV2.Service
  243. yield* plugin.add(AmazonBedrockPlugin)
  244. const result = yield* plugin.trigger(
  245. "aisdk.sdk",
  246. {
  247. model: model("amazon-bedrock", "openai.gpt-5.5", {
  248. api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock/mantle" },
  249. }),
  250. package: "@ai-sdk/amazon-bedrock/mantle",
  251. options: {
  252. name: "amazon-bedrock",
  253. bearerToken: "token",
  254. baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
  255. region: "us-east-2",
  256. },
  257. },
  258. {},
  259. )
  260. const language = result.sdk.responses("openai.gpt-5.5")
  261. expect(openAIUrl(language, "/responses", "openai.gpt-5.5")).toBe(
  262. "https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses",
  263. )
  264. }),
  265. ),
  266. )
  267. it.effect("selects Mantle APIs without Bedrock cross-region prefixes", () =>
  268. Effect.gen(function* () {
  269. const plugin = yield* PluginV2.Service
  270. const calls: string[] = []
  271. yield* plugin.add(AmazonBedrockPlugin)
  272. yield* plugin.trigger(
  273. "aisdk.language",
  274. {
  275. model: model("amazon-bedrock", "openai.gpt-5.5", {
  276. api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock/mantle" },
  277. }),
  278. sdk: fakeSelectorSdk(calls),
  279. options: { baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", region: "us-east-2" },
  280. },
  281. {},
  282. )
  283. yield* plugin.trigger(
  284. "aisdk.language",
  285. {
  286. model: model("amazon-bedrock", "openai.gpt-oss-safeguard-120b", {
  287. api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock/mantle" },
  288. }),
  289. sdk: fakeSelectorSdk(calls),
  290. options: { region: "us-east-1" },
  291. },
  292. {},
  293. )
  294. expect(calls).toEqual(["responses:openai.gpt-5.5", "chat:openai.gpt-oss-safeguard-120b"])
  295. }),
  296. )
  297. it.effect("ignores other Bedrock provider subpaths", () =>
  298. Effect.gen(function* () {
  299. const plugin = yield* PluginV2.Service
  300. yield* plugin.add(AmazonBedrockPlugin)
  301. const result = yield* plugin.trigger(
  302. "aisdk.sdk",
  303. {
  304. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5", {
  305. api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock/anthropic" },
  306. }),
  307. package: "@ai-sdk/amazon-bedrock/anthropic",
  308. options: { name: "amazon-bedrock" },
  309. },
  310. {},
  311. )
  312. expect(result.sdk).toBeUndefined()
  313. }),
  314. )
  315. it.effect("uses SigV4 credential env when bearer token is absent", () =>
  316. withEnv(
  317. {
  318. AWS_ACCESS_KEY_ID: "test-access-key",
  319. AWS_BEARER_TOKEN_BEDROCK: undefined,
  320. AWS_REGION: "us-east-1",
  321. AWS_SECRET_ACCESS_KEY: "test-secret-key",
  322. AWS_SESSION_TOKEN: "test-session-token",
  323. },
  324. () =>
  325. Effect.gen(function* () {
  326. const plugin = yield* PluginV2.Service
  327. const headers: Array<string | null> = []
  328. yield* plugin.add(AmazonBedrockPlugin)
  329. const result = yield* plugin.trigger(
  330. "aisdk.sdk",
  331. {
  332. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  333. package: "@ai-sdk/amazon-bedrock",
  334. options: {
  335. name: "amazon-bedrock",
  336. fetch: async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
  337. headers.push(new Headers(init?.headers).get("Authorization"))
  338. return new Response("{}")
  339. },
  340. },
  341. },
  342. {},
  343. )
  344. yield* Effect.promise(() =>
  345. bedrockFetch(result.sdk)("https://bedrock-runtime.us-east-1.amazonaws.com/model/test/invoke", {
  346. body: "{}",
  347. method: "POST",
  348. }),
  349. )
  350. expect(headers[0]?.startsWith("AWS4-HMAC-SHA256 ")).toBe(true)
  351. }),
  352. ),
  353. )
  354. it.effect("applies legacy cross-region inference prefixes", () =>
  355. Effect.gen(function* () {
  356. const plugin = yield* PluginV2.Service
  357. const calls: string[] = []
  358. yield* plugin.add(AmazonBedrockPlugin)
  359. yield* plugin.trigger(
  360. "aisdk.language",
  361. {
  362. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  363. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  364. options: {},
  365. },
  366. {},
  367. )
  368. yield* plugin.trigger(
  369. "aisdk.language",
  370. {
  371. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  372. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  373. options: { region: "eu-west-1" },
  374. },
  375. {},
  376. )
  377. yield* plugin.trigger(
  378. "aisdk.language",
  379. {
  380. model: model("amazon-bedrock", "global.anthropic.claude-sonnet-4-5"),
  381. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  382. options: { region: "eu-west-1" },
  383. },
  384. {},
  385. )
  386. yield* plugin.trigger(
  387. "aisdk.language",
  388. {
  389. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  390. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  391. options: { region: "ap-northeast-1" },
  392. },
  393. {},
  394. )
  395. yield* plugin.trigger(
  396. "aisdk.language",
  397. {
  398. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  399. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  400. options: { region: "ap-southeast-2" },
  401. },
  402. {},
  403. )
  404. expect(calls).toEqual([
  405. "languageModel:us.anthropic.claude-sonnet-4-5",
  406. "languageModel:eu.anthropic.claude-sonnet-4-5",
  407. "languageModel:global.anthropic.claude-sonnet-4-5",
  408. "languageModel:jp.anthropic.claude-sonnet-4-5",
  409. "languageModel:au.anthropic.claude-sonnet-4-5",
  410. ])
  411. }),
  412. )
  413. it.effect("uses AWS_REGION for language prefixes when region option is absent", () =>
  414. withEnv({ AWS_REGION: "eu-west-1" }, () =>
  415. Effect.gen(function* () {
  416. const plugin = yield* PluginV2.Service
  417. const calls: string[] = []
  418. yield* plugin.add(AmazonBedrockPlugin)
  419. yield* plugin.trigger(
  420. "aisdk.language",
  421. {
  422. model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"),
  423. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  424. options: {},
  425. },
  426. {},
  427. )
  428. expect(calls).toEqual(["languageModel:eu.anthropic.claude-sonnet-4-5"])
  429. }),
  430. ),
  431. )
  432. it.effect("applies the full legacy cross-region prefix matrix", () =>
  433. Effect.gen(function* () {
  434. const plugin = yield* PluginV2.Service
  435. const calls: string[] = []
  436. const cases = [
  437. { region: "us-east-1", modelID: "amazon.nova-micro-v1:0", expected: "us.amazon.nova-micro-v1:0" },
  438. { region: "us-east-1", modelID: "amazon.nova-lite-v1:0", expected: "us.amazon.nova-lite-v1:0" },
  439. { region: "us-east-1", modelID: "amazon.nova-pro-v1:0", expected: "us.amazon.nova-pro-v1:0" },
  440. { region: "us-east-1", modelID: "amazon.nova-premier-v1:0", expected: "us.amazon.nova-premier-v1:0" },
  441. { region: "us-east-1", modelID: "amazon.nova-2-lite-v1:0", expected: "us.amazon.nova-2-lite-v1:0" },
  442. { region: "us-east-1", modelID: "anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" },
  443. { region: "us-east-1", modelID: "deepseek.r1-v1:0", expected: "us.deepseek.r1-v1:0" },
  444. { region: "us-gov-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" },
  445. { region: "us-east-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" },
  446. { region: "eu-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
  447. { region: "eu-west-2", modelID: "amazon.nova-lite-v1:0", expected: "eu.amazon.nova-lite-v1:0" },
  448. { region: "eu-west-3", modelID: "amazon.nova-micro-v1:0", expected: "eu.amazon.nova-micro-v1:0" },
  449. {
  450. region: "eu-north-1",
  451. modelID: "meta.llama3-70b-instruct-v1:0",
  452. expected: "eu.meta.llama3-70b-instruct-v1:0",
  453. },
  454. { region: "eu-central-1", modelID: "mistral.pixtral-large-v1:0", expected: "eu.mistral.pixtral-large-v1:0" },
  455. { region: "eu-south-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
  456. { region: "eu-south-2", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
  457. { region: "eu-central-2", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" },
  458. { region: "eu-west-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" },
  459. {
  460. region: "ap-southeast-2",
  461. modelID: "anthropic.claude-sonnet-4-5",
  462. expected: "au.anthropic.claude-sonnet-4-5",
  463. },
  464. {
  465. region: "ap-southeast-4",
  466. modelID: "anthropic.claude-haiku-v1:0",
  467. expected: "au.anthropic.claude-haiku-v1:0",
  468. },
  469. { region: "ap-southeast-2", modelID: "anthropic.claude-opus-4", expected: "apac.anthropic.claude-opus-4" },
  470. {
  471. region: "ap-northeast-1",
  472. modelID: "anthropic.claude-sonnet-4-5",
  473. expected: "jp.anthropic.claude-sonnet-4-5",
  474. },
  475. { region: "ap-northeast-1", modelID: "amazon.nova-pro-v1:0", expected: "jp.amazon.nova-pro-v1:0" },
  476. { region: "ap-south-1", modelID: "anthropic.claude-sonnet-4-5", expected: "apac.anthropic.claude-sonnet-4-5" },
  477. { region: "ap-south-1", modelID: "amazon.nova-lite-v1:0", expected: "apac.amazon.nova-lite-v1:0" },
  478. { region: "ca-central-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" },
  479. {
  480. region: "us-east-1",
  481. modelID: "global.anthropic.claude-sonnet-4-5",
  482. expected: "global.anthropic.claude-sonnet-4-5",
  483. },
  484. { region: "us-east-1", modelID: "us.anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" },
  485. { region: "eu-west-1", modelID: "eu.anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
  486. {
  487. region: "ap-northeast-1",
  488. modelID: "jp.anthropic.claude-sonnet-4-5",
  489. expected: "jp.anthropic.claude-sonnet-4-5",
  490. },
  491. {
  492. region: "ap-south-1",
  493. modelID: "apac.anthropic.claude-sonnet-4-5",
  494. expected: "apac.anthropic.claude-sonnet-4-5",
  495. },
  496. {
  497. region: "ap-southeast-2",
  498. modelID: "au.anthropic.claude-sonnet-4-5",
  499. expected: "au.anthropic.claude-sonnet-4-5",
  500. },
  501. ]
  502. yield* plugin.add(AmazonBedrockPlugin)
  503. for (const item of cases) {
  504. yield* plugin.trigger(
  505. "aisdk.language",
  506. {
  507. model: model("amazon-bedrock", item.modelID),
  508. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  509. options: { region: item.region },
  510. },
  511. {},
  512. )
  513. }
  514. expect(calls).toEqual(cases.map((item) => `languageModel:${item.expected}`))
  515. }),
  516. )
  517. it.effect("ignores non-Bedrock providers for language selection", () =>
  518. Effect.gen(function* () {
  519. const plugin = yield* PluginV2.Service
  520. const calls: string[] = []
  521. yield* plugin.add(AmazonBedrockPlugin)
  522. const result = yield* plugin.trigger(
  523. "aisdk.language",
  524. {
  525. model: model("openai", "anthropic.claude-sonnet-4-5"),
  526. sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
  527. options: { region: "eu-west-1" },
  528. },
  529. {},
  530. )
  531. expect(calls).toEqual([])
  532. expect(result.language).toBeUndefined()
  533. }),
  534. )
  535. })