feat: compute and inject cch hash in fetch wrapper

The buildFetch wrapper now intercepts requests to /v1/messages that
contain the cch=00000 placeholder, computes xxHash64 over the serialized
body, and replaces the placeholder with the 5-char hex digest before
sending. This replicates the request signing that was previously done
in Bun's native HTTP stack.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
paoloanznandClaude committed 2026-04-01 15:27:09 +02:00
1 parent ce8298b6db
commit 7631c6288e
1 file changed
+20 -5
+20 -5
View File
@@ -1,5 +1,10 @@
import Anthropic, { type ClientOptions } from '@anthropic-ai/sdk'
import { randomUUID } from 'crypto'
import {
computeCch,
hasCchPlaceholder,
replaceCchPlaceholder,
} from 'src/utils/cch.js'
import type { GoogleAuth } from 'google-auth-library'
import {
checkAndRefreshOAuthTokenIfNeeded,
@@ -383,15 +388,14 @@ function buildFetch(
// and unknown headers risk rejection by strict proxies (inc-4029 class).
const injectClientRequestId =
getAPIProvider() === 'firstParty' && isFirstPartyAnthropicBaseUrl()
return (input, init) => {
return async (input, init) => {
// eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins
const headers = new Headers(init?.headers)
// Generate a client-side request ID so timeouts (which return no server
// request ID) can still be correlated with server logs by the API team.
// Callers that want to track the ID themselves can pre-set the header.
if (injectClientRequestId && !headers.has(CLIENT_REQUEST_ID_HEADER)) {
headers.set(CLIENT_REQUEST_ID_HEADER, randomUUID())
}
let body = init?.body
try {
// eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins
const url = input instanceof Request ? input.url : String(input)
@@ -399,9 +403,20 @@ function buildFetch(
logForDebugging(
`[API REQUEST] ${new URL(url).pathname}${id ? ` ${CLIENT_REQUEST_ID_HEADER}=${id}` : ''} source=${source ?? 'unknown'}`,
)
if (
url.includes('/v1/messages') &&
headers.has('anthropic-version') &&
typeof body === 'string' &&
hasCchPlaceholder(body)
) {
const cch = await computeCch(body)
body = replaceCchPlaceholder(body, cch)
logForDebugging(`[CCH] signed request cch=${cch}`)
}
} catch {
// never let logging crash the fetch
}
return inner(input, { ...init, headers })
return inner(input, { ...init, headers, body })
}
}