From 82bae06d6de6f1a3fe4013c443c994afaf4b6857 Mon Sep 17 00:00:00 2001 From: devmello Date: Mon, 3 Aug 2026 04:02:45 -0700 Subject: [PATCH 1/2] fix(compact): report upstream usage for native compact turns The native branch buffers the upstream compact JSON and returns it without inspecting the body, so the request log row lands with no usage. Lift usage and response metadata from the buffered body the same way the routed branch gets it through handleResponses. --- src/server/responses/compact.ts | 4 ++++ tests/responses-compaction-routing.test.ts | 28 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/server/responses/compact.ts b/src/server/responses/compact.ts index 9a49a03f7..fc963ad62 100644 --- a/src/server/responses/compact.ts +++ b/src/server/responses/compact.ts @@ -506,6 +506,10 @@ export async function handleResponsesCompact( // Always record the real upstream status: a local buffering failure after a // 200 upstream response must not soft-avoid a healthy account or rotate a thread. recordCompactPoolOutcome(outcomeCtx, upstream.status, { retryAfter, resetAt }); + // Lift usage and response metadata from the buffered upstream JSON into the + // request log; the routed branch gets the same through handleResponses. The + // synthetic buffer errors are not upstream bodies and stay uninspected. + if (buffered.ok) inspectResponseLogJson(logCtx, await buffered.clone().text()); return buffered; } diff --git a/tests/responses-compaction-routing.test.ts b/tests/responses-compaction-routing.test.ts index 7003fa11f..d8752d2fa 100644 --- a/tests/responses-compaction-routing.test.ts +++ b/tests/responses-compaction-routing.test.ts @@ -25,6 +25,7 @@ import { } from "../src/codex/auth-context"; import { supportsNativeResponsesCompactEndpoint } from "../src/providers/openai-tiers"; import { acquireNativeMainProfileDrain, tryAdmitTurn } from "../src/server/lifecycle"; +import type { RequestLogContext } from "../src/server/request-log"; import type { OcxConfig, OcxProviderConfig } from "../src/types"; const originalFetch = globalThis.fetch; @@ -166,6 +167,33 @@ describe("supportsNativeResponsesCompactEndpoint (#422)", () => { }); }); +describe("native compact usage reporting", () => { + test("the buffered upstream body fills the request log usage and stays intact for the client", async () => { + const config = { + defaultProvider: "openai-apikey", + providers: { + "openai-apikey": { + adapter: "openai-responses", + baseUrl: "https://api.openai.com/v1", + authMode: "key", + apiKey: "sk-test", + }, + }, + } as unknown as OcxConfig; + globalThis.fetch = (async () => jsonResponse(completedPayload("native summary"))) as typeof fetch; + const logCtx: RequestLogContext = { model: "", provider: "" }; + const response = await handleResponsesCompact( + compactionRequest(baseCompactionBody({ model: "openai-apikey/gpt-5.5" })), + config, + logCtx, + ); + expect(response.status).toBe(200); + const body = await response.json() as { usage?: Record }; + expect(body.usage).toMatchObject({ input_tokens: 10, output_tokens: 5, total_tokens: 15 }); + expect(logCtx.usage).toMatchObject({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }); + }); +}); + describe("native Codex pool compaction", () => { test("keeps a Spark reset cooldown separate from a Terra compact request (#590)", async () => { const testDir = mkdtempSync(join(tmpdir(), "ocx-compact-scope-")); From 8192ea49003f5dd4e76d8ce48196fa0bbf7af22d Mon Sep 17 00:00:00 2001 From: devmello Date: Mon, 3 Aug 2026 14:05:09 -0700 Subject: [PATCH 2/2] fix(compact): read usage without the debug body sampler --- src/server/responses/compact.ts | 15 +++++++++---- tests/responses-compaction-routing.test.ts | 25 ++++++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/server/responses/compact.ts b/src/server/responses/compact.ts index fc963ad62..447a0371c 100644 --- a/src/server/responses/compact.ts +++ b/src/server/responses/compact.ts @@ -86,10 +86,10 @@ import { redactSecretString } from "../../lib/redact"; import { readBoundedResponseBody } from "../../lib/bounded-body"; import { supportedLadderFor } from "../effort-policy"; import { + applyResponseLogMetadata, beginRequestAttempt, catalogModelSupportsServiceTier, finishRequestAttempt, - inspectResponseLogJson, noteAttemptSend, readConfiguredCodexServiceTier, requestLogSpeedLabel, @@ -507,9 +507,16 @@ export async function handleResponsesCompact( // 200 upstream response must not soft-avoid a healthy account or rotate a thread. recordCompactPoolOutcome(outcomeCtx, upstream.status, { retryAfter, resetAt }); // Lift usage and response metadata from the buffered upstream JSON into the - // request log; the routed branch gets the same through handleResponses. The - // synthetic buffer errors are not upstream bodies and stay uninspected. - if (buffered.ok) inspectResponseLogJson(logCtx, await buffered.clone().text()); + // request log; the routed branch gets the same through handleResponses. Only + // the parsed fields are read: a compact body is replacement history derived + // from the conversation, so it must never reach the usage debug body sampler. + if (buffered.ok) { + try { + applyResponseLogMetadata(logCtx, JSON.parse(await buffered.clone().text())); + } catch { + /* body may not be JSON; usage stays unreported */ + } + } return buffered; } diff --git a/tests/responses-compaction-routing.test.ts b/tests/responses-compaction-routing.test.ts index d8752d2fa..9b50412dc 100644 --- a/tests/responses-compaction-routing.test.ts +++ b/tests/responses-compaction-routing.test.ts @@ -182,15 +182,26 @@ describe("native compact usage reporting", () => { } as unknown as OcxConfig; globalThis.fetch = (async () => jsonResponse(completedPayload("native summary"))) as typeof fetch; const logCtx: RequestLogContext = { model: "", provider: "" }; - const response = await handleResponsesCompact( - compactionRequest(baseCompactionBody({ model: "openai-apikey/gpt-5.5" })), - config, - logCtx, - ); + const previousUsageDebug = process.env.OPENCODEX_USAGE_DEBUG; + process.env.OPENCODEX_USAGE_DEBUG = "1"; + let response: Response; + try { + response = await handleResponsesCompact( + compactionRequest(baseCompactionBody({ model: "openai-apikey/gpt-5.5" })), + config, + logCtx, + ); + } finally { + if (previousUsageDebug === undefined) delete process.env.OPENCODEX_USAGE_DEBUG; + else process.env.OPENCODEX_USAGE_DEBUG = previousUsageDebug; + } expect(response.status).toBe(200); - const body = await response.json() as { usage?: Record }; - expect(body.usage).toMatchObject({ input_tokens: 10, output_tokens: 5, total_tokens: 15 }); + expect(await response.json()).toEqual(completedPayload("native summary")); expect(logCtx.usage).toMatchObject({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }); + // The compact body is replacement history; even with usage debug on it must + // never be sampled into the debug log. + expect(logCtx.usageDebugBodyKind).toBeUndefined(); + expect(logCtx.usageDebugBodySample).toBeUndefined(); }); });