fix(admission): reserve Responses and Messages bodies before clone (#10814)

Merged — validated together with a batch of related RaviTharuma PRs in one combined worktree (typecheck:core clean, complexity/file-size/changelog gates green, focused tests passing). Thanks for the contribution!
This commit is contained in:
Ravi Tharuma
2026-08-20 16:48:14 +02:00
committed by GitHub
parent 7c6bf32186
commit 9eddafff60
9 changed files with 168 additions and 7 deletions

View File

@@ -0,0 +1,48 @@
/**
* Compose process-wide chat admission in front of a route handler.
*
* Uses the shipped `admitChatRequest` budget/fairness controller — it does not
* introduce a second admission path. Call this *outside* `withInjectionGuard`
* so a large `/v1/responses` or `/v1/messages` body is reserved (or 503-shed)
* before `request.clone()` / `.json()`.
*/
import {
admitChatRequest,
CHAT_ADMISSION_QUEUE_MAX_MS,
releaseChatAdmissionAfterHandler,
resolveSessionId,
type ChatAdmissionController,
} from "./chatBodyAdmission";
type RouteHandler = (request: Request, ...args: any[]) => Promise<Response> | Response;
export function withChatAdmission(
handler: RouteHandler,
options: {
controller?: ChatAdmissionController;
queueMs?: number;
largeBodyBytes?: number;
hardMaxBytes?: number;
} = {}
): RouteHandler {
return async function admittedHandler(request: Request, ...args: any[]) {
const sessionId = resolveSessionId(request);
const admission = await admitChatRequest(request, {
sessionId,
queueMs: options.queueMs ?? CHAT_ADMISSION_QUEUE_MAX_MS,
controller: options.controller,
largeBodyBytes: options.largeBodyBytes,
hardMaxBytes: options.hardMaxBytes,
});
if (admission.admit === false) return admission.response;
try {
return await releaseChatAdmissionAfterHandler(
Promise.resolve(handler(admission.request, ...args)),
admission.lease
);
} catch (error) {
admission.lease?.release();
throw error;
}
};
}