mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 04:27:20 +03:00
feat(core): add AWS profile setup for Bedrock (#53626)
This commit is contained in:
committed by
GitHub
Unverified
parent
554e7c2341
commit
56be008756
@@ -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,
|
||||
|
||||
@@ -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"] },
|
||||
])
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user