summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSergiy Dybskiy <[email protected]>2026-02-04 00:06:17 -0500
committerGitHub <[email protected]>2026-02-03 23:06:17 -0600
commitb942e0b4dcaada78ed646f7e3868424cab9e913a (patch)
tree8a042e5ff4cb9ca01d1d6ecde63d12914a63e412
parentf2826137463d099d3cf032fc8329a8fce437ef80 (diff)
downloadopencode-b942e0b4dcaada78ed646f7e3868424cab9e913a.tar.gz
opencode-b942e0b4dcaada78ed646f7e3868424cab9e913a.zip
fix: prevent double-prefixing of Bedrock cross-region inference models (#12056)
-rw-r--r--packages/opencode/src/provider/provider.ts4
-rw-r--r--packages/opencode/test/provider/amazon-bedrock.test.ts213
2 files changed, 215 insertions, 2 deletions
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index fc90571fe..d5d54c5e5 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -239,7 +239,9 @@ export namespace Provider {
options: providerOptions,
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
// Skip region prefixing if model already has a cross-region inference profile prefix
- if (modelID.startsWith("global.") || modelID.startsWith("jp.")) {
+ // Models from models.dev may already include prefixes like us., eu., global., etc.
+ const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
+ if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) {
return sdk.languageModel(modelID)
}
diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts
index 6b5cf681c..a90c9632d 100644
--- a/packages/opencode/test/provider/amazon-bedrock.test.ts
+++ b/packages/opencode/test/provider/amazon-bedrock.test.ts
@@ -1,4 +1,4 @@
-import { test, expect, mock } from "bun:test"
+import { test, expect, mock, describe } from "bun:test"
import path from "path"
import { unlink } from "fs/promises"
@@ -266,3 +266,214 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async ()
},
})
})
+
+// 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)
+ })
+})