test(opencode): port amazon-bedrock.test.ts to it.instance (#28559)

This commit is contained in:
Kit Langton
2026-05-20 22:31:42 -04:00
committed by GitHub
parent 26008696e1
commit c05ce3b725
@@ -1,31 +1,25 @@
import { afterEach, test, expect, describe } from "bun:test" import { afterEach, describe, expect, test } from "bun:test"
import { Effect, Layer } from "effect"
import path from "path" import path from "path"
import { unlink } from "fs/promises" import { unlink } from "fs/promises"
import { ProviderID } from "../../src/provider/schema"
import { disposeAllInstances, tmpdir, withTestInstance } from "../fixture/fixture"
import type { InstanceContext } from "../../src/project/instance-context"
import { Provider } from "@/provider/provider"
import { Env } from "../../src/env"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import { Effect } from "effect" import { Env } from "../../src/env"
import { AppRuntime } from "../../src/effect/app-runtime" import { Provider } from "@/provider/provider"
import { InstanceRef } from "../../src/effect/instance-ref" import { ProviderID } from "../../src/provider/schema"
import { makeRuntime } from "../../src/effect/run-service" import { disposeAllInstances } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(Provider.defaultLayer, Env.defaultLayer))
const env = makeRuntime(Env.Service, Env.defaultLayer)
const originalEnv = new Map<string, string | undefined>() const originalEnv = new Map<string, string | undefined>()
function rememberEnv(k: string) { const set = (k: string, v: string) =>
Effect.gen(function* () {
if (!originalEnv.has(k)) originalEnv.set(k, process.env[k]) if (!originalEnv.has(k)) originalEnv.set(k, process.env[k])
}
const set = (ctx: InstanceContext, k: string, v: string) => {
rememberEnv(k)
process.env[k] = v process.env[k] = v
return env.runSync((svc) => svc.set(k, v).pipe(Effect.provideService(InstanceRef, ctx))) yield* Env.Service.use((svc) => svc.set(k, v))
} })
afterEach(async () => { afterEach(async () => {
for (const [key, value] of originalEnv) { for (const [key, value] of originalEnv) {
@@ -36,427 +30,250 @@ afterEach(async () => {
await disposeAllInstances() await disposeAllInstances()
}) })
async function list(ctx: InstanceContext) { const list = Provider.Service.use((svc) => svc.list())
return AppRuntime.runPromise(
Effect.gen(function* () {
const provider = yield* Provider.Service
return yield* provider.list()
}).pipe(Effect.provideService(InstanceRef, ctx)),
)
}
test("Bedrock: config region takes precedence over AWS_REGION env var", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: {
"amazon-bedrock": {
options: {
region: "eu-west-1",
},
},
},
}),
)
},
})
await withTestInstance({
directory: tmp.path,
fn: async (ctx) => {
set(ctx, "AWS_REGION", "us-east-1")
set(ctx, "AWS_PROFILE", "default")
const providers = await list(ctx)
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].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 Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
}),
)
},
})
await withTestInstance({
directory: tmp.path,
fn: async (ctx) => {
set(ctx, "AWS_REGION", "eu-west-1")
set(ctx, "AWS_PROFILE", "default")
const providers = await list(ctx)
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].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 Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: {
"amazon-bedrock": {
options: {
region: "eu-west-1",
},
},
},
}),
)
},
})
const withAuthJson = (contents: string) =>
Effect.acquireRelease(
Effect.promise(async () => {
const authPath = path.join(Global.Path.data, "auth.json") const authPath = path.join(Global.Path.data, "auth.json")
let original: string | undefined
// Save original auth.json if it exists
let originalAuth: string | undefined
try { try {
originalAuth = await Filesystem.readText(authPath) original = await Filesystem.readText(authPath)
} catch { } catch {
// File doesn't exist, that's fine original = undefined
} }
await Filesystem.write(authPath, contents)
try { return { authPath, original }
// Write test auth.json }),
await Filesystem.write( ({ authPath, original }) =>
authPath, Effect.promise(async () => {
JSON.stringify({ if (original !== undefined) {
"amazon-bedrock": { await Filesystem.write(authPath, original)
type: "api", return
key: "test-bearer-token", }
}, await unlink(authPath).catch(() => undefined)
}), }),
) )
await withTestInstance({ it.instance(
directory: tmp.path, "Bedrock: config region takes precedence over AWS_REGION env var",
fn: async (ctx) => { () =>
set(ctx, "AWS_PROFILE", "") Effect.gen(function* () {
set(ctx, "AWS_ACCESS_KEY_ID", "") yield* set("AWS_REGION", "us-east-1")
set(ctx, "AWS_BEARER_TOKEN_BEDROCK", "") yield* set("AWS_PROFILE", "default")
const providers = await list(ctx) const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1") expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
}, }),
}) { config: { provider: { "amazon-bedrock": { options: { region: "eu-west-1" } } } } },
} finally { )
// Restore original or delete
if (originalAuth !== undefined) {
await Filesystem.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 () => { it.instance(
await using tmp = await tmpdir({ "Bedrock: falls back to AWS_REGION env var when no config region",
init: async (dir) => { () =>
await Filesystem.write( Effect.gen(function* () {
path.join(dir, "opencode.json"), yield* set("AWS_REGION", "eu-west-1")
JSON.stringify({ yield* set("AWS_PROFILE", "default")
$schema: "https://opencode.ai/config.json", const providers = yield* list
provider: { expect(providers[ProviderID.amazonBedrock]).toBeDefined()
"amazon-bedrock": { expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
options: {
profile: "my-custom-profile",
region: "us-east-1",
},
},
},
}), }),
) )
},
}) it.instance(
await withTestInstance({ "Bedrock: loads when bearer token from auth.json is present",
directory: tmp.path, () =>
fn: async (ctx) => { Effect.gen(function* () {
set(ctx, "AWS_PROFILE", "default") yield* withAuthJson(JSON.stringify({ "amazon-bedrock": { type: "api", key: "test-bearer-token" } }))
set(ctx, "AWS_ACCESS_KEY_ID", "test-key-id") yield* set("AWS_PROFILE", "")
const providers = await list(ctx) yield* set("AWS_ACCESS_KEY_ID", "")
yield* set("AWS_BEARER_TOKEN_BEDROCK", "")
const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
}),
{ config: { provider: { "amazon-bedrock": { options: { region: "eu-west-1" } } } } },
)
it.instance(
"Bedrock: config profile takes precedence over AWS_PROFILE env var",
() =>
Effect.gen(function* () {
yield* set("AWS_PROFILE", "default")
yield* set("AWS_ACCESS_KEY_ID", "test-key-id")
const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1") expect(providers[ProviderID.amazonBedrock].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 Filesystem.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",
},
},
},
}), }),
) {
config: {
provider: { "amazon-bedrock": { options: { profile: "my-custom-profile", region: "us-east-1" } } },
}, },
}) },
await withTestInstance({ )
directory: tmp.path,
fn: async (ctx) => { it.instance(
set(ctx, "AWS_PROFILE", "default") "Bedrock: includes custom endpoint in options when specified",
const providers = await list(ctx) () =>
Effect.gen(function* () {
yield* set("AWS_PROFILE", "default")
const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].options?.endpoint).toBe( expect(providers[ProviderID.amazonBedrock].options?.endpoint).toBe(
"https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com", "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com",
) )
}, }),
}) {
}) config: {
test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: { provider: {
"amazon-bedrock": { "amazon-bedrock": {
options: { options: { endpoint: "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com" },
region: "us-east-1", },
}, },
}, },
}, },
}),
) )
},
}) it.instance(
await withTestInstance({ "Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present",
directory: tmp.path, () =>
fn: async (ctx) => { Effect.gen(function* () {
set(ctx, "AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token") yield* set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token")
set(ctx, "AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role") yield* set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role")
set(ctx, "AWS_PROFILE", "") yield* set("AWS_PROFILE", "")
set(ctx, "AWS_ACCESS_KEY_ID", "") yield* set("AWS_ACCESS_KEY_ID", "")
const providers = await list(ctx) const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1") expect(providers[ProviderID.amazonBedrock].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 Filesystem.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)",
},
},
},
},
}), }),
{ config: { provider: { "amazon-bedrock": { options: { region: "us-east-1" } } } } },
) )
},
}) // Cross-region inference profile prefix handling.
await withTestInstance({ // Models from models.dev may come with prefixes already (e.g. us., eu., global.).
directory: tmp.path, // These should NOT be double-prefixed when passed to the SDK.
fn: async (ctx) => {
set(ctx, "AWS_PROFILE", "default") it.instance(
const providers = await list(ctx) "Bedrock: model with us. prefix should not be double-prefixed",
() =>
Effect.gen(function* () {
yield* set("AWS_PROFILE", "default")
const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
// The model should exist with the us. prefix
expect(providers[ProviderID.amazonBedrock].models["us.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() expect(providers[ProviderID.amazonBedrock].models["us.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
}, }),
}) {
}) config: {
test("Bedrock: model with global. prefix should not be prefixed", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: { provider: {
"amazon-bedrock": { "amazon-bedrock": {
options: { options: { region: "us-east-1" },
region: "us-east-1", models: { "us.anthropic.claude-opus-4-5-20251101-v1:0": { name: "Claude Opus 4.5 (US)" } },
},
models: {
"global.anthropic.claude-opus-4-5-20251101-v1:0": {
name: "Claude Opus 4.5 (Global)",
}, },
}, },
}, },
}, },
}),
) )
},
}) it.instance(
await withTestInstance({ "Bedrock: model with global. prefix should not be prefixed",
directory: tmp.path, () =>
fn: async (ctx) => { Effect.gen(function* () {
set(ctx, "AWS_PROFILE", "default") yield* set("AWS_PROFILE", "default")
const providers = await list(ctx) const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].models["global.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() expect(providers[ProviderID.amazonBedrock].models["global.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
}, }),
}) {
}) config: {
test("Bedrock: model with eu. prefix should not be double-prefixed", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: { provider: {
"amazon-bedrock": { "amazon-bedrock": {
options: { options: { region: "us-east-1" },
region: "eu-west-1", models: { "global.anthropic.claude-opus-4-5-20251101-v1:0": { name: "Claude Opus 4.5 (Global)" } },
},
models: {
"eu.anthropic.claude-opus-4-5-20251101-v1:0": {
name: "Claude Opus 4.5 (EU)",
}, },
}, },
}, },
}, },
}),
) )
},
}) it.instance(
await withTestInstance({ "Bedrock: model with eu. prefix should not be double-prefixed",
directory: tmp.path, () =>
fn: async (ctx) => { Effect.gen(function* () {
set(ctx, "AWS_PROFILE", "default") yield* set("AWS_PROFILE", "default")
const providers = await list(ctx) const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
expect(providers[ProviderID.amazonBedrock].models["eu.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() expect(providers[ProviderID.amazonBedrock].models["eu.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
}, }),
}) {
}) config: {
test("Bedrock: model without prefix in US region should get us. prefix added", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: { provider: {
"amazon-bedrock": { "amazon-bedrock": {
options: { options: { region: "eu-west-1" },
region: "us-east-1", models: { "eu.anthropic.claude-opus-4-5-20251101-v1:0": { name: "Claude Opus 4.5 (EU)" } },
},
models: {
"anthropic.claude-opus-4-5-20251101-v1:0": {
name: "Claude Opus 4.5",
}, },
}, },
}, },
}, },
}),
) )
},
}) it.instance(
await withTestInstance({ "Bedrock: model without prefix in US region should get us. prefix added",
directory: tmp.path, () =>
fn: async (ctx) => { Effect.gen(function* () {
set(ctx, "AWS_PROFILE", "default") yield* set("AWS_PROFILE", "default")
const providers = await list(ctx) const providers = yield* list
expect(providers[ProviderID.amazonBedrock]).toBeDefined() expect(providers[ProviderID.amazonBedrock]).toBeDefined()
// Non-prefixed model should still be registered
expect(providers[ProviderID.amazonBedrock].models["anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined() expect(providers[ProviderID.amazonBedrock].models["anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
}),
{
config: {
provider: {
"amazon-bedrock": {
options: { region: "us-east-1" },
models: { "anthropic.claude-opus-4-5-20251101-v1:0": { name: "Claude Opus 4.5" } },
}, },
}) },
}) },
},
// Direct unit tests for cross-region inference profile prefix handling )
// These test the prefix detection logic used in getModel
// Direct unit tests for cross-region inference profile prefix detection.
describe("Bedrock cross-region prefix detection", () => { describe("Bedrock cross-region prefix detection", () => {
const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."] const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
test("should detect global. prefix", () => { test("should detect global. prefix", () => {
const modelID = "global.anthropic.claude-opus-4-5-20251101-v1:0" expect(crossRegionPrefixes.some((p) => "global.anthropic.claude-opus-4-5-20251101-v1:0".startsWith(p))).toBe(true)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(true)
}) })
test("should detect us. prefix", () => { test("should detect us. prefix", () => {
const modelID = "us.anthropic.claude-opus-4-5-20251101-v1:0" expect(crossRegionPrefixes.some((p) => "us.anthropic.claude-opus-4-5-20251101-v1:0".startsWith(p))).toBe(true)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(true)
}) })
test("should detect eu. prefix", () => { test("should detect eu. prefix", () => {
const modelID = "eu.anthropic.claude-opus-4-5-20251101-v1:0" expect(crossRegionPrefixes.some((p) => "eu.anthropic.claude-opus-4-5-20251101-v1:0".startsWith(p))).toBe(true)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(true)
}) })
test("should detect jp. prefix", () => { test("should detect jp. prefix", () => {
const modelID = "jp.anthropic.claude-sonnet-4-20250514-v1:0" expect(crossRegionPrefixes.some((p) => "jp.anthropic.claude-sonnet-4-20250514-v1:0".startsWith(p))).toBe(true)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(true)
}) })
test("should detect apac. prefix", () => { test("should detect apac. prefix", () => {
const modelID = "apac.anthropic.claude-sonnet-4-20250514-v1:0" expect(crossRegionPrefixes.some((p) => "apac.anthropic.claude-sonnet-4-20250514-v1:0".startsWith(p))).toBe(true)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(true)
}) })
test("should detect au. prefix", () => { test("should detect au. prefix", () => {
const modelID = "au.anthropic.claude-sonnet-4-5-20250929-v1:0" expect(crossRegionPrefixes.some((p) => "au.anthropic.claude-sonnet-4-5-20250929-v1:0".startsWith(p))).toBe(true)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(true)
}) })
test("should NOT detect prefix for non-prefixed model", () => { test("should NOT detect prefix for non-prefixed model", () => {
const modelID = "anthropic.claude-opus-4-5-20251101-v1:0" expect(crossRegionPrefixes.some((p) => "anthropic.claude-opus-4-5-20251101-v1:0".startsWith(p))).toBe(false)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(false)
}) })
test("should NOT detect prefix for amazon nova models", () => { test("should NOT detect prefix for amazon nova models", () => {
const modelID = "amazon.nova-pro-v1:0" expect(crossRegionPrefixes.some((p) => "amazon.nova-pro-v1:0".startsWith(p))).toBe(false)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(false)
}) })
test("should NOT detect prefix for cohere models", () => { test("should NOT detect prefix for cohere models", () => {
const modelID = "cohere.command-r-plus-v1:0" expect(crossRegionPrefixes.some((p) => "cohere.command-r-plus-v1:0".startsWith(p))).toBe(false)
const hasPrefix = crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))
expect(hasPrefix).toBe(false)
}) })
}) })