feat(bedrock): add Claude Code compatibility transformations for Bedrock accounts

Add channel-level Bedrock CC compatibility toggle (similar to web_search_emulation)
that fixes 4 types of Bedrock 400 errors seen with Claude Code:

1. thinking.type "enabled" → "adaptive" for Opus 4.7+ (only supports adaptive)
2. Add default budget_tokens when missing for older models
3. Replace illegal characters in tool_use IDs to match Bedrock's ^[a-zA-Z0-9_-]+$ pattern
4. anthropic_version / invalid beta flag (already handled elsewhere)

Transformations run in Forward() before any forwarding path, so both native Bedrock
accounts and apikey passthrough accounts pointing to Bedrock relays benefit.

Includes channel-level toggle UI and unit tests.
This commit is contained in:
erio
2026-05-20 21:47:38 +08:00
parent 73b43bbb8a
commit 4fd21994c5
7 changed files with 399 additions and 3 deletions
+33
View File
@@ -354,6 +354,21 @@
</div>
</div>
<!-- Bedrock CC Compatibility (Anthropic only) -->
<div v-if="section.platform === 'anthropic'" class="border-t border-gray-200 pt-3 dark:border-dark-600">
<div class="flex items-center justify-between gap-4">
<div>
<label class="text-xs font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.channels.form.bedrockCCCompat') }}
</label>
<p class="mt-0.5 text-[11px] text-amber-600 dark:text-amber-400">
{{ t('admin.channels.form.bedrockCCCompatHint') }}
</p>
</div>
<Toggle v-model="section.bedrock_cc_compat" />
</div>
</div>
<!-- Model Mapping -->
<div>
<div class="mb-1 flex items-center justify-between">
@@ -669,6 +684,7 @@ interface PlatformSection {
model_pricing: PricingFormEntry[]
web_search_emulation: boolean
codex_image_generation_bridge: boolean
bedrock_cc_compat: boolean
account_stats_pricing_rules: FormPricingRule[]
}
@@ -765,6 +781,7 @@ function addPlatformSection(platform: GroupPlatform) {
model_pricing: [],
web_search_emulation: false,
codex_image_generation_bridge: false,
bedrock_cc_compat: false,
account_stats_pricing_rules: [],
})
}
@@ -1125,6 +1142,19 @@ function formToAPI(): { group_ids: number[], model_pricing: ChannelModelPricing[
delete featuresConfig.codex_image_generation_bridge
}
const bedrockCCCompat: Record<string, boolean> = {}
for (const section of form.platforms) {
if (!section.enabled) continue
if (section.platform === 'anthropic') {
bedrockCCCompat[section.platform] = !!section.bedrock_cc_compat
}
}
if (Object.keys(bedrockCCCompat).length > 0) {
featuresConfig.bedrock_cc_compat = bedrockCCCompat
} else {
delete featuresConfig.bedrock_cc_compat
}
return { group_ids, model_pricing, model_mapping, features_config: featuresConfig }
}
@@ -1175,6 +1205,8 @@ function apiToForm(channel: Channel): PlatformSection[] {
const webSearchEnabled = wsEmulation?.[platform] === true
const codexImageGenerationBridge = fc?.codex_image_generation_bridge as Record<string, boolean> | undefined
const codexImageGenerationBridgeEnabled = codexImageGenerationBridge?.[platform] === true
const bedrockCCCompat = fc?.bedrock_cc_compat as Record<string, boolean> | undefined
const bedrockCCCompatEnabled = bedrockCCCompat?.[platform] === true
sections.push({
platform,
@@ -1185,6 +1217,7 @@ function apiToForm(channel: Channel): PlatformSection[] {
model_pricing: pricing,
web_search_emulation: webSearchEnabled,
codex_image_generation_bridge: codexImageGenerationBridgeEnabled,
bedrock_cc_compat: bedrockCCCompatEnabled,
account_stats_pricing_rules: [],
})
}