feat(core): add AWS profile setup for Bedrock (#53626)

This commit is contained in:
Aiden Cline
2026-10-07 14:05:08 -05:00
committed by GitHub
Unverified
parent 554e7c2341
commit 56be008756
4 changed files with 64 additions and 4 deletions
@@ -37,7 +37,8 @@ export const defaultChain = (options: DefaultChainOptions): Effect.Effect<Creden
Effect.tryPromise({
try: async () => {
const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers")
const identity = await fromNodeProviderChain(options.profile === undefined ? {} : { profile: options.profile })()
// ignoreCache re-reads shared config and SSO token files, so `aws sso login` takes effect without a restart.
const identity = await fromNodeProviderChain({ ignoreCache: true, profile: options.profile })()
return {
region: options.region,
accessKeyId: identity.accessKeyId,
+4 -1
View File
@@ -300,7 +300,10 @@ function unresolvedProviderVariables(model: RuntimeInfo, baseURL: string) {
}
const nativeCredentialSettings = (specifier: string, credential: Credential.Value | undefined) => {
if (!credential || credential.type === "external") return {}
if (!credential) return {}
// The saved profile reaches the package through metadata; SigV4 keeps an ambient bearer token from taking over.
if (credential.type === "external")
return specifier.startsWith("@opencode/ai/providers/amazon-bedrock") ? { auth: "sigv4" } : {}
if (credential.type === "key") return { apiKey: credential.key }
if (specifier === "@opencode/ai/providers/anthropic" || specifier === "@opencode/ai/providers/anthropic-compatible")
return { authToken: credential.access }
@@ -1,5 +1,8 @@
import { Effect } from "effect"
import path from "node:path"
import { define } from "@opencode/plugin/effect/plugin"
import { FSUtil } from "@opencode/util/fs-util"
import { Global } from "@opencode/util/global"
import { Provider } from "../../provider.js"
// Ambient inputs the AWS default credential chain can turn into credentials
@@ -19,6 +22,32 @@ const isBedrock = (item: { readonly package: string }) =>
export const AmazonBedrockPlugin = define({
id: "opencode.provider.amazon.bedrock",
effect: Effect.fn(function* (ctx) {
const fs = yield* FSUtil.Service
const paths = [
process.env.AWS_CONFIG_FILE ?? path.join(Global.Path.home, ".aws", "config"),
process.env.AWS_SHARED_CREDENTIALS_FILE ?? path.join(Global.Path.home, ".aws", "credentials"),
]
const files = yield* Effect.all(
paths.map((file) => fs.readFileStringSafe(file).pipe(Effect.orElseSucceed(() => undefined))),
)
// Discover names only. Resolving every profile here could run credential helpers or contact AWS.
const profiles = Array.from(
new Set(
files.flatMap((content, index) =>
Array.from((content ?? "").matchAll(/^\s*\[([^\]\r\n]+)\]/gm)).flatMap((match) => {
const section = match[1].trim()
// The credentials file uses bare names; the config file prefixes all but "default" with "profile ".
if (index === 1 || section === "default") return [section]
return section.startsWith("profile ") ? [section.slice(8).trim()] : []
}),
),
),
)
.filter(Boolean)
.toSorted()
const sources = paths
.map((file) => (file.startsWith(Global.Path.home + path.sep) ? `~${file.slice(Global.Path.home.length)}` : file))
.join(" and ")
yield* ctx.integration.transform((editor) => {
// models.dev advertises AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
// AWS_REGION alongside the bearer token. Only the bearer token is a key;
@@ -27,6 +56,34 @@ export const AmazonBedrockPlugin = define({
integrationID: Provider.ID.amazonBedrock,
method: { type: "env", names: ["AWS_BEARER_TOKEN_BEDROCK"] },
})
editor.method.update({
integrationID: Provider.ID.amazonBedrock,
method: { type: "key", label: "Bedrock API key" },
})
editor.method.update({
integrationID: Provider.ID.amazonBedrock,
method: {
id: "aws-profile",
type: "external",
label: "AWS profile (SSO or named profile)",
form: [
{
key: "profile",
type: "string",
title: "AWS profile",
description: profiles.length
? `Found ${profiles.length} profile${profiles.length === 1 ? "" : "s"} in ${sources} on the server.`
: `No AWS profiles found in ${sources} on the server.`,
required: true,
minLength: 1,
pattern: "\\S",
placeholder: "Profile name",
custom: true,
options: profiles.map((profile) => ({ value: profile, label: profile })),
},
],
},
})
})
yield* ctx.provider.transform((evt) => {
for (const item of evt.list()) {
@@ -104,8 +104,7 @@ describe("AmazonBedrockPlugin", () => {
})
})
yield* addPlugin()
expect((yield* integrations.get(integrationID))?.methods).toEqual([
{ type: "key" },
expect((yield* integrations.get(integrationID))?.methods.filter((method) => method.type === "env")).toEqual([
{ type: "env", names: ["AWS_BEARER_TOKEN_BEDROCK"] },
])
}),