From 8ea9638b9d57a20aa6c35a2ca12d285783e40ac6 Mon Sep 17 00:00:00 2001 From: Keith Yu <103325445+keithyt06@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:11:18 +0800 Subject: [PATCH] feat: Add AWS Bedrock Provider Support (AKSK & API Key) (#1047) * Add AWS Bedrock provider integration design document Co-Authored-By: Claude Opus 4.6 * Add AWS Bedrock provider implementation plan Co-Authored-By: Claude Opus 4.6 * Update implementation plan: add OpenCode Bedrock support Co-Authored-By: Claude Opus 4.6 * feat: add cloud_provider category to ProviderCategory type Co-Authored-By: Claude Opus 4.6 * feat: add AWS Bedrock (AKSK) Claude Code provider preset with tests Co-Authored-By: Claude Opus 4.6 * feat: add AWS Bedrock (API Key) Claude Code provider preset with tests Co-Authored-By: Claude Opus 4.6 * feat: add AWS Bedrock OpenCode provider preset with @ai-sdk/amazon-bedrock Co-Authored-By: Claude Opus 4.6 * docs: add AWS Bedrock provider feature summary for PR Co-Authored-By: Claude Opus 4.6 * chore: remove internal planning documents Co-Authored-By: Claude Opus 4.6 * docs: add AWS Bedrock support to README (EN/ZH/JA) Co-Authored-By: Claude Opus 4.6 * Add AWS Bedrock UI merge design document Co-Authored-By: Claude Opus 4.6 * Add AWS Bedrock UI merge implementation plan Co-Authored-By: Claude Opus 4.6 * feat: skip optional template values in validation Co-Authored-By: Claude Opus 4.6 * feat: support isSecret template fields and hide base URL for Bedrock Co-Authored-By: Claude Opus 4.6 * feat: add Bedrock validation, cleanup, and isBedrock prop in ProviderForm Co-Authored-By: Claude Opus 4.6 * feat: extend TemplateValueConfig and merge Bedrock presets Co-Authored-By: Claude Opus 4.6 * fix: mask Bedrock API Key as secret and support GovCloud regions - Add isSecret: true to BEDROCK_API_KEY template value - Update region regex to support multi-segment regions (us-gov-west-1) Co-Authored-By: Claude Opus 4.6 * style: replace AWS icon with updated logo Co-Authored-By: Claude Opus 4.6 * style: replace AWS icon with updated logo Co-Authored-By: Claude Opus 4.6 * style: replace AWS icon with new PNG image Co-Authored-By: Claude Opus 4.6 * fix: address code review findings - Fix AWS icon: use SVG with embedded instead of raw tag - Hide duplicate ApiKeySection for Bedrock (auth via template fields only) - Guard settingsConfig cleanup against unresolved template placeholders Co-Authored-By: Claude Opus 4.6 * chore: remove planning documents Co-Authored-By: Claude Opus 4.6 * refactor: address PR review - split Bedrock into two presets, restore SVG icon Based on maintainer review feedback on PR #1047: 1. Split merged "AWS Bedrock" back into two separate presets: - "AWS Bedrock (AKSK)": uses AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - "AWS Bedrock (API Key)": uses top-level apiKey field via standard UI input 2. Restore aws.svg to pure vector SVG (was PNG-in-SVG) 3. Remove all Bedrock-specific logic from shared components: - Remove isBedrock prop from ClaudeFormFields - Remove Bedrock validation/cleanup blocks from ProviderForm - Remove optional/isSecret from TemplateValueConfig - Remove optional skip from useTemplateValues 4. Add cloud_provider category handling: - Skip API Key/Base URL required validation - Hide Speed Test and Base URL for cloud_provider - Hide API format selector for cloud_provider (always Anthropic) - Show API Key input only when config has apiKey field 5. Fix providerConfigUtils to support top-level apiKey: - getApiKeyFromConfig: check config.apiKey before env fields - setApiKeyInConfig: write to config.apiKey when present - hasApiKeyField: detect top-level apiKey property 6. Add OpenClaw Bedrock preset (bedrock-converse-stream protocol) 7. Update model IDs: - Sonnet: global.anthropic.claude-sonnet-4-6 - Opus: global.anthropic.claude-opus-4-6-v1 - Haiku: global.anthropic.claude-haiku-4-5-20251001-v1:0 Co-Authored-By: Claude Opus 4.6 * fix(test): align Bedrock API Key test assertions with preset implementation The API Key preset was refactored to use standard UI input (apiKey: "") instead of template variables, but the tests were not updated accordingly. --------- Co-authored-by: root Co-authored-by: Claude Opus 4.6 Co-authored-by: Jason --- README.md | 1 + README_JA.md | 1 + README_ZH.md | 1 + .../providers/forms/ClaudeFormFields.tsx | 4 +- .../providers/forms/ProviderForm.tsx | 22 +++-- src/config/claudeProviderPresets.ts | 69 ++++++++++++++ src/config/openclawProviderPresets.ts | 35 +++++++ src/config/opencodeProviderPresets.ts | 93 +++++++++++++++++++ src/types.ts | 1 + src/utils/providerConfigUtils.ts | 23 +++++ tests/config/claudeProviderPresets.test.ts | 78 ++++++++++++++++ tests/config/opencodeProviderPresets.test.ts | 68 ++++++++++++++ 12 files changed, 387 insertions(+), 9 deletions(-) create mode 100644 tests/config/claudeProviderPresets.test.ts create mode 100644 tests/config/opencodeProviderPresets.test.ts diff --git a/README.md b/README.md index c2a25fec0..44f4c54d4 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original pric **Core Capabilities** - **Provider Management**: One-click switching between Claude Code, Codex, and Gemini API configurations +- **AWS Bedrock Support**: Built-in AWS Bedrock provider presets with AKSK and API Key authentication, cross-region inference support (global/us/eu/apac), covering Claude Code and OpenCode - **Speed Testing**: Measure API endpoint latency with visual quality indicators - **Import/Export**: Backup and restore configs with auto-rotation (keep 10 most recent) - **i18n Support**: Complete Chinese/English localization (UI, errors, tray) diff --git a/README_JA.md b/README_JA.md index 51e53b6dd..2b69c1cef 100644 --- a/README_JA.md +++ b/README_JA.md @@ -156,6 +156,7 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% / **コア機能** - **プロバイダ管理**:Claude Code、Codex、Gemini の API 設定をワンクリックで切り替え +- **AWS Bedrock 対応**:AWS Bedrock プロバイダプリセットを内蔵、AKSK および API Key 認証に対応、クロスリージョン推論(global/us/eu/apac)をサポート、Claude Code と OpenCode に対応 - **速度テスト**:エンドポイント遅延を計測し、品質を可視化 - **インポート/エクスポート**:設定をバックアップ・復元(最新 10 件を自動ローテーション) - **多言語対応**:UI/エラー/トレイを含む中国語・英語・日本語ローカライズ diff --git a/README_ZH.md b/README_ZH.md index f791d8868..f7beadb3e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -157,6 +157,7 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更 **核心功能** - **供应商管理**:一键切换 Claude Code、Codex 与 Gemini 的 API 配置 +- **AWS Bedrock 支持**:内置 AWS Bedrock 供应商预设,支持 AKSK 和 API Key 两种认证方式,支持跨区域推理(global/us/eu/apac),覆盖 Claude Code 和 OpenCode - **速度测试**:测量 API 端点延迟,可视化连接质量指示器 - **导入导出**:备份和恢复配置,自动轮换(保留最近 10 个) - **国际化支持**:完整的中英文本地化(UI、错误、托盘) diff --git a/src/components/providers/forms/ClaudeFormFields.tsx b/src/components/providers/forms/ClaudeFormFields.tsx index 9dd750576..f9ac424c3 100644 --- a/src/components/providers/forms/ClaudeFormFields.tsx +++ b/src/components/providers/forms/ClaudeFormFields.tsx @@ -198,8 +198,8 @@ export function ClaudeFormFields({ /> )} - {/* API 格式选择(仅非官方供应商显示) */} - {shouldShowModelSelector && ( + {/* API 格式选择(仅非官方、非云服务商显示) */} + {shouldShowModelSelector && category !== "cloud_provider" && (
{t("providerForm.apiFormat", { defaultValue: "API 格式" })} diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 03f4da197..647e02f10 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -40,7 +40,10 @@ import { import { OpenCodeFormFields } from "./OpenCodeFormFields"; import { OpenClawFormFields } from "./OpenClawFormFields"; import type { UniversalProviderPreset } from "@/config/universalProviderPresets"; -import { applyTemplateValues } from "@/utils/providerConfigUtils"; +import { + applyTemplateValues, + hasApiKeyField, +} from "@/utils/providerConfigUtils"; import { mergeProviderMeta } from "@/utils/providerMetaUtils"; import { getCodexCustomTemplate } from "@/config/codexTemplates"; import CodexConfigEditor from "./CodexConfigEditor"; @@ -594,7 +597,8 @@ export function ProviderForm({ } // 非官方供应商必填校验:端点和 API Key - if (category !== "official") { + // cloud_provider(如 Bedrock)通过模板变量处理认证,跳过通用校验 + if (category !== "official" && category !== "cloud_provider") { if (appId === "claude") { if (!baseUrl.trim()) { toast.error( @@ -843,7 +847,8 @@ export function ProviderForm({ ); }, [groupedPresets]); - const shouldShowSpeedTest = category !== "official"; + const shouldShowSpeedTest = + category !== "official" && category !== "cloud_provider"; const { shouldShowApiKeyLink: shouldShowClaudeApiKeyLink, @@ -1233,10 +1238,13 @@ export function ProviderForm({ {appId === "claude" && ( { try { const config = JSON.parse(jsonString); + + // 优先检查顶层 apiKey 字段(用于 Bedrock API Key 等预设) + if ( + typeof config?.apiKey === "string" && + config.apiKey && + !config.apiKey.includes("${") + ) { + return config.apiKey; + } + const env = config?.env; if (!env) return ""; @@ -112,6 +122,12 @@ export const hasApiKeyField = ( ): boolean => { try { const config = JSON.parse(jsonString); + + // 检查顶层 apiKey 字段(用于 Bedrock API Key 等预设) + if (Object.prototype.hasOwnProperty.call(config, "apiKey")) { + return true; + } + const env = config?.env ?? {}; if (appType === "gemini") { @@ -144,6 +160,13 @@ export const setApiKeyInConfig = ( const { createIfMissing = false, appType, apiKeyField } = options; try { const config = JSON.parse(jsonString); + + // 优先检查顶层 apiKey 字段(用于 Bedrock API Key 等预设) + if (Object.prototype.hasOwnProperty.call(config, "apiKey")) { + config.apiKey = apiKey; + return JSON.stringify(config, null, 2); + } + if (!config.env) { if (!createIfMissing) return jsonString; config.env = {}; diff --git a/tests/config/claudeProviderPresets.test.ts b/tests/config/claudeProviderPresets.test.ts new file mode 100644 index 000000000..dc1f4397c --- /dev/null +++ b/tests/config/claudeProviderPresets.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "vitest"; +import { providerPresets } from "@/config/claudeProviderPresets"; + +describe("AWS Bedrock Provider Presets", () => { + const bedrockAksk = providerPresets.find( + (p) => p.name === "AWS Bedrock (AKSK)", + ); + + it("should include AWS Bedrock (AKSK) preset", () => { + expect(bedrockAksk).toBeDefined(); + }); + + it("AKSK preset should have required AWS env variables", () => { + const env = (bedrockAksk!.settingsConfig as any).env; + expect(env).toHaveProperty("AWS_ACCESS_KEY_ID"); + expect(env).toHaveProperty("AWS_SECRET_ACCESS_KEY"); + expect(env).toHaveProperty("AWS_REGION"); + expect(env).toHaveProperty("CLAUDE_CODE_USE_BEDROCK", "1"); + }); + + it("AKSK preset should have template values for AWS credentials", () => { + expect(bedrockAksk!.templateValues).toBeDefined(); + expect(bedrockAksk!.templateValues!.AWS_ACCESS_KEY_ID).toBeDefined(); + expect(bedrockAksk!.templateValues!.AWS_SECRET_ACCESS_KEY).toBeDefined(); + expect(bedrockAksk!.templateValues!.AWS_REGION).toBeDefined(); + expect(bedrockAksk!.templateValues!.AWS_REGION.editorValue).toBe( + "us-west-2", + ); + }); + + it("AKSK preset should have correct base URL template", () => { + const env = (bedrockAksk!.settingsConfig as any).env; + expect(env.ANTHROPIC_BASE_URL).toContain("bedrock-runtime"); + expect(env.ANTHROPIC_BASE_URL).toContain("${AWS_REGION}"); + }); + + it("AKSK preset should have cloud_provider category", () => { + expect(bedrockAksk!.category).toBe("cloud_provider"); + }); + + it("AKSK preset should have Bedrock model as default", () => { + const env = (bedrockAksk!.settingsConfig as any).env; + expect(env.ANTHROPIC_MODEL).toContain("anthropic.claude"); + }); + + const bedrockApiKey = providerPresets.find( + (p) => p.name === "AWS Bedrock (API Key)", + ); + + it("should include AWS Bedrock (API Key) preset", () => { + expect(bedrockApiKey).toBeDefined(); + }); + + it("API Key preset should have apiKey field and AWS env variables", () => { + const config = bedrockApiKey!.settingsConfig as any; + expect(config).toHaveProperty("apiKey", ""); + expect(config.env).toHaveProperty("AWS_REGION"); + expect(config.env).toHaveProperty("CLAUDE_CODE_USE_BEDROCK", "1"); + }); + + it("API Key preset should NOT have AKSK env variables", () => { + const env = (bedrockApiKey!.settingsConfig as any).env; + expect(env).not.toHaveProperty("AWS_ACCESS_KEY_ID"); + expect(env).not.toHaveProperty("AWS_SECRET_ACCESS_KEY"); + }); + + it("API Key preset should have template values for region only", () => { + expect(bedrockApiKey!.templateValues).toBeDefined(); + expect(bedrockApiKey!.templateValues!.AWS_REGION).toBeDefined(); + expect(bedrockApiKey!.templateValues!.AWS_REGION.editorValue).toBe( + "us-west-2", + ); + }); + + it("API Key preset should have cloud_provider category", () => { + expect(bedrockApiKey!.category).toBe("cloud_provider"); + }); +}); diff --git a/tests/config/opencodeProviderPresets.test.ts b/tests/config/opencodeProviderPresets.test.ts new file mode 100644 index 000000000..0a57869a0 --- /dev/null +++ b/tests/config/opencodeProviderPresets.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; +import { + opencodeProviderPresets, + opencodeNpmPackages, + OPENCODE_PRESET_MODEL_VARIANTS, +} from "@/config/opencodeProviderPresets"; + +describe("AWS Bedrock OpenCode Provider Presets", () => { + it("should include @ai-sdk/amazon-bedrock in npm packages", () => { + const bedrockPkg = opencodeNpmPackages.find( + (p) => p.value === "@ai-sdk/amazon-bedrock", + ); + expect(bedrockPkg).toBeDefined(); + expect(bedrockPkg!.label).toBe("Amazon Bedrock"); + }); + + it("should include Bedrock model variants", () => { + const variants = OPENCODE_PRESET_MODEL_VARIANTS["@ai-sdk/amazon-bedrock"]; + expect(variants).toBeDefined(); + expect(variants.length).toBeGreaterThan(0); + + const opusModel = variants.find((v) => + v.id.includes("anthropic.claude-opus-4-6"), + ); + expect(opusModel).toBeDefined(); + }); + + const bedrockPreset = opencodeProviderPresets.find( + (p) => p.name === "AWS Bedrock", + ); + + it("should include AWS Bedrock preset", () => { + expect(bedrockPreset).toBeDefined(); + }); + + it("Bedrock preset should use @ai-sdk/amazon-bedrock npm package", () => { + expect(bedrockPreset!.settingsConfig.npm).toBe( + "@ai-sdk/amazon-bedrock", + ); + }); + + it("Bedrock preset should have region in options", () => { + expect(bedrockPreset!.settingsConfig.options).toHaveProperty("region"); + }); + + it("Bedrock preset should have cloud_provider category", () => { + expect(bedrockPreset!.category).toBe("cloud_provider"); + }); + + it("Bedrock preset should have template values for AWS credentials", () => { + expect(bedrockPreset!.templateValues).toBeDefined(); + expect(bedrockPreset!.templateValues!.region).toBeDefined(); + expect(bedrockPreset!.templateValues!.region.editorValue).toBe( + "us-west-2", + ); + expect(bedrockPreset!.templateValues!.accessKeyId).toBeDefined(); + expect(bedrockPreset!.templateValues!.secretAccessKey).toBeDefined(); + }); + + it("Bedrock preset should include Claude models", () => { + const models = bedrockPreset!.settingsConfig.models; + expect(models).toBeDefined(); + const modelIds = Object.keys(models!); + expect( + modelIds.some((id) => id.includes("anthropic.claude")), + ).toBe(true); + }); +});