mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
refactor(executors): extract pure quota parsing from codex (#5999)
Extract the pure Codex quota-snapshot parsing + reset/cooldown scheduling (CodexQuotaSnapshot, parseCodexQuotaHeaders, getCodexResetTime, getCodexDualWindowCooldownMs) verbatim into the leaf codex/quota.ts. Host re-exports the 4 symbols so handlers/chatCore/codexQuota.ts + tests keep resolving. Host 1539 -> 1427 LOC. Byte-identical bodies (verbatim 98/98), leaf has zero imports (only Date, no cycle). WS transport, auth, HTTP dispatch untouched. Adds a split-guard; consumer tests stay green (executor-codex 40, codex-quota-fetcher 7, chatcore-codex-quota 5).
This commit is contained in:
committed by
GitHub
parent
cc570cbc6b
commit
edb01b9cfe
@@ -37,6 +37,14 @@ import { CORS_HEADERS } from "../utils/cors.ts";
|
||||
import { normalizeCodexResponsesInput } from "../utils/responsesInputNormalization.ts";
|
||||
import * as prl from "../utils/providerRequestLogging.ts";
|
||||
import { createRequire } from "module";
|
||||
// Quota parsing/scheduling extracted to a pure leaf; re-exported for external
|
||||
// importers (handlers/chatCore/codexQuota.ts + tests).
|
||||
export {
|
||||
type CodexQuotaSnapshot,
|
||||
parseCodexQuotaHeaders,
|
||||
getCodexResetTime,
|
||||
getCodexDualWindowCooldownMs,
|
||||
} from "./codex/quota.ts";
|
||||
|
||||
// ─── wreq-js lazy loader ───────────────────────────────────────────────────
|
||||
// wreq-js is a Rust-native module that requires platform-specific .node binaries.
|
||||
@@ -104,119 +112,6 @@ function codexWebSocketUnavailableResponse(): Response {
|
||||
// Ref: sub2api PR #1129 (feat(openai): split codex spark rate limiting from codex)
|
||||
export { getCodexModelScope, getCodexRateLimitKey, type CodexQuotaScope };
|
||||
|
||||
/**
|
||||
* T03: Parsed quota snapshot from Codex response headers.
|
||||
* Codex includes per-account usage windows that allow precise reset scheduling.
|
||||
* Ref: sub2api PR #357 (feat(oauth): persist usage snapshots and window cooldown)
|
||||
*/
|
||||
export interface CodexQuotaSnapshot {
|
||||
usage5h: number; // tokens used in 5h window
|
||||
limit5h: number; // token limit for 5h window
|
||||
resetAt5h: string | null; // ISO timestamp when 5h window resets
|
||||
usage7d: number; // tokens used in 7d window
|
||||
limit7d: number; // token limit for 7d window
|
||||
resetAt7d: string | null; // ISO timestamp when 7d window resets
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Parse Codex-specific quota headers from a provider response.
|
||||
* Returns null if none of the relevant headers are present.
|
||||
*
|
||||
* Extracts:
|
||||
* x-codex-5h-usage / x-codex-5h-limit / x-codex-5h-reset-at
|
||||
* x-codex-7d-usage / x-codex-7d-limit / x-codex-7d-reset-at
|
||||
*/
|
||||
export function parseCodexQuotaHeaders(headers: Record<string, string>): CodexQuotaSnapshot | null {
|
||||
const usage5h = headers["x-codex-5h-usage"] ?? null;
|
||||
const limit5h = headers["x-codex-5h-limit"] ?? null;
|
||||
const resetAt5h = headers["x-codex-5h-reset-at"] ?? null;
|
||||
const usage7d = headers["x-codex-7d-usage"] ?? null;
|
||||
const limit7d = headers["x-codex-7d-limit"] ?? null;
|
||||
const resetAt7d = headers["x-codex-7d-reset-at"] ?? null;
|
||||
|
||||
// Return null if none of the quota headers are present (not a quota-aware response)
|
||||
if (!usage5h && !limit5h && !resetAt5h && !usage7d && !limit7d && !resetAt7d) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
usage5h: usage5h ? parseFloat(usage5h) : 0,
|
||||
limit5h: limit5h ? parseFloat(limit5h) : Infinity,
|
||||
resetAt5h: resetAt5h ?? null,
|
||||
usage7d: usage7d ? parseFloat(usage7d) : 0,
|
||||
limit7d: limit7d ? parseFloat(limit7d) : Infinity,
|
||||
resetAt7d: resetAt7d ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Get the soonest quota reset time from a CodexQuotaSnapshot.
|
||||
* 7d window takes priority (wider window, harder limit) but we use whichever
|
||||
* is further in the future to avoid releasing the block too early.
|
||||
*
|
||||
* @returns Unix timestamp (ms) of the soonest effective reset, or null
|
||||
*/
|
||||
export function getCodexResetTime(quota: CodexQuotaSnapshot): number | null {
|
||||
const times: number[] = [];
|
||||
if (quota.resetAt7d) {
|
||||
const t = new Date(quota.resetAt7d).getTime();
|
||||
if (!isNaN(t) && t > Date.now()) times.push(t);
|
||||
}
|
||||
if (quota.resetAt5h) {
|
||||
const t = new Date(quota.resetAt5h).getTime();
|
||||
if (!isNaN(t) && t > Date.now()) times.push(t);
|
||||
}
|
||||
if (times.length === 0) return null;
|
||||
return Math.max(...times); // Use furthest-out reset to avoid premature unblock
|
||||
}
|
||||
|
||||
/**
|
||||
* T03 (Item 3): Compute the minimum-necessary cooldown based on which window
|
||||
* is actually exhausted. Prevents over-blocking the account:
|
||||
*
|
||||
* - If 7d window >= threshold: cooldown until 7d reset (weekly window exhausted)
|
||||
* - If 5h window >= threshold: cooldown until 5h reset only (short-term limit)
|
||||
* - Otherwise: 0 (account is healthy, no cooldown needed)
|
||||
*
|
||||
* Called after parsing quota headers from a successful/429 response to
|
||||
* mark the account accordingly without overly long cooldowns.
|
||||
*
|
||||
* @param quota - Parsed quota snapshot from response headers
|
||||
* @param threshold - Fraction (0-1) that triggers cooldown (default: 0.95)
|
||||
* @returns Cooldown duration in milliseconds (0 = no cooldown needed)
|
||||
*/
|
||||
export function getCodexDualWindowCooldownMs(
|
||||
quota: CodexQuotaSnapshot,
|
||||
threshold = 0.95
|
||||
): { cooldownMs: number; window: "7d" | "5h" | "none" } {
|
||||
const now = Date.now();
|
||||
|
||||
// Compute per-window usage ratios (0..1)
|
||||
const ratio7d =
|
||||
quota.limit7d > 0 && Number.isFinite(quota.limit7d) ? quota.usage7d / quota.limit7d : 0;
|
||||
const ratio5h =
|
||||
quota.limit5h > 0 && Number.isFinite(quota.limit5h) ? quota.usage5h / quota.limit5h : 0;
|
||||
|
||||
// 7d window takes priority — if the weekly budget is near-exhausted,
|
||||
// we must wait until the weekly reset (not just 5h).
|
||||
if (ratio7d >= threshold && quota.resetAt7d) {
|
||||
const resetTime = new Date(quota.resetAt7d).getTime();
|
||||
if (resetTime > now) {
|
||||
return { cooldownMs: resetTime - now, window: "7d" };
|
||||
}
|
||||
}
|
||||
|
||||
// 5h window (primary short-term rate limit)
|
||||
if (ratio5h >= threshold && quota.resetAt5h) {
|
||||
const resetTime = new Date(quota.resetAt5h).getTime();
|
||||
if (resetTime > now) {
|
||||
return { cooldownMs: resetTime - now, window: "5h" };
|
||||
}
|
||||
}
|
||||
|
||||
return { cooldownMs: 0, window: "none" };
|
||||
}
|
||||
|
||||
// Ordered list of effort levels from lowest to highest
|
||||
const EFFORT_ORDER = ["none", "low", "medium", "high", "xhigh"] as const;
|
||||
type EffortLevel = (typeof EFFORT_ORDER)[number];
|
||||
@@ -1159,9 +1054,7 @@ export class CodexExecutor extends BaseExecutor {
|
||||
headers["chatgpt-account-id"] = workspaceId;
|
||||
}
|
||||
const clientIdentity = credentials?.providerSpecificData?.codexClientIdentity as
|
||||
| CodexClientIdentity
|
||||
| null
|
||||
| undefined;
|
||||
CodexClientIdentity | null | undefined;
|
||||
|
||||
// Originator header — identifies the client type to the Codex backend.
|
||||
// Ref: openai/codex login/src/auth/default_client.rs DEFAULT_ORIGINATOR = "codex_cli_rs"
|
||||
@@ -1481,9 +1374,7 @@ export class CodexExecutor extends BaseExecutor {
|
||||
applyCodexClientMetadata(
|
||||
body,
|
||||
credentials?.providerSpecificData?.codexClientIdentity as
|
||||
| CodexClientIdentity
|
||||
| null
|
||||
| undefined
|
||||
CodexClientIdentity | null | undefined
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
114
open-sse/executors/codex/quota.ts
Normal file
114
open-sse/executors/codex/quota.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// Codex quota-snapshot parsing + reset/cooldown scheduling (pure). Verbatim from codex.ts.
|
||||
|
||||
/**
|
||||
* T03: Parsed quota snapshot from Codex response headers.
|
||||
* Codex includes per-account usage windows that allow precise reset scheduling.
|
||||
* Ref: sub2api PR #357 (feat(oauth): persist usage snapshots and window cooldown)
|
||||
*/
|
||||
export interface CodexQuotaSnapshot {
|
||||
usage5h: number; // tokens used in 5h window
|
||||
limit5h: number; // token limit for 5h window
|
||||
resetAt5h: string | null; // ISO timestamp when 5h window resets
|
||||
usage7d: number; // tokens used in 7d window
|
||||
limit7d: number; // token limit for 7d window
|
||||
resetAt7d: string | null; // ISO timestamp when 7d window resets
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Parse Codex-specific quota headers from a provider response.
|
||||
* Returns null if none of the relevant headers are present.
|
||||
*
|
||||
* Extracts:
|
||||
* x-codex-5h-usage / x-codex-5h-limit / x-codex-5h-reset-at
|
||||
* x-codex-7d-usage / x-codex-7d-limit / x-codex-7d-reset-at
|
||||
*/
|
||||
export function parseCodexQuotaHeaders(headers: Record<string, string>): CodexQuotaSnapshot | null {
|
||||
const usage5h = headers["x-codex-5h-usage"] ?? null;
|
||||
const limit5h = headers["x-codex-5h-limit"] ?? null;
|
||||
const resetAt5h = headers["x-codex-5h-reset-at"] ?? null;
|
||||
const usage7d = headers["x-codex-7d-usage"] ?? null;
|
||||
const limit7d = headers["x-codex-7d-limit"] ?? null;
|
||||
const resetAt7d = headers["x-codex-7d-reset-at"] ?? null;
|
||||
|
||||
// Return null if none of the quota headers are present (not a quota-aware response)
|
||||
if (!usage5h && !limit5h && !resetAt5h && !usage7d && !limit7d && !resetAt7d) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
usage5h: usage5h ? parseFloat(usage5h) : 0,
|
||||
limit5h: limit5h ? parseFloat(limit5h) : Infinity,
|
||||
resetAt5h: resetAt5h ?? null,
|
||||
usage7d: usage7d ? parseFloat(usage7d) : 0,
|
||||
limit7d: limit7d ? parseFloat(limit7d) : Infinity,
|
||||
resetAt7d: resetAt7d ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* T03: Get the soonest quota reset time from a CodexQuotaSnapshot.
|
||||
* 7d window takes priority (wider window, harder limit) but we use whichever
|
||||
* is further in the future to avoid releasing the block too early.
|
||||
*
|
||||
* @returns Unix timestamp (ms) of the soonest effective reset, or null
|
||||
*/
|
||||
export function getCodexResetTime(quota: CodexQuotaSnapshot): number | null {
|
||||
const times: number[] = [];
|
||||
if (quota.resetAt7d) {
|
||||
const t = new Date(quota.resetAt7d).getTime();
|
||||
if (!isNaN(t) && t > Date.now()) times.push(t);
|
||||
}
|
||||
if (quota.resetAt5h) {
|
||||
const t = new Date(quota.resetAt5h).getTime();
|
||||
if (!isNaN(t) && t > Date.now()) times.push(t);
|
||||
}
|
||||
if (times.length === 0) return null;
|
||||
return Math.max(...times); // Use furthest-out reset to avoid premature unblock
|
||||
}
|
||||
|
||||
/**
|
||||
* T03 (Item 3): Compute the minimum-necessary cooldown based on which window
|
||||
* is actually exhausted. Prevents over-blocking the account:
|
||||
*
|
||||
* - If 7d window >= threshold: cooldown until 7d reset (weekly window exhausted)
|
||||
* - If 5h window >= threshold: cooldown until 5h reset only (short-term limit)
|
||||
* - Otherwise: 0 (account is healthy, no cooldown needed)
|
||||
*
|
||||
* Called after parsing quota headers from a successful/429 response to
|
||||
* mark the account accordingly without overly long cooldowns.
|
||||
*
|
||||
* @param quota - Parsed quota snapshot from response headers
|
||||
* @param threshold - Fraction (0-1) that triggers cooldown (default: 0.95)
|
||||
* @returns Cooldown duration in milliseconds (0 = no cooldown needed)
|
||||
*/
|
||||
export function getCodexDualWindowCooldownMs(
|
||||
quota: CodexQuotaSnapshot,
|
||||
threshold = 0.95
|
||||
): { cooldownMs: number; window: "7d" | "5h" | "none" } {
|
||||
const now = Date.now();
|
||||
|
||||
// Compute per-window usage ratios (0..1)
|
||||
const ratio7d =
|
||||
quota.limit7d > 0 && Number.isFinite(quota.limit7d) ? quota.usage7d / quota.limit7d : 0;
|
||||
const ratio5h =
|
||||
quota.limit5h > 0 && Number.isFinite(quota.limit5h) ? quota.usage5h / quota.limit5h : 0;
|
||||
|
||||
// 7d window takes priority — if the weekly budget is near-exhausted,
|
||||
// we must wait until the weekly reset (not just 5h).
|
||||
if (ratio7d >= threshold && quota.resetAt7d) {
|
||||
const resetTime = new Date(quota.resetAt7d).getTime();
|
||||
if (resetTime > now) {
|
||||
return { cooldownMs: resetTime - now, window: "7d" };
|
||||
}
|
||||
}
|
||||
|
||||
// 5h window (primary short-term rate limit)
|
||||
if (ratio5h >= threshold && quota.resetAt5h) {
|
||||
const resetTime = new Date(quota.resetAt5h).getTime();
|
||||
if (resetTime > now) {
|
||||
return { cooldownMs: resetTime - now, window: "5h" };
|
||||
}
|
||||
}
|
||||
|
||||
return { cooldownMs: 0, window: "none" };
|
||||
}
|
||||
36
tests/unit/codex-executor-split.test.ts
Normal file
36
tests/unit/codex-executor-split.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
// Split-guard for the codex executor quota extraction.
|
||||
// The pure quota-snapshot parsing + reset/cooldown scheduling lives in codex/quota.ts.
|
||||
// Host re-exports the 4 public symbols (chatCore/codexQuota.ts + tests import them).
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const EXE = join(HERE, "../../open-sse/executors");
|
||||
const HOST = join(EXE, "codex.ts");
|
||||
const LEAF = join(EXE, "codex/quota.ts");
|
||||
|
||||
test("leaf hosts the quota helpers and does not import the host", () => {
|
||||
const src = readFileSync(LEAF, "utf8");
|
||||
for (const sym of [
|
||||
"parseCodexQuotaHeaders",
|
||||
"getCodexResetTime",
|
||||
"getCodexDualWindowCooldownMs",
|
||||
"CodexQuotaSnapshot",
|
||||
]) {
|
||||
assert.match(src, new RegExp(`export (function|interface) ${sym}\\b`));
|
||||
}
|
||||
assert.doesNotMatch(src, /from "\.\.\/codex\.ts"/);
|
||||
});
|
||||
|
||||
test("host re-exports the quota symbols for external importers", () => {
|
||||
const host = readFileSync(HOST, "utf8");
|
||||
assert.match(host, /from "\.\/codex\/quota\.ts"/);
|
||||
});
|
||||
|
||||
test("parseCodexQuotaHeaders returns null without quota headers", async () => {
|
||||
const { parseCodexQuotaHeaders } = await import("../../open-sse/executors/codex/quota.ts");
|
||||
assert.equal(parseCodexQuotaHeaders({}), null);
|
||||
});
|
||||
Reference in New Issue
Block a user