diff --git a/src/services/api/client.ts b/src/services/api/client.ts index d0720df..bf4b262 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -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 }) } }