refactor(chatCore): extrai núcleo puro de persistCodexQuotaState (#3501)

Move a construção do payload de persistCodexQuotaState para o novo leaf puro
open-sse/handlers/chatCore/codexQuota.ts (buildCodexQuotaPersistence): parseia
os headers de quota do Codex no snapshot codexQuotaState, faz passthrough do
providerSpecificData existente e, num 429 cuja janela (dual-window) passou do
threshold de exaustão, registra o cooldown por escopo (codexScopeRateLimitedUntil)
+ codexExhaustedWindow, retornando a mensagem de debug-log.

O handler mantém as partes impuras byte-idênticas e na mesma ordem: emitir o
exhaustionLog retornado, invalidateCodexQuotaCache em todo 429 (connectionId é
garantido pelo early-return), depois updateProviderConnection + mutação de
credentials. Imports órfãos (parseCodexQuotaHeaders/getCodexModelScope/
getCodexDualWindowCooldownMs) migram para o leaf; isCompactResponsesEndpoint
permanece (usado no site de passthrough).

chatCore.ts 5055->5019 (shrink -36); baseline file-size ratchetado.
complexity 1905=1905 (neutro). Coberto por
tests/unit/chatcore-codex-quota.test.ts (5 casos: sem-headers null,
snapshot+passthrough, 429 cooldown+janela+log, merge do scope-map, 429
abaixo-do-threshold no-op).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-21 08:37:58 -03:00
parent aa019bbb95
commit b12c80fa0c
3 changed files with 211 additions and 56 deletions

View File

@@ -206,12 +206,8 @@ import {
shouldDetectLimit,
} from "../services/toolLimitDetector.ts";
import {
parseCodexQuotaHeaders,
getCodexModelScope,
getCodexDualWindowCooldownMs,
isCompactResponsesEndpoint,
} from "../executors/codex.ts";
import { isCompactResponsesEndpoint } from "../executors/codex.ts";
import { buildCodexQuotaPersistence } from "./chatCore/codexQuota.ts";
import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts";
import { translateNonStreamingResponse } from "./responseTranslator.ts";
import { extractUsageFromResponse } from "./usageExtractor.ts";
@@ -826,67 +822,35 @@ export async function handleChatCore({
if (provider !== "codex" || !connectionId || !headers) return;
try {
const quota = parseCodexQuotaHeaders(headers);
if (!quota) return;
const existingProviderData =
credentials?.providerSpecificData && typeof credentials.providerSpecificData === "object"
? credentials.providerSpecificData
? (credentials.providerSpecificData as Record<string, unknown>)
: {};
const scope = getCodexModelScope(model || requestedModel || "");
const quotaState = {
usage5h: quota.usage5h,
limit5h: quota.limit5h,
resetAt5h: quota.resetAt5h,
usage7d: quota.usage7d,
limit7d: quota.limit7d,
resetAt7d: quota.resetAt7d,
scope,
updatedAt: new Date().toISOString(),
};
// Pure payload build extracted to chatCore/codexQuota.ts (#3501). Returns null when the
// response carries no quota headers (nothing to persist).
const built = buildCodexQuotaPersistence({
headers,
existingProviderData,
modelForScope: model || requestedModel || "",
status,
});
if (!built) return;
const nextProviderData: Record<string, unknown> = {
...existingProviderData,
codexQuotaState: quotaState,
};
if (built.exhaustionLog) {
log?.debug?.("CODEX", built.exhaustionLog);
}
// T03/T09: on 429, persist exact reset time per scope to avoid global over-blocking.
// Use dual-window cooldown to distinguish short-term and weekly Codex exhaustion.
// Invalidate the preflight cache for this connection so the next
// isModelAvailable check fetches fresh quota data.
if (status === 429) {
const { cooldownMs, window: exhaustedWindow } = getCodexDualWindowCooldownMs(quota);
if (cooldownMs > 0) {
const scopeUntil = new Date(Date.now() + cooldownMs).toISOString();
const scopeMapRaw =
existingProviderData &&
typeof existingProviderData === "object" &&
existingProviderData.codexScopeRateLimitedUntil &&
typeof existingProviderData.codexScopeRateLimitedUntil === "object"
? existingProviderData.codexScopeRateLimitedUntil
: {};
nextProviderData.codexScopeRateLimitedUntil = {
...(scopeMapRaw as Record<string, unknown>),
[scope]: scopeUntil,
};
nextProviderData.codexExhaustedWindow = exhaustedWindow;
log?.debug?.(
"CODEX",
`Quota exhaustion on ${exhaustedWindow} window, cooldown until ${scopeUntil}`
);
}
// Invalidate the preflight cache for this connection so the next
// isModelAvailable check fetches fresh quota data.
if (connectionId) {
invalidateCodexQuotaCache(connectionId);
}
invalidateCodexQuotaCache(connectionId);
}
await updateProviderConnection(connectionId, {
providerSpecificData: nextProviderData,
providerSpecificData: built.nextProviderData,
});
credentials.providerSpecificData = nextProviderData;
credentials.providerSpecificData = built.nextProviderData;
} catch (err) {
const errMessage = err instanceof Error ? err.message : String(err);
log?.debug?.("CODEX", `Failed to persist codex quota state: ${errMessage}`);

View File

@@ -0,0 +1,85 @@
/**
* chatCore Codex quota-persistence builder (Quality Gate v2 / Fase 9 — chatCore god-file
* decomposition, #3501).
*
* Pure core of handleChatCore's persistCodexQuotaState: turns the upstream Codex quota response
* headers into the next `providerSpecificData` payload (the codexQuotaState snapshot, plus — on a
* 429 whose dual-window usage is past the exhaustion threshold — the per-scope cooldown timestamp,
* the exhausted window, and the debug-log message). The handler keeps the impure parts byte-
* identically: the DB write (updateProviderConnection), the preflight-cache invalidation on every
* 429, the credentials mutation, and emitting the returned log line.
*/
import {
parseCodexQuotaHeaders,
getCodexModelScope,
getCodexDualWindowCooldownMs,
} from "../../executors/codex.ts";
export type CodexQuotaPersistence = {
/** The merged providerSpecificData to persist (existing data + codexQuotaState [+ 429 cooldown]). */
nextProviderData: Record<string, unknown>;
/** The CODEX debug-log message to emit when a 429 exhausted a window, else null. */
exhaustionLog: string | null;
};
/**
* Build the providerSpecificData update for a Codex quota response. Returns null when the response
* carries no quota headers (nothing to persist). Pure: a function of the headers, the existing
* provider data, the model used for scope resolution, and the upstream status.
*/
export function buildCodexQuotaPersistence(opts: {
headers: Record<string, string>;
existingProviderData: Record<string, unknown>;
modelForScope: string;
status: number;
}): CodexQuotaPersistence | null {
const { headers, existingProviderData, modelForScope, status } = opts;
const quota = parseCodexQuotaHeaders(headers);
if (!quota) return null;
const scope = getCodexModelScope(modelForScope);
const quotaState = {
usage5h: quota.usage5h,
limit5h: quota.limit5h,
resetAt5h: quota.resetAt5h,
usage7d: quota.usage7d,
limit7d: quota.limit7d,
resetAt7d: quota.resetAt7d,
scope,
updatedAt: new Date().toISOString(),
};
const nextProviderData: Record<string, unknown> = {
...existingProviderData,
codexQuotaState: quotaState,
};
let exhaustionLog: string | null = null;
// T03/T09: on 429, persist exact reset time per scope to avoid global over-blocking.
// Use dual-window cooldown to distinguish short-term and weekly Codex exhaustion.
if (status === 429) {
const { cooldownMs, window: exhaustedWindow } = getCodexDualWindowCooldownMs(quota);
if (cooldownMs > 0) {
const scopeUntil = new Date(Date.now() + cooldownMs).toISOString();
const scopeMapRaw =
existingProviderData &&
typeof existingProviderData === "object" &&
existingProviderData.codexScopeRateLimitedUntil &&
typeof existingProviderData.codexScopeRateLimitedUntil === "object"
? existingProviderData.codexScopeRateLimitedUntil
: {};
nextProviderData.codexScopeRateLimitedUntil = {
...(scopeMapRaw as Record<string, unknown>),
[scope]: scopeUntil,
};
nextProviderData.codexExhaustedWindow = exhaustedWindow;
exhaustionLog = `Quota exhaustion on ${exhaustedWindow} window, cooldown until ${scopeUntil}`;
}
}
return { nextProviderData, exhaustionLog };
}

View File

@@ -0,0 +1,106 @@
// tests/unit/chatcore-codex-quota.test.ts
// Characterization of buildCodexQuotaPersistence — the pure core of handleChatCore's
// persistCodexQuotaState, extracted during the chatCore god-file decomposition (#3501). Locks the
// shape of the persisted providerSpecificData: the codexQuotaState snapshot, the existing-data
// passthrough, and the 429 dual-window exhaustion fields (codexScopeRateLimitedUntil /
// codexExhaustedWindow) plus the debug-log message. The handler keeps the DB write, the
// preflight-cache invalidation, and the log emission; this function only builds the data.
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildCodexQuotaPersistence } from "../../open-sse/handlers/chatCore/codexQuota.ts";
import { getCodexModelScope } from "../../open-sse/executors/codex.ts";
const MODEL = "gpt-5-codex";
const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
function quotaHeaders(over: Record<string, string> = {}) {
return {
"x-codex-5h-usage": "50",
"x-codex-5h-limit": "100",
"x-codex-5h-reset-at": "2999-01-01T00:00:00.000Z",
"x-codex-7d-usage": "10",
"x-codex-7d-limit": "100",
"x-codex-7d-reset-at": "2999-01-08T00:00:00.000Z",
...over,
};
}
test("returns null when the response carries no codex quota headers", () => {
assert.equal(
buildCodexQuotaPersistence({ headers: {}, existingProviderData: {}, modelForScope: MODEL, status: 200 }),
null
);
assert.equal(
buildCodexQuotaPersistence({ headers: { "content-type": "application/json" }, existingProviderData: {}, modelForScope: MODEL, status: 200 }),
null
);
});
test("builds codexQuotaState (parsed numbers + scope + updatedAt) and preserves existing provider data", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders(),
existingProviderData: { keepMe: "yes", apiKeyHealth: { primary: {} } },
modelForScope: MODEL,
status: 200,
});
assert.ok(built);
const qs = built.nextProviderData.codexQuotaState as Record<string, unknown>;
assert.equal(qs.usage5h, 50);
assert.equal(qs.limit5h, 100);
assert.equal(qs.usage7d, 10);
assert.equal(qs.limit7d, 100);
assert.equal(qs.scope, getCodexModelScope(MODEL));
assert.match(String(qs.updatedAt), ISO);
// existing keys passed through, not dropped
assert.equal(built.nextProviderData.keepMe, "yes");
assert.deepEqual(built.nextProviderData.apiKeyHealth, { primary: {} });
// non-429 → no exhaustion fields, no log
assert.equal(built.exhaustionLog, null);
assert.equal(built.nextProviderData.codexScopeRateLimitedUntil, undefined);
assert.equal(built.nextProviderData.codexExhaustedWindow, undefined);
});
test("429 with a near-exhausted 5h window records the per-scope cooldown + window + log", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders({ "x-codex-5h-usage": "100" }), // ratio 1.0 >= 0.95, reset far in the future
existingProviderData: {},
modelForScope: MODEL,
status: 429,
});
assert.ok(built);
assert.equal(built.nextProviderData.codexExhaustedWindow, "5h");
const scope = getCodexModelScope(MODEL);
const scopeMap = built.nextProviderData.codexScopeRateLimitedUntil as Record<string, string>;
assert.ok(scopeMap[scope]?.startsWith("2999-01-01T00:00:00"));
assert.match(
String(built.exhaustionLog),
/^Quota exhaustion on 5h window, cooldown until 2999-01-01T00:00:00/
);
});
test("429 merges into an existing codexScopeRateLimitedUntil map without dropping other scopes", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders({ "x-codex-5h-usage": "100" }),
existingProviderData: { codexScopeRateLimitedUntil: { "other-scope": "2999-12-31T00:00:00.000Z" } },
modelForScope: MODEL,
status: 429,
});
assert.ok(built);
const scopeMap = built.nextProviderData.codexScopeRateLimitedUntil as Record<string, string>;
assert.equal(scopeMap["other-scope"], "2999-12-31T00:00:00.000Z");
assert.ok(scopeMap[getCodexModelScope(MODEL)]);
});
test("429 below the exhaustion threshold builds the snapshot but no cooldown / no log", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders({ "x-codex-5h-usage": "1", "x-codex-7d-usage": "1" }), // ratios well under 0.95
existingProviderData: {},
modelForScope: MODEL,
status: 429,
});
assert.ok(built);
assert.ok(built.nextProviderData.codexQuotaState);
assert.equal(built.exhaustionLog, null);
assert.equal(built.nextProviderData.codexScopeRateLimitedUntil, undefined);
assert.equal(built.nextProviderData.codexExhaustedWindow, undefined);
});