-
Notifications
You must be signed in to change notification settings - Fork 939
fix(responses): bound the durable spill directory with an aggregate byte cap #3097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8e2d2c6
d5c7b56
3408626
6545a4b
663ce61
cf1f661
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -470,6 +470,26 @@ function serializedSpill( | |
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Exact on-disk payload size this spill WOULD occupy, measured before publication. | ||
| * | ||
| * Callers that reserve disk against a cap need the real envelope, not the resident | ||
| * measurement: the resident figure omits the `version` field the published payload | ||
| * carries, so pricing an admission by it undercounts and lets a request that sits exactly | ||
| * at the cap still exceed it. Shares `serializedSpill` rather than describing it, so the | ||
| * two cannot drift. | ||
| */ | ||
| export function prospectiveResponseSpillBytes( | ||
| responseId: string, | ||
| state: Omit<ResponseSpillPayload, "version" | "responseId">, | ||
| ): number | null { | ||
| try { | ||
| return serializedSpill(responseId, state).bytes.byteLength; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
Comment on lines
+482
to
+491
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win Sizing through
Callers hit this on the admission path. The anti-drift goal does not require the digests. Split the payload construction so the size path shares the exact envelope shape without hashing it. ♻️ Suggested split that keeps the envelope shared and drops the unused digests+function spillEnvelope(
+ responseId: string,
+ state: Omit<ResponseSpillPayload, "version" | "responseId">,
+): Buffer {
+ const payload: ResponseSpillPayload = {
+ version: 1,
+ responseId,
+ createdAt: state.createdAt,
+ ...(state.clientThreadId ? { clientThreadId: state.clientThreadId } : {}),
+ items: state.items,
+ ...(state.providerOutputStart !== undefined ? { providerOutputStart: state.providerOutputStart } : {}),
+ ...(state.providers ? { providers: state.providers } : {}),
+ };
+ const serialized = JSON.stringify(payload);
+ if (serialized === undefined) throw new Error("Response spill serialization failed");
+ return Buffer.from(serialized, "utf8");
+}
+
export function prospectiveResponseSpillBytes(
responseId: string,
state: Omit<ResponseSpillPayload, "version" | "responseId">,
): number | null {
try {
- return serializedSpill(responseId, state).bytes.byteLength;
+ return spillEnvelope(responseId, state).byteLength;
} catch {
return null;
}
}
Note: measuring 🤖 Prompt for AI Agents |
||
|
|
||
| function responseSpillWriteError(cause: unknown): NodeJS.ErrnoException { | ||
| const error = new Error("Response spill write failed", { cause }) as NodeJS.ErrnoException; | ||
| if (cause && typeof cause === "object" && "code" in cause) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The stated count of design-changing findings does not match the list.
Line 134-135 says "Three of their findings changed the design rather than the code, so they belong here", and four bullets follow at Lines 137, 141, 145, and 150:
All four describe design changes, and all four match the implementation. I verified each one:
prospectiveResponseSpillBytessharesserializedSpill(src/responses/spill-store.tsLine 487).src/responses/state.tsLine 423) and again in the shutdown fallback (Line 553).existsSync(src/responses/state.tsLines 258-273 and 640-642).ENOSPC(src/responses/state.tsLine 567).This document is the audit record the PR objectives point to, so the count should agree with the list.
📝 Proposed fix
Secondary point on the same added block: the new
## What implementation added beyond this planheading at Line 132 is inserted directly above the### Amendment after audit round 1heading at Line 157. Because the new heading is an H2 and the amendment is an H3, the round-1 amendment now renders as a subsection of this retrospective instead of a sibling of the round-3 and round-4 amendments at Lines 47 and 66. Moving the new section below Line 175 keeps the three amendments at the same level.📝 Committable suggestion
🤖 Prompt for AI Agents