From 68c3d4104c3c8fe981bb38014bb8945c650dab8a Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 14 Sep 2026 18:53:53 +0900 Subject: [PATCH] fix(responses): narrow the send budget once for the adapter contract (#4546) Refs #4546. Forward fix for the gates failure on 1abc5cc563. HandleResponsesOptions.sendBudget is typed as the narrow TransientSendBudget holder so a caller that predates the execution budget can still pass one. AdapterFetchContext needs the full contract, because an adapter that retries internally has to call reserveDispatch. Passing the narrowed value straight through failed typecheck at all three fetchResponse literals. Narrow it once next to the other budget helpers instead of asserting at each call site; an adapter that receives undefined keeps its own retry shape, which is the documented optional behaviour. --- src/server/responses/core.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index 908c46f7f2..57c808401f 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -5063,6 +5063,10 @@ async function handleResponsesInner( isRequestExecutionBudget(sendBudget) ? sendBudget.remainingBaseSends(budget) : Math.max(0, budget - sendBudget.used); + // The adapter contract needs the full budget, not just the counter. options.sendBudget is + // typed as the narrow holder so a caller that predates this can still pass one, so narrow it + // once here rather than asserting at each adapter call site. + const adapterSendBudget = isRequestExecutionBudget(sendBudget) ? sendBudget : undefined; const sendBudgetExhausted = (): boolean => remainingTransientSendBudget(TRANSIENT_RETRY_MAX_ATTEMPTS) === 0; /** @@ -7745,7 +7749,7 @@ async function handleResponsesInner( upstreamResponse = await activeAdapter.fetchResponse(builtInitialRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, - sendBudget, + sendBudget: adapterSendBudget, stream: parsed.stream, executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, { dispatchOverride: oauthDispatch(builtInitialRequest), @@ -7880,7 +7884,7 @@ async function handleResponsesInner( return await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, - sendBudget, + sendBudget: adapterSendBudget, stream: parsed.stream, executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, { dispatchOverride: oauthDispatch(retryRequest), @@ -8438,7 +8442,7 @@ async function handleResponsesInner( return await activeAdapter.fetchResponse(builtContinuationRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, - sendBudget, + sendBudget: adapterSendBudget, stream: nextParsed.stream, executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, { dispatchOverride: oauthDispatch(builtContinuationRequest, nextParsed),