summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGrĂ©goire Morpain <[email protected]>2026-01-05 19:51:43 +0100
committerGitHub <[email protected]>2026-01-05 12:51:43 -0600
commite3b4d4ad49fcb34919b0cda4a9a9b4e6f1bc4f2b (patch)
tree36527bffac598dec189d6410d6d190a9b2a31474
parent6b207b09d6a19aec70c7e312016779721f64a377 (diff)
downloadopencode-e3b4d4ad49fcb34919b0cda4a9a9b4e6f1bc4f2b.tar.gz
opencode-e3b4d4ad49fcb34919b0cda4a9a9b4e6f1bc4f2b.zip
feat(bedrock): config options and authentication precedence (#6377)
-rw-r--r--packages/opencode/src/cli/cmd/auth.ts6
-rw-r--r--packages/opencode/src/provider/provider.ts42
-rw-r--r--packages/opencode/test/preload.ts2
-rw-r--r--packages/opencode/test/provider/amazon-bedrock.test.ts173
-rw-r--r--packages/web/src/content/docs/config.mdx35
-rw-r--r--packages/web/src/content/docs/providers.mdx93
6 files changed, 226 insertions, 125 deletions
diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts
index f200ec4fe..adbd3f45a 100644
--- a/packages/opencode/src/cli/cmd/auth.ts
+++ b/packages/opencode/src/cli/cmd/auth.ts
@@ -335,7 +335,11 @@ export const AuthLoginCommand = cmd({
if (provider === "amazon-bedrock") {
prompts.log.info(
- "Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
+ "Amazon Bedrock authentication priority:\n" +
+ " 1. Bearer token (AWS_BEARER_TOKEN_BEDROCK or /connect)\n" +
+ " 2. AWS credential chain (profile, access keys, IAM roles)\n\n" +
+ "Configure via opencode.json options (profile, region, endpoint) or\n" +
+ "AWS environment variables (AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID).",
)
prompts.outro("Done")
return
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 9967edec5..606714f08 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -15,7 +15,7 @@ import { Flag } from "../flag/flag"
import { iife } from "@/util/iife"
// Direct imports for bundled providers
-import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"
+import { createAmazonBedrock, type AmazonBedrockProviderSettings } from "@ai-sdk/amazon-bedrock"
import { createAnthropic } from "@ai-sdk/anthropic"
import { createAzure } from "@ai-sdk/azure"
import { createGoogleGenerativeAI } from "@ai-sdk/google"
@@ -168,10 +168,22 @@ export namespace Provider {
}
},
"amazon-bedrock": async () => {
+ const config = await Config.get()
+ const providerConfig = config.provider?.["amazon-bedrock"]
+
const auth = await Auth.get("amazon-bedrock")
- const awsProfile = Env.get("AWS_PROFILE")
+
+ // Region precedence: 1) config file, 2) env var, 3) default
+ const configRegion = providerConfig?.options?.region
+ const envRegion = Env.get("AWS_REGION")
+ const defaultRegion = configRegion ?? envRegion ?? "us-east-1"
+
+ // Profile: config file takes precedence over env var
+ const configProfile = providerConfig?.options?.profile
+ const envProfile = Env.get("AWS_PROFILE")
+ const profile = configProfile ?? envProfile
+
const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID")
- const awsRegion = Env.get("AWS_REGION")
const awsBearerToken = iife(() => {
const envToken = Env.get("AWS_BEARER_TOKEN_BEDROCK")
@@ -183,17 +195,27 @@ export namespace Provider {
return undefined
})
- if (!awsProfile && !awsAccessKeyId && !awsBearerToken) return { autoload: false }
-
- const defaultRegion = awsRegion ?? "us-east-1"
+ if (!profile && !awsAccessKeyId && !awsBearerToken) return { autoload: false }
const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
+
+ // Build credential provider options (only pass profile if specified)
+ const credentialProviderOptions = profile ? { profile } : {}
+
+ const providerOptions: AmazonBedrockProviderSettings = {
+ region: defaultRegion,
+ credentialProvider: fromNodeProviderChain(credentialProviderOptions),
+ }
+
+ // Add custom endpoint if specified (endpoint takes precedence over baseURL)
+ const endpoint = providerConfig?.options?.endpoint ?? providerConfig?.options?.baseURL
+ if (endpoint) {
+ providerOptions.baseURL = endpoint
+ }
+
return {
autoload: true,
- options: {
- region: defaultRegion,
- credentialProvider: fromNodeProviderChain(),
- },
+ options: providerOptions,
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
// Skip region prefixing if model already has global prefix
if (modelID.startsWith("global.")) {
diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts
index 76d1329f4..35b0b6c76 100644
--- a/packages/opencode/test/preload.ts
+++ b/packages/opencode/test/preload.ts
@@ -42,6 +42,8 @@ delete process.env["GOOGLE_GENERATIVE_AI_API_KEY"]
delete process.env["AZURE_OPENAI_API_KEY"]
delete process.env["AWS_ACCESS_KEY_ID"]
delete process.env["AWS_PROFILE"]
+delete process.env["AWS_REGION"]
+delete process.env["AWS_BEARER_TOKEN_BEDROCK"]
delete process.env["OPENROUTER_API_KEY"]
delete process.env["GROQ_API_KEY"]
delete process.env["MISTRAL_API_KEY"]
diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts
index 30cd2d0b6..d10e85139 100644
--- a/packages/opencode/test/provider/amazon-bedrock.test.ts
+++ b/packages/opencode/test/provider/amazon-bedrock.test.ts
@@ -1,11 +1,40 @@
-import { test, expect } from "bun:test"
+import { test, expect, mock } from "bun:test"
import path from "path"
-import { tmpdir } from "../fixture/fixture"
-import { Instance } from "../../src/project/instance"
-import { Provider } from "../../src/provider/provider"
-import { Env } from "../../src/env"
-import { Auth } from "../../src/auth"
-import { Global } from "../../src/global"
+
+// === Mocks ===
+// These mocks are required because Provider.list() triggers:
+// 1. BunProc.install("@aws-sdk/credential-providers") - in bedrock custom loader
+// 2. Plugin.list() which calls BunProc.install() for default plugins
+// Without mocks, these would attempt real package installations that timeout in tests.
+
+mock.module("../../src/bun/index", () => ({
+ BunProc: {
+ install: async (pkg: string) => pkg,
+ run: async () => {
+ throw new Error("BunProc.run should not be called in tests")
+ },
+ which: () => process.execPath,
+ InstallFailedError: class extends Error {},
+ },
+}))
+
+mock.module("@aws-sdk/credential-providers", () => ({
+ fromNodeProviderChain: () => async () => ({
+ accessKeyId: "mock-access-key-id",
+ secretAccessKey: "mock-secret-access-key",
+ }),
+}))
+
+const mockPlugin = () => ({})
+mock.module("opencode-copilot-auth", () => ({ default: mockPlugin }))
+mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin }))
+
+// Import after mocks are set up
+const { tmpdir } = await import("../fixture/fixture")
+const { Instance } = await import("../../src/project/instance")
+const { Provider } = await import("../../src/provider/provider")
+const { Env } = await import("../../src/env")
+const { Global } = await import("../../src/global")
test("Bedrock: config region takes precedence over AWS_REGION env var", async () => {
await using tmp = await tmpdir({
@@ -34,13 +63,12 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async ()
fn: async () => {
const providers = await Provider.list()
expect(providers["amazon-bedrock"]).toBeDefined()
- // Region from config should be used (not env var)
expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
},
})
})
-test("Bedrock: falls back to AWS_REGION env var when no config", async () => {
+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(
@@ -65,34 +93,7 @@ test("Bedrock: falls back to AWS_REGION env var when no config", async () => {
})
})
-test("Bedrock: without explicit region config, uses AWS_REGION env or defaults", 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_PROFILE", "default")
- // AWS_REGION might be set in the environment, use that or default
- },
- fn: async () => {
- const providers = await Provider.list()
- expect(providers["amazon-bedrock"]).toBeDefined()
- // Should have some region set (either from env or default)
- expect(providers["amazon-bedrock"].options?.region).toBeDefined()
- expect(typeof providers["amazon-bedrock"].options?.region).toBe("string")
- },
- })
-})
-
-test("Bedrock: uses config region in provider options", async () => {
+test("Bedrock: loads when bearer token from auth.json is present", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
@@ -102,7 +103,7 @@ test("Bedrock: uses config region in provider options", async () => {
provider: {
"amazon-bedrock": {
options: {
- region: "eu-north-1",
+ region: "eu-west-1",
},
},
},
@@ -110,54 +111,35 @@ test("Bedrock: uses config region in provider options", async () => {
)
},
})
+
+ const authPath = path.join(Global.Path.data, "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", "default")
+ Env.set("AWS_PROFILE", "")
+ Env.set("AWS_ACCESS_KEY_ID", "")
+ Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
},
fn: async () => {
const providers = await Provider.list()
- const bedrockProvider = providers["amazon-bedrock"]
- expect(bedrockProvider).toBeDefined()
- expect(bedrockProvider.options?.region).toBe("eu-north-1")
+ expect(providers["amazon-bedrock"]).toBeDefined()
+ expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1")
},
})
})
-test("Bedrock: respects config region for different instances", async () => {
- // First instance with EU config
- await using tmp1 = 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: tmp1.path,
- init: async () => {
- Env.set("AWS_PROFILE", "default")
- Env.set("AWS_REGION", "us-east-1")
- },
- fn: async () => {
- const providers1 = await Provider.list()
- expect(providers1["amazon-bedrock"].options?.region).toBe("eu-west-1")
- },
- })
-
- // Second instance with US config
- await using tmp2 = await tmpdir({
+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"),
@@ -166,7 +148,8 @@ test("Bedrock: respects config region for different instances", async () => {
provider: {
"amazon-bedrock": {
options: {
- region: "us-west-2",
+ profile: "my-custom-profile",
+ region: "us-east-1",
},
},
},
@@ -174,21 +157,21 @@ test("Bedrock: respects config region for different instances", async () => {
)
},
})
-
await Instance.provide({
- directory: tmp2.path,
+ directory: tmp.path,
init: async () => {
Env.set("AWS_PROFILE", "default")
- Env.set("AWS_REGION", "eu-west-1")
+ Env.set("AWS_ACCESS_KEY_ID", "test-key-id")
},
fn: async () => {
- const providers2 = await Provider.list()
- expect(providers2["amazon-bedrock"].options?.region).toBe("us-west-2")
+ const providers = await Provider.list()
+ expect(providers["amazon-bedrock"]).toBeDefined()
+ expect(providers["amazon-bedrock"].options?.region).toBe("us-east-1")
},
})
})
-test("Bedrock: loads when bearer token from auth.json is present", async () => {
+test("Bedrock: includes custom endpoint in options when specified", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
@@ -198,7 +181,7 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
provider: {
"amazon-bedrock": {
options: {
- region: "eu-west-1",
+ endpoint: "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com",
},
},
},
@@ -206,31 +189,17 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
)
},
})
-
- // Setup auth.json with bearer token for amazon-bedrock
- const authPath = path.join(Global.Path.data, "auth.json")
- await Bun.write(
- authPath,
- JSON.stringify({
- "amazon-bedrock": {
- type: "api",
- key: "test-bearer-token",
- },
- }),
- )
-
await Instance.provide({
directory: tmp.path,
init: async () => {
- // Clear env vars so only auth.json should trigger autoload
- Env.set("AWS_PROFILE", "")
- Env.set("AWS_ACCESS_KEY_ID", "")
- Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
+ 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")
+ expect(providers["amazon-bedrock"].options?.endpoint).toBe(
+ "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com",
+ )
},
})
})
diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx
index 24b822cc4..d9076e13a 100644
--- a/packages/web/src/content/docs/config.mdx
+++ b/packages/web/src/content/docs/config.mdx
@@ -205,6 +205,41 @@ You can also configure [local models](/docs/models#local). [Learn more](/docs/mo
---
+#### Provider-Specific Options
+
+Some providers support additional configuration options beyond the generic `timeout` and `apiKey` settings.
+
+##### Amazon Bedrock
+
+Amazon Bedrock supports AWS-specific configuration:
+
+```json title="opencode.json"
+{
+ "$schema": "https://opencode.ai/config.json",
+ "provider": {
+ "amazon-bedrock": {
+ "options": {
+ "region": "us-east-1",
+ "profile": "my-aws-profile",
+ "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com"
+ }
+ }
+ }
+}
+```
+
+- `region` - AWS region for Bedrock (defaults to `AWS_REGION` env var or `us-east-1`)
+- `profile` - AWS named profile from `~/.aws/credentials` (defaults to `AWS_PROFILE` env var)
+- `endpoint` - Custom endpoint URL for VPC endpoints. This is an alias for the generic `baseURL` option using AWS-specific terminology. If both are specified, `endpoint` takes precedence.
+
+:::note
+Bearer tokens (`AWS_BEARER_TOKEN_BEDROCK` or `/connect`) take precedence over profile-based authentication. See [authentication precedence](/docs/providers#authentication-precedence) for details.
+:::
+
+[Learn more about Amazon Bedrock configuration](/docs/providers#amazon-bedrock).
+
+---
+
### Themes
You can configure the theme you want to use in your OpenCode config through the `theme` option.
diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx
index e59fbc819..0e4539e12 100644
--- a/packages/web/src/content/docs/providers.mdx
+++ b/packages/web/src/content/docs/providers.mdx
@@ -107,27 +107,96 @@ To use Amazon Bedrock with OpenCode:
You need to have access to the model you want in Amazon Bedrock.
:::
-1. You'll need either to set one of the following environment variables:
- - `AWS_ACCESS_KEY_ID`: You can get this by creating an IAM user and generating
- an access key for it.
- - `AWS_PROFILE`: First login through AWS IAM Identity Center (or AWS SSO) using
- `aws sso login`. Then get the name of the profile you want to use.
- - `AWS_BEARER_TOKEN_BEDROCK`: You can generate a long-term API key from the
- Amazon Bedrock console.
+2. **Configure authentication** using one of the following methods:
- Once you have one of the above, set it while running opencode.
+ #### Environment Variables (Quick Start)
+
+ Set one of these environment variables while running opencode:
```bash
- AWS_ACCESS_KEY_ID=XXX opencode
+ # Option 1: Using AWS access keys
+ AWS_ACCESS_KEY_ID=XXX AWS_SECRET_ACCESS_KEY=YYY opencode
+
+ # Option 2: Using named AWS profile
+ AWS_PROFILE=my-profile opencode
+
+ # Option 3: Using Bedrock bearer token
+ AWS_BEARER_TOKEN_BEDROCK=XXX opencode
```
- Or add it to your bash profile.
+ Or add them to your bash profile:
```bash title="~/.bash_profile"
- export AWS_ACCESS_KEY_ID=XXX
+ export AWS_PROFILE=my-dev-profile
+ export AWS_REGION=us-east-1
+ ```
+
+ #### Configuration File (Recommended)
+
+ For project-specific or persistent configuration, use `opencode.json`:
+
+ ```json title="opencode.json"
+ {
+ "$schema": "https://opencode.ai/config.json",
+ "provider": {
+ "amazon-bedrock": {
+ "options": {
+ "region": "us-east-1",
+ "profile": "my-aws-profile"
+ }
+ }
+ }
+ }
```
-1. Run the `/models` command to select the model you want.
+ **Available options:**
+ - `region` - AWS region (e.g., `us-east-1`, `eu-west-1`)
+ - `profile` - AWS named profile from `~/.aws/credentials`
+ - `endpoint` - Custom endpoint URL for VPC endpoints (alias for generic `baseURL` option)
+
+ :::tip
+ Configuration file options take precedence over environment variables.
+ :::
+
+ #### Advanced: VPC Endpoints
+
+ If you're using VPC endpoints for Bedrock:
+
+ ```json title="opencode.json"
+ {
+ "$schema": "https://opencode.ai/config.json",
+ "provider": {
+ "amazon-bedrock": {
+ "options": {
+ "region": "us-east-1",
+ "profile": "production",
+ "endpoint": "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com"
+ }
+ }
+ }
+ }
+ ```
+
+ :::note
+ The `endpoint` option is an alias for the generic `baseURL` option, using AWS-specific terminology. If both `endpoint` and `baseURL` are specified, `endpoint` takes precedence.
+ :::
+
+ #### Authentication Methods
+ - **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**: Create an IAM user and generate access keys in the AWS Console
+ - **`AWS_PROFILE`**: Use named profiles from `~/.aws/credentials`. First configure with `aws configure --profile my-profile` or `aws sso login`
+ - **`AWS_BEARER_TOKEN_BEDROCK`**: Generate long-term API keys from the Amazon Bedrock console
+
+ #### Authentication Precedence
+
+ Amazon Bedrock uses the following authentication priority:
+ 1. **Bearer Token** - `AWS_BEARER_TOKEN_BEDROCK` environment variable or token from `/connect` command
+ 2. **AWS Credential Chain** - Profile, access keys, shared credentials, IAM roles, instance metadata
+
+ :::note
+ When a bearer token is set (via `/connect` or `AWS_BEARER_TOKEN_BEDROCK`), it takes precedence over all AWS credential methods including configured profiles.
+ :::
+
+3. Run the `/models` command to select the model you want.
```txt
/models