Skip to content

Commit 77a96e5

Browse files
lawrencecchenclaude
andcommitted
worker: stream proxied responses through, record transcripts off-path
The HTTP proxy awaited a full response.clone().arrayBuffer() (plus DO transcript RPCs) before returning, so clients got their first byte only when upstream finished: measured TTFB == total (3.0s) vs direct 1.0s TTFB on a 400-token stream, and claude CLI showed ttft at 85% of duration. Now the upstream body is tee'd: one branch returns immediately, the other drains eagerly at tee time and transcript recording (meta -> request -> response, same fields/sha256/order) runs in ctx.waitUntil. Rejection sidecars prevent stranded tee branches and unhandled rejections when a DO RPC fails or the client aborts mid-upload; recording failures never affect the client response. No-ctx fallback preserves awaited behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 357a703 commit 77a96e5

3 files changed

Lines changed: 439 additions & 30 deletions

File tree

cloudflare/packages/worker/src/index.ts

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ interface TenantAccountOutput {
108108
}
109109

110110
type UpstreamProvider = "claude" | "codex"
111+
type WaitUntil = (promise: Promise<unknown>) => void
111112

112113
const anthropicBootstrapModelQuotas: AccountModelQuotas = {
113114
opus: { remainingPercent: 100 },
@@ -2702,7 +2703,8 @@ const proxyUpstream = async (
27022703
env: Env,
27032704
actor: DurableObjectStub<SubrouterDurableObject>,
27042705
routeInput: RouteInput,
2705-
telemetry?: MutableRequestTelemetryContext
2706+
telemetry?: MutableRequestTelemetryContext,
2707+
waitUntil?: WaitUntil
27062708
): Promise<Response> => {
27072709
if (!routeInput.orgId) {
27082710
return json({ error: "tenant id required" }, { status: 401 })
@@ -2722,9 +2724,10 @@ const proxyUpstream = async (
27222724
defaultUpstreamForAccount(env, account)
27232725
)
27242726
const headers = setUpstreamAuthHeaders(request.headers, account)
2725-
await actor.recordTranscriptMeta({
2727+
const agentType = routeInput.agentType ?? "codex"
2728+
const transcriptMetaInput = {
27262729
orgId: routeInput.orgId,
2727-
agentType: routeInput.agentType ?? "codex",
2730+
agentType,
27282731
sessionId: routeInput.sessionId,
27292732
type: "subrouter_meta",
27302733
payload: {
@@ -2736,36 +2739,56 @@ const proxyUpstream = async (
27362739
upstream: upstreamURL.toString(),
27372740
headers: redactedHeaders(stripSubrouterHeaders(request.headers)),
27382741
},
2739-
})
2742+
} satisfies TranscriptEventInput
27402743
const init: RequestInit = {
27412744
method: request.method,
27422745
headers,
27432746
redirect: "manual",
27442747
}
2748+
let requestBody: Promise<ArrayBuffer> | undefined
27452749
if (request.method !== "GET" && request.method !== "HEAD") {
2750+
requestBody = request.clone().arrayBuffer()
2751+
void requestBody.catch(() => {})
2752+
init.body = request.body
2753+
}
2754+
const upstreamRequest = new Request(upstreamURL, init)
2755+
const response = await fetch(upstreamRequest)
2756+
if (telemetry) telemetry.upstreamStatus = response.status
2757+
const [clientBody, transcriptBody] = response.body
2758+
? response.body.tee()
2759+
: [null, null]
2760+
const transcriptBodyBuffer = transcriptBody
2761+
? new Response(transcriptBody).arrayBuffer()
2762+
: Promise.resolve(new ArrayBuffer(0))
2763+
void transcriptBodyBuffer.catch(() => {})
2764+
const recordTranscripts = async (): Promise<void> => {
2765+
await actor.recordTranscriptMeta(transcriptMetaInput)
2766+
if (requestBody) {
2767+
await recordTranscriptBodyFromArrayBuffer(actor, {
2768+
orgId: routeInput.orgId,
2769+
agentType,
2770+
sessionId: routeInput.sessionId,
2771+
eventType: "http_body",
2772+
direction: "client_to_upstream",
2773+
body: await requestBody,
2774+
})
2775+
}
27462776
await recordTranscriptBodyFromArrayBuffer(actor, {
27472777
orgId: routeInput.orgId,
2748-
agentType: routeInput.agentType ?? "codex",
2778+
agentType,
27492779
sessionId: routeInput.sessionId,
27502780
eventType: "http_body",
2751-
direction: "client_to_upstream",
2752-
body: await request.clone().arrayBuffer(),
2781+
direction: "upstream_to_client",
2782+
body: await transcriptBodyBuffer,
2783+
payload: { status: response.status },
27532784
})
2754-
init.body = request.body
27552785
}
2756-
const upstreamRequest = new Request(upstreamURL, init)
2757-
const response = await fetch(upstreamRequest)
2758-
if (telemetry) telemetry.upstreamStatus = response.status
2759-
await recordTranscriptBodyFromArrayBuffer(actor, {
2760-
orgId: routeInput.orgId,
2761-
agentType: routeInput.agentType ?? "codex",
2762-
sessionId: routeInput.sessionId,
2763-
eventType: "http_body",
2764-
direction: "upstream_to_client",
2765-
body: await response.clone().arrayBuffer(),
2766-
payload: { status: response.status },
2767-
})
2768-
return new Response(response.body, {
2786+
if (waitUntil) {
2787+
waitUntil(recordTranscripts().catch(() => {}))
2788+
} else {
2789+
await recordTranscripts()
2790+
}
2791+
return new Response(clientBody, {
27692792
status: response.status,
27702793
statusText: response.statusText,
27712794
headers: response.headers,
@@ -3203,7 +3226,8 @@ const escapeHTML = (value: string): string =>
32033226
const handleFetch = async (
32043227
request: Request,
32053228
env: Env,
3206-
telemetry?: MutableRequestTelemetryContext
3229+
telemetry?: MutableRequestTelemetryContext,
3230+
ctx?: ExecutionContext
32073231
): Promise<Response> => {
32083232
const url = new URL(request.url)
32093233

@@ -3561,28 +3585,37 @@ const handleFetch = async (
35613585
if (isWebSocketUpgrade(request)) {
35623586
return await proxyUpstreamWebSocket(request, env, actor, tenantRouteInput, telemetry)
35633587
}
3564-
return await proxyUpstream(request, env, actor, tenantRouteInput, telemetry)
3588+
return await proxyUpstream(
3589+
request,
3590+
env,
3591+
actor,
3592+
tenantRouteInput,
3593+
telemetry,
3594+
ctx?.waitUntil.bind(ctx)
3595+
)
35653596
} catch (error) {
35663597
if (telemetry) telemetry.errorType = errorTypeFor(error)
35673598
return errorJson(error, 503)
35683599
}
35693600
}
35703601

3602+
export const __test = { proxyUpstream }
3603+
35713604
export default {
35723605
async fetch(
35733606
request: Request,
35743607
env: Env,
35753608
ctx?: ExecutionContext
35763609
): Promise<Response> {
35773610
if (!isAxiomTelemetryConfigured(env)) {
3578-
return handleFetch(request, env)
3611+
return handleFetch(request, env, undefined, ctx)
35793612
}
35803613

35813614
const startTimeMs = Date.now()
35823615
const telemetry = createRequestTelemetryContext(request)
35833616
let response: Response
35843617
try {
3585-
response = await handleFetch(request, env, telemetry)
3618+
response = await handleFetch(request, env, telemetry, ctx)
35863619
} catch (error) {
35873620
telemetry.errorType = errorTypeFor(error)
35883621
response = new Response("Internal Server Error", { status: 500 })

0 commit comments

Comments
 (0)