mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
feat: 添加 Bedrock API 客户端及 API 层增强
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
65
src/services/api/bedrockClient.ts
Normal file
65
src/services/api/bedrockClient.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk'
|
||||
|
||||
/**
|
||||
* Extends AnthropicBedrock to work around an upstream bug where the SDK
|
||||
* re-plants the `anthropic-beta` HTTP header value into the request body
|
||||
* as `anthropic_beta`. Bedrock's Opus 4.7 endpoint rejects any request with
|
||||
* `anthropic_beta` in the body with a 400 "invalid beta flag" error.
|
||||
*
|
||||
* Source of the bug (SDK 0.26.4, still present through 0.28.1):
|
||||
* node_modules/@anthropic-ai/bedrock-sdk/client.js lines 122-127
|
||||
* (TS source: packages/bedrock-sdk/src/client.ts lines 193-198)
|
||||
*
|
||||
* Related upstream issue: anthropics/claude-code#49238 (opened 2026-04-16).
|
||||
*
|
||||
* Fix strategy: let super.buildRequest do its work, then strip
|
||||
* `body.anthropic_beta` from the resulting Request before the SDK computes
|
||||
* the AWS SigV4 signature (signing happens downstream of buildRequest, so
|
||||
* the signature hashes the cleaned body — no 403 risk). The `anthropic-beta`
|
||||
* HTTP header remains intact (base SDK placed it there from the `betas:`
|
||||
* parameter), so beta flags still reach the API the way Bedrock accepts them.
|
||||
*
|
||||
* When upstream ships a fix, verify the probe in scripts/probe-bedrock-beta-fix.ts
|
||||
* shows "bug reproduced: false", then delete this class and change
|
||||
* `services/api/client.ts` to instantiate `AnthropicBedrock` directly.
|
||||
*/
|
||||
type BuildRequestArg = Parameters<AnthropicBedrock['buildRequest']>[0]
|
||||
type BuildRequestRet = Awaited<ReturnType<AnthropicBedrock['buildRequest']>>
|
||||
|
||||
export class BedrockClient extends AnthropicBedrock {
|
||||
async buildRequest(options: BuildRequestArg): Promise<BuildRequestRet> {
|
||||
const req = await super.buildRequest(options)
|
||||
|
||||
const inner = (
|
||||
req as unknown as { req?: { body?: unknown; headers?: unknown } }
|
||||
)?.req
|
||||
if (!inner || typeof inner.body !== 'string' || inner.body.length === 0) {
|
||||
return req
|
||||
}
|
||||
|
||||
let parsed: Record<string, unknown>
|
||||
try {
|
||||
parsed = JSON.parse(inner.body) as Record<string, unknown>
|
||||
} catch {
|
||||
return req
|
||||
}
|
||||
if (!('anthropic_beta' in parsed)) {
|
||||
return req
|
||||
}
|
||||
|
||||
delete parsed.anthropic_beta
|
||||
const cleanedBody = JSON.stringify(parsed)
|
||||
inner.body = cleanedBody
|
||||
|
||||
const byteLen = String(new TextEncoder().encode(cleanedBody).length)
|
||||
const h = inner.headers
|
||||
if (typeof Headers !== 'undefined' && h instanceof Headers) {
|
||||
if (h.has('content-length')) h.set('content-length', byteLen)
|
||||
} else if (h && typeof h === 'object') {
|
||||
const asDict = h as Record<string, string>
|
||||
if ('content-length' in asDict) asDict['content-length'] = byteLen
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user