Skip to content

Commit c1dbe0a

Browse files
committed
fix(compact): read usage without the debug body sampler
1 parent 2ae3b5f commit c1dbe0a

2 files changed

Lines changed: 29 additions & 11 deletions

File tree

src/server/responses/compact.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ import { redactSecretString } from "../../lib/redact";
8383
import { readBoundedResponseBody } from "../../lib/bounded-body";
8484
import { supportedLadderFor } from "../effort-policy";
8585
import {
86+
applyResponseLogMetadata,
8687
beginRequestAttempt,
8788
catalogModelSupportsServiceTier,
8889
finishRequestAttempt,
89-
inspectResponseLogJson,
9090
noteAttemptSend,
9191
readConfiguredCodexServiceTier,
9292
requestLogSpeedLabel,
@@ -483,9 +483,16 @@ export async function handleResponsesCompact(
483483
// 200 upstream response must not soft-avoid a healthy account or rotate a thread.
484484
recordCompactPoolOutcome(outcomeCtx, upstream.status, { retryAfter, resetAt });
485485
// Lift usage and response metadata from the buffered upstream JSON into the
486-
// request log; the routed branch gets the same through handleResponses. The
487-
// synthetic buffer errors are not upstream bodies and stay uninspected.
488-
if (buffered.ok) inspectResponseLogJson(logCtx, await buffered.clone().text());
486+
// request log; the routed branch gets the same through handleResponses. Only
487+
// the parsed fields are read: a compact body is replacement history derived
488+
// from the conversation, so it must never reach the usage debug body sampler.
489+
if (buffered.ok) {
490+
try {
491+
applyResponseLogMetadata(logCtx, JSON.parse(await buffered.clone().text()));
492+
} catch {
493+
/* body may not be JSON; usage stays unreported */
494+
}
495+
}
489496
return buffered;
490497
}
491498

tests/responses-compaction-routing.test.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,26 @@ describe("native compact usage reporting", () => {
180180
} as unknown as OcxConfig;
181181
globalThis.fetch = (async () => jsonResponse(completedPayload("native summary"))) as typeof fetch;
182182
const logCtx: RequestLogContext = { model: "", provider: "" };
183-
const response = await handleResponsesCompact(
184-
compactionRequest(baseCompactionBody({ model: "openai-apikey/gpt-5.5" })),
185-
config,
186-
logCtx,
187-
);
183+
const previousUsageDebug = process.env.OPENCODEX_USAGE_DEBUG;
184+
process.env.OPENCODEX_USAGE_DEBUG = "1";
185+
let response: Response;
186+
try {
187+
response = await handleResponsesCompact(
188+
compactionRequest(baseCompactionBody({ model: "openai-apikey/gpt-5.5" })),
189+
config,
190+
logCtx,
191+
);
192+
} finally {
193+
if (previousUsageDebug === undefined) delete process.env.OPENCODEX_USAGE_DEBUG;
194+
else process.env.OPENCODEX_USAGE_DEBUG = previousUsageDebug;
195+
}
188196
expect(response.status).toBe(200);
189-
const body = await response.json() as { usage?: Record<string, unknown> };
190-
expect(body.usage).toMatchObject({ input_tokens: 10, output_tokens: 5, total_tokens: 15 });
197+
expect(await response.json()).toEqual(completedPayload("native summary"));
191198
expect(logCtx.usage).toMatchObject({ inputTokens: 10, outputTokens: 5, totalTokens: 15 });
199+
// The compact body is replacement history; even with usage debug on it must
200+
// never be sampled into the debug log.
201+
expect(logCtx.usageDebugBodyKind).toBeUndefined();
202+
expect(logCtx.usageDebugBodySample).toBeUndefined();
192203
});
193204
});
194205

0 commit comments

Comments
 (0)