mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
Merged — locally validated (30/30 focused tests: chatcore-codex-account-pool, codex-account-cooldown-write, codex-account-pool, providers-route-codex-account-pool, resilience-explain-codex-account, sse-auth-codex-account-pool; typecheck:core clean; file-size/complexity/cognitive-complexity/changelog gates all green). Merges clean against the current release tip with zero conflicts. Great refactor — extracting persistCodexQuotaState out of chatCore.ts into a proper codexAccount/ module with virtual quota pool isolation is a solid improvement. Thanks!
181 lines
6.4 KiB
TypeScript
181 lines
6.4 KiB
TypeScript
import { getCodexModelScope, type CodexQuotaScope } from "../../config/codexQuotaScopes.ts";
|
|
import type {
|
|
CodexAccountConnection,
|
|
CodexAccountPool,
|
|
CodexAccountPoolState,
|
|
CodexChildAccount,
|
|
CodexChildAccountState,
|
|
CodexChildCooldown,
|
|
CodexChildQuotaHydration,
|
|
CodexPersistedQuotaState,
|
|
CodexParentAccount,
|
|
CodexParentAccountDiagnostic,
|
|
CodexAccountState,
|
|
CodexAccount,
|
|
} from "./types.ts";
|
|
|
|
const CODEX_SCOPES: readonly CodexQuotaScope[] = ["codex", "spark"];
|
|
|
|
type LegacyStateOwner = Pick<CodexAccountConnection, "providerSpecificData">;
|
|
|
|
function asRecord(value: unknown): Readonly<Record<string, unknown>> {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as Readonly<Record<string, unknown>>)
|
|
: {};
|
|
}
|
|
|
|
function getLegacyCooldownMap(connection: LegacyStateOwner): Readonly<Record<string, unknown>> {
|
|
return asRecord(connection.providerSpecificData.codexScopeRateLimitedUntil);
|
|
}
|
|
|
|
function getLegacyCooldown(account: CodexChildAccount): string | null {
|
|
const value = getLegacyCooldownMap(account.connection)[account.scope];
|
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
}
|
|
|
|
function asQuotaState(value: unknown): CodexPersistedQuotaState | null {
|
|
const record = asRecord(value);
|
|
return Object.keys(record).length > 0 ? (record as CodexPersistedQuotaState) : null;
|
|
}
|
|
|
|
function asExhaustedWindow(value: unknown): "5h" | "7d" | null {
|
|
return value === "5h" || value === "7d" ? value : null;
|
|
}
|
|
|
|
/** Decode persisted quota facts for exactly one virtual child. */
|
|
export function getCodexChildQuotaHydration(account: CodexChildAccount): CodexChildQuotaHydration {
|
|
const data = account.connection.providerSpecificData;
|
|
const scopedQuota = asQuotaState(asRecord(data.codexQuotaStateByScope)[account.scope]);
|
|
const legacyQuota = asRecord(data.codexQuotaState);
|
|
const matchingLegacyQuota =
|
|
legacyQuota.scope === account.scope ? asQuotaState(legacyQuota) : null;
|
|
const exhaustedByScope = asRecord(data.codexExhaustedWindowByScope);
|
|
const scopedExhaustedWindow = asExhaustedWindow(exhaustedByScope[account.scope]);
|
|
const legacyExhaustedWindow = matchingLegacyQuota
|
|
? asExhaustedWindow(data.codexExhaustedWindow)
|
|
: null;
|
|
|
|
return {
|
|
scope: account.scope,
|
|
quotaState: scopedQuota ?? matchingLegacyQuota,
|
|
exhaustedWindow: scopedExhaustedWindow ?? legacyExhaustedWindow,
|
|
rateLimitedUntil: getLegacyCooldown(account),
|
|
};
|
|
}
|
|
|
|
function parseFutureTimestamp(value: string | null, nowMs: number): number | null {
|
|
if (!value) return null;
|
|
const timestampMs = new Date(value).getTime();
|
|
return Number.isFinite(timestampMs) && timestampMs > nowMs ? timestampMs : null;
|
|
}
|
|
|
|
function resolveChild(pool: CodexAccountPool, model: string): CodexChildAccount {
|
|
const scope = getCodexModelScope(model);
|
|
return pool.children.find((account) => account.scope === scope) ?? pool.children[0];
|
|
}
|
|
|
|
/** Inspect the read-only parent aggregate without exposing legacy storage parsing. */
|
|
export function inspectCodexAccount(
|
|
pool: CodexAccountPool,
|
|
account: CodexParentAccount,
|
|
nowMs?: number
|
|
): CodexAccountPoolState;
|
|
/** Inspect one scoped child without exposing legacy storage parsing. */
|
|
export function inspectCodexAccount(
|
|
pool: CodexAccountPool,
|
|
account: CodexChildAccount,
|
|
nowMs?: number
|
|
): CodexChildAccountState;
|
|
/** Inspect a runtime-selected parent or child account. */
|
|
export function inspectCodexAccount(
|
|
pool: CodexAccountPool,
|
|
account: CodexAccount,
|
|
nowMs?: number
|
|
): CodexAccountState;
|
|
export function inspectCodexAccount(
|
|
pool: CodexAccountPool,
|
|
account: CodexParentAccount | CodexChildAccount,
|
|
nowMs = Date.now()
|
|
): CodexAccountPoolState | CodexChildAccountState {
|
|
if (account.connectionId !== pool.parent.connectionId) {
|
|
throw new Error("Codex account does not belong to this pool");
|
|
}
|
|
if (account.kind === "parent") return getCodexAccountPoolState(pool, nowMs);
|
|
const rateLimitedUntil = getLegacyCooldown(account);
|
|
return {
|
|
kind: "child",
|
|
scope: account.scope,
|
|
rateLimitedUntil,
|
|
unavailable: parseFutureTimestamp(rateLimitedUntil, nowMs) !== null,
|
|
};
|
|
}
|
|
|
|
/** Return the earliest active child cooldown for a model across account pools. */
|
|
export function getEarliestCodexChildCooldown(
|
|
pools: readonly CodexAccountPool[],
|
|
model: string | null | undefined,
|
|
nowMs = Date.now()
|
|
): CodexChildCooldown | null {
|
|
if (typeof model !== "string" || model.trim().length === 0) return null;
|
|
let earliest: CodexChildCooldown | null = null;
|
|
let earliestMs = Infinity;
|
|
for (const pool of pools) {
|
|
const child = resolveChild(pool, model);
|
|
const until = getLegacyCooldown(child);
|
|
const timestampMs = parseFutureTimestamp(until, nowMs);
|
|
if (timestampMs !== null && timestampMs < earliestMs && until !== null) {
|
|
earliest = { account: child, until };
|
|
earliestMs = timestampMs;
|
|
}
|
|
}
|
|
return earliest;
|
|
}
|
|
|
|
/** Build one parent-only diagnostic from virtual child state. */
|
|
export function getCodexParentAccountDiagnostic(
|
|
pool: CodexAccountPool,
|
|
nowMs = Date.now()
|
|
): CodexParentAccountDiagnostic {
|
|
const state = getCodexAccountPoolState(pool, nowMs);
|
|
const retryTimestamps = pool.children
|
|
.map((child) => parseFutureTimestamp(getLegacyCooldown(child), nowMs))
|
|
.filter((value): value is number => value !== null);
|
|
const observedScopeCount = pool.children.filter(
|
|
(child) => getCodexChildQuotaHydration(child).quotaState !== null
|
|
).length;
|
|
return {
|
|
status: state.status,
|
|
limitedScopeCount: state.limitedScopes.length,
|
|
cooldown: {
|
|
coolingDown: state.status === "fully_limited",
|
|
soonestRetryAfterMs:
|
|
retryTimestamps.length > 0 ? Math.max(0, Math.min(...retryTimestamps) - nowMs) : 0,
|
|
},
|
|
quota: { observedScopeCount },
|
|
};
|
|
}
|
|
|
|
/** Aggregate the two virtual child states as a read-only parent view. */
|
|
export function getCodexAccountPoolState(
|
|
pool: CodexAccountPool,
|
|
nowMs = Date.now()
|
|
): CodexAccountPoolState {
|
|
const limitedScopes = CODEX_SCOPES.filter((scope) => {
|
|
const child = pool.children.find((account) => account.scope === scope);
|
|
if (!child) return false;
|
|
const until = getLegacyCooldown(child);
|
|
return parseFutureTimestamp(until, nowMs) !== null;
|
|
});
|
|
|
|
return {
|
|
kind: "parent",
|
|
status:
|
|
limitedScopes.length === 0
|
|
? "available"
|
|
: limitedScopes.length === CODEX_SCOPES.length
|
|
? "fully_limited"
|
|
: "partially_limited",
|
|
limitedScopes,
|
|
};
|
|
}
|