| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- import { test, expect, describe } from "bun:test"
- import path from "path"
- import { unlink } from "fs/promises"
- import { tmpdir } from "../fixture/fixture"
- import { Instance } from "../../src/project/instance"
- import { Provider } from "../../src/provider/provider"
- import { Env } from "../../src/env"
- import { Global } from "../../src/global"
- test("Bedrock: config region takes precedence over AWS_REGION env var", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "eu-west-1",
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_REGION", "us-east-1")
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
- },
- })
- })
- test("Bedrock: falls back to AWS_REGION env var when no config region", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_REGION", "eu-west-1")
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
- },
- })
- })
- test("Bedrock: loads when bearer token from auth.json is present", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "eu-west-1",
- },
- },
- },
- }),
- )
- },
- })
- const authPath = path.join(Global.Path.data, "auth.json")
- // Save original auth.json if it exists
- let originalAuth: string | undefined
- try {
- originalAuth = await Bun.file(authPath).text()
- } catch {
- // File doesn't exist, that's fine
- }
- try {
- // Write test auth.json
- await Bun.write(
- authPath,
- JSON.stringify({
- "amazon-bedrock": {
- type: "api",
- key: "test-bearer-token",
- },
- }),
- )
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "")
- Env.set("AWS_ACCESS_KEY_ID", "")
- Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
- },
- })
- } finally {
- // Restore original or delete
- if (originalAuth !== undefined) {
- await Bun.write(authPath, originalAuth)
- } else {
- try {
- await unlink(authPath)
- } catch {
- // Ignore errors if file doesn't exist
- }
- }
- }
- })
- test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- profile: "my-custom-profile",
- region: "us-east-1",
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- Env.set("AWS_ACCESS_KEY_ID", "test-key-id")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].options?.region).toBe("us-east-1")
- },
- })
- })
- test("Bedrock: includes custom endpoint in options when specified", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- endpoint: "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com",
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].options?.endpoint).toBe(
- "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com",
- )
- },
- })
- })
- test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "us-east-1",
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token")
- Env.set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role")
- Env.set("AWS_PROFILE", "")
- Env.set("AWS_ACCESS_KEY_ID", "")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].options?.region).toBe("us-east-1")
- },
- })
- })
- // Tests for cross-region inference profile prefix handling
- // Models from models.dev may come with prefixes already (e.g., us., eu., global.)
- // These should NOT be double-prefixed when passed to the SDK
- test("Bedrock: model with us. prefix should not be double-prefixed", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "us-east-1",
- },
- models: {
- "us.anthropic.claude-opus-4-5-20251101-v1:0": {
- name: "Claude Opus 4.5 (US)",
- },
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- // The model should exist with the us. prefix
- expect(providers["amazon-bedrock"].models["us.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
- },
- })
- })
- test("Bedrock: model with global. prefix should not be prefixed", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "us-east-1",
- },
- models: {
- "global.anthropic.claude-opus-4-5-20251101-v1:0": {
- name: "Claude Opus 4.5 (Global)",
- },
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].models["global.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
- },
- })
- })
- test("Bedrock: model with eu. prefix should not be double-prefixed", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "eu-west-1",
- },
- models: {
- "eu.anthropic.claude-opus-4-5-20251101-v1:0": {
- name: "Claude Opus 4.5 (EU)",
- },
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- expect(providers["amazon-bedrock"].models["eu.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
- },
- })
- })
- test("Bedrock: model without prefix in US region should get us. prefix added", async () => {
- await using tmp = await tmpdir({
- init: async (dir) => {
- await Bun.write(
- path.join(dir, "opencode.json"),
- JSON.stringify({
- $schema: "https://opencode.ai/config.json",
- provider: {
- "amazon-bedrock": {
- options: {
- region: "us-east-1",
- },
- models: {
- "anthropic.claude-opus-4-5-20251101-v1:0": {
- name: "Claude Opus 4.5",
- },
- },
- },
- },
- }),
- )
- },
- })
- await Instance.provide({
- directory: tmp.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- // Non-prefixed model should still be registered
- expect(providers["amazon-bedrock"].models["anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
- },
- })
- })
- // Direct unit tests for cross-region inference profile prefix handling
- // These test the prefix detection logic used in getModel
- describe("Bedrock cross-region prefix detection", () => {
- const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
- test("should detect global. prefix", () => {
- const modelID = "global.anthropic.claude-opus-4-5-20251101-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(true)
- })
- test("should detect us. prefix", () => {
- const modelID = "us.anthropic.claude-opus-4-5-20251101-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(true)
- })
- test("should detect eu. prefix", () => {
- const modelID = "eu.anthropic.claude-opus-4-5-20251101-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(true)
- })
- test("should detect jp. prefix", () => {
- const modelID = "jp.anthropic.claude-sonnet-4-20250514-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(true)
- })
- test("should detect apac. prefix", () => {
- const modelID = "apac.anthropic.claude-sonnet-4-20250514-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(true)
- })
- test("should detect au. prefix", () => {
- const modelID = "au.anthropic.claude-sonnet-4-5-20250929-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(true)
- })
- test("should NOT detect prefix for non-prefixed model", () => {
- const modelID = "anthropic.claude-opus-4-5-20251101-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(false)
- })
- test("should NOT detect prefix for amazon nova models", () => {
- const modelID = "amazon.nova-pro-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(false)
- })
- test("should NOT detect prefix for cohere models", () => {
- const modelID = "cohere.command-r-plus-v1:0"
- const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
- expect(hasPrefix).toBe(false)
- })
- })
|