feat(auth): per-session sticky routing for codex (#1887)

Integrated into release/v3.8.0
This commit is contained in:
smartenok-ops
2026-05-09 23:58:07 -04:00
committed by GitHub
parent 7d6854e925
commit fa29e19863
6 changed files with 264 additions and 131 deletions

View File

@@ -136,11 +136,15 @@ export async function registerNodejs(): Promise<void> {
}
try {
const [{ migrateCodexConnectionDefaultsFromLegacySettings }, { seedDefaultModelAliases }] =
await Promise.all([
import("@/lib/providers/codexConnectionDefaults"),
import("@/lib/modelAliasSeed"),
]);
const [
{ migrateCodexConnectionDefaultsFromLegacySettings },
{ startSessionAccountAffinityCleanup },
{ seedDefaultModelAliases },
] = await Promise.all([
import("@/lib/providers/codexConnectionDefaults"),
import("@/lib/db/sessionAccountAffinity"),
import("@/lib/modelAliasSeed"),
]);
let settings = await getSettings();
const passwordState = await ensurePersistentManagementPasswordHash({
logger: console,
@@ -161,6 +165,7 @@ export async function registerNodejs(): Promise<void> {
console.log(
`[STARTUP] Model alias seed: applied=${seededModelAliases.applied.length}, skipped=${seededModelAliases.skipped.length}, failed=${seededModelAliases.failed.length}`
);
startSessionAccountAffinityCleanup();
const migration = await migrateCodexConnectionDefaultsFromLegacySettings();
if (migration.migrated) {

View File

@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS session_account_affinity (
session_key TEXT NOT NULL,
provider TEXT NOT NULL,
connection_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL,
PRIMARY KEY (session_key, provider)
);
CREATE INDEX IF NOT EXISTS idx_saa_provider ON session_account_affinity(provider);
CREATE INDEX IF NOT EXISTS idx_saa_last_seen ON session_account_affinity(last_seen_at);

View File

@@ -1,7 +1,49 @@
// Stubbed functions for session account affinity (PR 1887 pending)
import { getDbInstance } from "./core";
export function getSessionAccountAffinity(sessionKey: string, provider: string): any {
return null;
export interface SessionAccountAffinity {
sessionKey: string;
provider: string;
connectionId: string;
createdAt: number;
lastSeenAt: number;
}
interface SessionAccountAffinityRow {
session_key: string;
provider: string;
connection_id: string;
created_at: number;
last_seen_at: number;
}
const DEFAULT_TTL_MS = 30 * 60 * 1000;
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
let cleanupTimer: NodeJS.Timeout | null = null;
function rowToAffinity(row: SessionAccountAffinityRow): SessionAccountAffinity {
return {
sessionKey: row.session_key,
provider: row.provider,
connectionId: row.connection_id,
createdAt: row.created_at,
lastSeenAt: row.last_seen_at,
};
}
export function getSessionAccountAffinity(
sessionKey: string,
provider: string
): SessionAccountAffinity | null {
const db = getDbInstance();
const row = db
.prepare(
`SELECT session_key, provider, connection_id, created_at, last_seen_at
FROM session_account_affinity
WHERE session_key = ? AND provider = ?`
)
.get(sessionKey, provider) as SessionAccountAffinityRow | undefined;
return row ? rowToAffinity(row) : null;
}
export function upsertSessionAccountAffinity(
@@ -9,23 +51,72 @@ export function upsertSessionAccountAffinity(
provider: string,
connectionId: string,
now: number = Date.now()
): void {}
): void {
const db = getDbInstance();
db.prepare(
`INSERT INTO session_account_affinity
(session_key, provider, connection_id, created_at, last_seen_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(session_key, provider) DO UPDATE SET
connection_id = excluded.connection_id,
last_seen_at = excluded.last_seen_at`
).run(sessionKey, provider, connectionId, now, now);
}
export function touchSessionAccountAffinity(
sessionKey: string,
provider: string,
now: number = Date.now()
): void {}
export function deleteSessionAccountAffinity(sessionKey: string, provider: string): void {}
export function cleanupStaleSessionAccountAffinities(
ttlMs: number = 30 * 60 * 1000,
now: number = Date.now()
): number {
return 0;
): void {
const db = getDbInstance();
db.prepare(
`UPDATE session_account_affinity
SET last_seen_at = ?
WHERE session_key = ? AND provider = ?`
).run(now, sessionKey, provider);
}
export function startSessionAccountAffinityCleanup(): void {}
export function deleteSessionAccountAffinity(sessionKey: string, provider: string): void {
const db = getDbInstance();
db.prepare("DELETE FROM session_account_affinity WHERE session_key = ? AND provider = ?").run(
sessionKey,
provider
);
}
export function stopSessionAccountAffinityCleanupForTests(): void {}
export function cleanupStaleSessionAccountAffinities(
ttlMs: number = DEFAULT_TTL_MS,
now: number = Date.now()
): number {
const db = getDbInstance();
const cutoff = now - ttlMs;
const result = db
.prepare("DELETE FROM session_account_affinity WHERE last_seen_at < ?")
.run(cutoff);
return Number(result.changes || 0);
}
export function startSessionAccountAffinityCleanup(): void {
if (cleanupTimer) return;
try {
cleanupStaleSessionAccountAffinities();
} catch (error) {
console.warn("[SESSION_AFFINITY] Startup cleanup failed:", error);
}
cleanupTimer = setInterval(() => {
try {
cleanupStaleSessionAccountAffinities();
} catch (error) {
console.warn("[SESSION_AFFINITY] Periodic cleanup failed:", error);
}
}, CLEANUP_INTERVAL_MS);
cleanupTimer.unref?.();
}
export function stopSessionAccountAffinityCleanupForTests(): void {
if (!cleanupTimer) return;
clearInterval(cleanupTimer);
cleanupTimer = null;
}

View File

@@ -372,3 +372,13 @@ export {
} from "./db/oneproxy";
export type { OneproxyProxyRecord, OneproxyStats } from "./db/oneproxy";
export {
getSessionAccountAffinity,
upsertSessionAccountAffinity,
touchSessionAccountAffinity,
deleteSessionAccountAffinity,
cleanupStaleSessionAccountAffinities,
startSessionAccountAffinityCleanup,
stopSessionAccountAffinityCleanupForTests,
} from "./db/sessionAccountAffinity";

View File

@@ -3,6 +3,8 @@ import {
getProviderCredentialsWithQuotaPreflight,
markAccountUnavailable,
extractApiKey,
isValidApiKey,
extractSessionAffinityKey,
} from "../services/auth";
import {
getRuntimeProviderProfile,
@@ -208,6 +210,7 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
// T04: client-provided external session header has priority over generated fingerprint.
const externalSessionId = extractExternalSessionId(request.headers);
const sessionId = externalSessionId || generateStableSessionId(body);
const sessionAffinityKey = extractSessionAffinityKey(body, request.headers) || sessionId;
const requestedConnectionId = request.headers.get("x-omniroute-connection")?.trim() || null;
if (sessionId) {
touchSession(sessionId);
@@ -358,6 +361,7 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
allowedConnections,
resolvedModel,
{
sessionKey: sessionAffinityKey,
...(target?.connectionId ? { forcedConnectionId: target.connectionId } : {}),
}
);
@@ -400,6 +404,7 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
telemetry,
{
sessionId,
sessionAffinityKey,
forceLiveComboTest: isComboLiveTest,
forcedConnectionId: target?.connectionId ?? null,
allowedConnectionIds: target?.allowedConnectionIds ?? null,
@@ -450,7 +455,12 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
combo.name,
apiKeyInfo,
telemetry,
{ sessionId, emergencyFallbackTried: true, forceLiveComboTest: isComboLiveTest },
{
sessionId,
sessionAffinityKey,
emergencyFallbackTried: true,
forceLiveComboTest: isComboLiveTest,
},
combo.strategy,
true
);
@@ -486,6 +496,7 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
telemetry,
{
sessionId,
sessionAffinityKey,
forceLiveComboTest: isComboLiveTest,
forcedConnectionId: requestedConnectionId,
},
@@ -524,6 +535,7 @@ async function handleSingleModelChat(
emergencyFallbackTried?: boolean;
forceLiveComboTest?: boolean;
sessionId?: string | null;
sessionAffinityKey?: string | null;
forcedConnectionId?: string | null;
allowedConnectionIds?: string[] | null;
comboStepId?: string | null;
@@ -670,6 +682,7 @@ async function handleSingleModelChat(
effectiveAllowedConnections,
model,
{
sessionKey: runtimeOptions.sessionAffinityKey ?? runtimeOptions.sessionId ?? null,
excludeConnectionIds: Array.from(excludedConnectionIds),
...(forceLiveComboTest
? {

View File

@@ -14,7 +14,6 @@ const settingsDb = await import("../../src/lib/db/settings.ts");
const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
const auth = await import("../../src/sse/services/auth.ts");
const quotaCache = await import("../../src/domain/quotaCache.ts");
const { COOLDOWN_MS } = await import("../../open-sse/config/constants.ts");
async function resetStorage() {
core.resetDbInstance();
@@ -45,7 +44,6 @@ async function seedConnection(provider: string, overrides: any = {}) {
lastErrorSource: overrides.lastErrorSource,
errorCode: overrides.errorCode,
backoffLevel: overrides.backoffLevel,
maxConcurrent: overrides.maxConcurrent,
providerSpecificData: overrides.providerSpecificData || {},
lastUsedAt: overrides.lastUsedAt,
consecutiveUseCount: overrides.consecutiveUseCount,
@@ -135,7 +133,7 @@ test("getProviderCredentials enforces generic quota policy unless explicitly byp
},
});
const resetAt = futureIso();
quotaCache.setQuotaCache((connection as any).id, "openai", {
quotaCache.setQuotaCache(connection.id, "openai", {
daily: { remainingPercentage: 10, resetAt },
});
@@ -179,38 +177,6 @@ test("getProviderCredentialsWithQuotaPreflight skips exhausted preflight account
assert.equal((selected as any).connectionId, healthy.id);
});
test("getProviderCredentials includes per-account maxConcurrent caps", async () => {
const connection = await seedConnection("openai", {
name: "openai-concurrency-cap",
maxConcurrent: 2,
});
const selected = await auth.getProviderCredentials("openai");
assert.equal(selected.connectionId, connection.id);
assert.equal(selected.maxConcurrent, 2);
});
test("getProviderCredentials skips connections that exclude the requested model and selects the next eligible account", async () => {
const excluded = await seedConnection("openai", {
name: "excluded-first",
priority: 1,
providerSpecificData: {
excludedModels: ["gpt-4o*"],
},
});
const allowed = await seedConnection("openai", {
name: "allowed-second",
priority: 2,
apiKey: "sk-allowed",
});
const selected = await auth.getProviderCredentials("openai", null, null, "gpt-4o-mini");
assert.equal(selected.connectionId, allowed.id);
assert.notEqual(selected.connectionId, excluded.id);
});
test("getProviderCredentialsWithQuotaPreflight returns allRateLimited when a forced connection is blocked by preflight", async () => {
const blocked = await seedConnection("openai", {
name: "quota-preflight-forced",
@@ -237,6 +203,62 @@ test("getProviderCredentialsWithQuotaPreflight returns allRateLimited when a for
assert.match(selected.lastError, /quota preflight/i);
});
test("getProviderCredentials keeps separate codex affinity per session", async () => {
await settingsDb.updateSettings({ fallbackStrategy: "round-robin", stickyRoundRobinLimit: 10 });
const first = await seedConnection("codex", {
name: "codex-affinity-a",
lastUsedAt: new Date(Date.now() - 20_000).toISOString(),
});
const second = await seedConnection("codex", {
name: "codex-affinity-b",
lastUsedAt: new Date(Date.now() - 10_000).toISOString(),
});
const sessionA1 = await auth.getProviderCredentials("codex", null, null, "gpt-5.5", {
sessionKey: "session-a",
});
const sessionB1 = await auth.getProviderCredentials("codex", null, null, "gpt-5.5", {
sessionKey: "session-b",
});
const sessionA2 = await auth.getProviderCredentials("codex", null, null, "gpt-5.5", {
sessionKey: "session-a",
});
const sessionB2 = await auth.getProviderCredentials("codex", null, null, "gpt-5.5", {
sessionKey: "session-b",
});
assert.equal(sessionA1.connectionId, first.id);
assert.equal(sessionB1.connectionId, second.id);
assert.equal(sessionA2.connectionId, first.id);
assert.equal(sessionB2.connectionId, second.id);
});
test("getProviderCredentials rebinds codex session when affinity connection is excluded", async () => {
await settingsDb.updateSettings({ fallbackStrategy: "round-robin", stickyRoundRobinLimit: 10 });
const first = await seedConnection("codex", {
name: "codex-affinity-excluded-a",
lastUsedAt: new Date(Date.now() - 20_000).toISOString(),
});
const second = await seedConnection("codex", {
name: "codex-affinity-excluded-b",
lastUsedAt: new Date(Date.now() - 10_000).toISOString(),
});
const initial = await auth.getProviderCredentials("codex", null, null, "gpt-5.5", {
sessionKey: "session-excluded",
});
const rebound = await auth.getProviderCredentials("codex", first.id, null, "gpt-5.5", {
sessionKey: "session-excluded",
});
const sticky = await auth.getProviderCredentials("codex", null, null, "gpt-5.5", {
sessionKey: "session-excluded",
});
assert.equal(initial.connectionId, first.id);
assert.equal(rebound.connectionId, second.id);
assert.equal(sticky.connectionId, second.id);
});
test("resolveQuotaLimitPolicy normalizes Codex windows, thresholds, and defaults", () => {
const normalized = auth.resolveQuotaLimitPolicy("codex", {
limitPolicy: {
@@ -264,7 +286,7 @@ test("resolveQuotaLimitPolicy normalizes Codex windows, thresholds, and defaults
});
assert.deepEqual(defaults, {
enabled: true,
thresholdPercent: 99,
thresholdPercent: 90,
windows: ["session", "weekly"],
});
assert.deepEqual(generic, {
@@ -314,17 +336,17 @@ test("getProviderCredentials round-robin stays on the current account while belo
priority: 2,
});
await providersDb.updateProviderConnection((current as any).id, {
await providersDb.updateProviderConnection(current.id, {
lastUsedAt: new Date().toISOString(),
consecutiveUseCount: 1,
});
await providersDb.updateProviderConnection((other as any).id, {
await providersDb.updateProviderConnection(other.id, {
lastUsedAt: new Date(Date.now() - 60_000).toISOString(),
consecutiveUseCount: 0,
});
const selected = await auth.getProviderCredentials("openai");
const updated = await providersDb.getProviderConnectionById((current as any).id);
const updated = await providersDb.getProviderConnectionById(current.id);
assert.equal(selected.connectionId, current.id);
assert.equal(updated.consecutiveUseCount, 2);
@@ -428,7 +450,7 @@ test("getProviderCredentials retains terminal accounts for combo live tests", as
const bypassed = await auth.getProviderCredentials("openai", null, null, null, {
allowSuppressedConnections: true,
});
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(blocked, null);
assert.equal(bypassed.connectionId, connection.id);
@@ -469,15 +491,9 @@ test("getProviderCredentials reports allRateLimited when every account is model-
name: "gemini-model-lock-second",
});
await auth.markAccountUnavailable(first.id, 429, "too many requests", "gemini", "gemini-2.5-pro");
await auth.markAccountUnavailable(
(first as any).id,
429,
"too many requests",
"gemini",
"gemini-2.5-pro"
);
await auth.markAccountUnavailable(
(second as any).id,
second.id,
429,
"too many requests",
"gemini",
@@ -504,7 +520,7 @@ test("getProviderCredentials auto-decays stale backoff metadata for recovered ac
const selected = await auth.getProviderCredentials("openai");
await flushWrites();
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(selected.connectionId, connection.id);
assert.equal(updated.backoffLevel, 0);
@@ -522,7 +538,7 @@ test("getProviderCredentials falls back to a five-minute retry window when quota
},
});
quotaCache.setQuotaCache((connection as any).id, "openai", {
quotaCache.setQuotaCache(connection.id, "openai", {
daily: { remainingPercentage: 0, resetAt: null },
});
@@ -547,10 +563,10 @@ test("getProviderCredentials prioritizes accounts that still have quota availabl
apiKey: "sk-available",
});
quotaCache.setQuotaCache((exhausted as any).id, "openai", {
quotaCache.setQuotaCache(exhausted.id, "openai", {
daily: { remainingPercentage: 0, resetAt: futureIso() },
});
quotaCache.setQuotaCache((available as any).id, "openai", {
quotaCache.setQuotaCache(available.id, "openai", {
daily: { remainingPercentage: 65, resetAt: futureIso() },
});
@@ -574,17 +590,17 @@ test("getProviderCredentials round-robin switches to the least recently used acc
priority: 2,
});
await providersDb.updateProviderConnection((current as any).id, {
await providersDb.updateProviderConnection(current.id, {
lastUsedAt: new Date().toISOString(),
consecutiveUseCount: 2,
});
await providersDb.updateProviderConnection((fallback as any).id, {
await providersDb.updateProviderConnection(fallback.id, {
lastUsedAt: new Date(Date.now() - 120_000).toISOString(),
consecutiveUseCount: 0,
});
const selected = await auth.getProviderCredentials("openai");
const updated = await providersDb.getProviderConnectionById((fallback as any).id);
const updated = await providersDb.getProviderConnectionById(fallback.id);
assert.equal(selected.connectionId, fallback.id);
assert.equal(updated.consecutiveUseCount, 1);
@@ -604,17 +620,17 @@ test("getProviderCredentials round-robin fallback mode excludes the failed accou
priority: 2,
});
await providersDb.updateProviderConnection((failed as any).id, {
await providersDb.updateProviderConnection(failed.id, {
lastUsedAt: new Date().toISOString(),
consecutiveUseCount: 3,
});
await providersDb.updateProviderConnection((fallback as any).id, {
await providersDb.updateProviderConnection(fallback.id, {
lastUsedAt: new Date(Date.now() - 120_000).toISOString(),
consecutiveUseCount: 0,
});
const selected = await auth.getProviderCredentials("openai" as any, (failed as any).id);
const updated = await providersDb.getProviderConnectionById((fallback as any).id);
const selected = await auth.getProviderCredentials("openai", failed.id);
const updated = await providersDb.getProviderConnectionById(fallback.id);
assert.equal(selected.connectionId, fallback.id);
assert.equal(updated.consecutiveUseCount, 1);
@@ -644,10 +660,10 @@ test("getProviderCredentials least-used prefers accounts that were never used",
name: "least-used-never",
priority: 9,
});
await providersDb.updateProviderConnection((recentlyUsed as any).id, {
await providersDb.updateProviderConnection(recentlyUsed.id, {
lastUsedAt: new Date().toISOString(),
});
await providersDb.updateProviderConnection((neverUsed as any).id, {
await providersDb.updateProviderConnection(neverUsed.id, {
lastUsedAt: null,
});
@@ -668,10 +684,10 @@ test("getProviderCredentials least-used prefers the oldest timestamp when all ac
priority: 1,
});
await providersDb.updateProviderConnection((oldest as any).id, {
await providersDb.updateProviderConnection(oldest.id, {
lastUsedAt: new Date(Date.now() - 120_000).toISOString(),
});
await providersDb.updateProviderConnection((newest as any).id, {
await providersDb.updateProviderConnection(newest.id, {
lastUsedAt: new Date().toISOString(),
});
@@ -723,10 +739,10 @@ test("getProviderCredentials p2c prefers the account with more quota headroom ov
},
});
(quotaCache as any).setQuotaCache(nearLimit.id, "openai", {
quotaCache.setQuotaCache(nearLimit.id, "openai", {
daily: { remainingPercentage: 12, resetAt: futureIso(180_000) },
});
(quotaCache as any).setQuotaCache(healthy.id, "openai", {
quotaCache.setQuotaCache(healthy.id, "openai", {
daily: { remainingPercentage: 78, resetAt: futureIso(180_000) },
});
@@ -786,7 +802,7 @@ test("getProviderCredentials exposes copilotToken when present in providerSpecif
assert.equal(selected.copilotToken, "copilot-token-value");
});
test("markAccountUnavailable keeps local 404 failures model-scoped with the local not-found cooldown", async () => {
test("markAccountUnavailable uses configured cooldowns for local 404 model lockouts", async () => {
await settingsDb.updateSettings({
providerProfiles: {
apikey: {
@@ -807,13 +823,13 @@ test("markAccountUnavailable keeps local 404 failures model-scoped with the loca
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
404,
"model not found",
"openai",
"local-model"
);
const updated = await (providersDb as any).getProviderConnectionById(connection.id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 250);
@@ -829,14 +845,14 @@ test("markAccountUnavailable applies a model-only lockout for Gemini 429 respons
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
429,
"too many requests",
"gemini",
"gemini-2.5-pro"
);
await flushWrites();
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.ok(result.cooldownMs > 0);
@@ -852,14 +868,14 @@ test("markAccountUnavailable applies a model-only lockout for compatible provide
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
429,
"The upstream compatible service exhausted its capacity",
"openai-compatible-custom-node",
"custom-model-a"
);
await flushWrites();
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.ok(result.cooldownMs > 0);
@@ -869,7 +885,7 @@ test("markAccountUnavailable applies a model-only lockout for compatible provide
assert.equal(Number(updated.errorCode), 429);
});
test("markAccountUnavailable uses the unified configured api-key connection cooldown", async () => {
test("markAccountUnavailable honors configured api-key rate-limit cooldowns", async () => {
await settingsDb.updateSettings({
providerProfiles: {
apikey: {
@@ -887,7 +903,7 @@ test("markAccountUnavailable uses the unified configured api-key connection cool
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
429,
"too many requests",
"openai",
@@ -909,20 +925,20 @@ test("markAccountUnavailable stores Codex scope-specific cooldowns without a glo
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
429,
"quota reached",
"codex",
"codex-spark-mini"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
const selected = await auth.getProviderCredentials("codex", null, null, "codex-spark-mini");
assert.equal(result.shouldFallback, true);
assert.ok(result.cooldownMs > 0);
assert.equal(updated.testStatus, "unavailable");
assert.equal(updated.rateLimitedUntil, undefined);
assert.ok((updated.providerSpecificData as any).codexScopeRateLimitedUntil.spark);
assert.ok(updated.providerSpecificData.codexScopeRateLimitedUntil.spark);
assert.equal(selected.allRateLimited, true);
});
@@ -932,13 +948,13 @@ test("markAccountUnavailable returns without fallback on bad requests", async ()
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
400,
"schema mismatch",
"openai",
"gpt-4o"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.deepEqual(result, { shouldFallback: false, cooldownMs: 0 });
assert.equal(updated.testStatus, "active");
@@ -952,13 +968,8 @@ test("markAccountUnavailable preserves terminal statuses without overwriting the
rateLimitedUntil: null,
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
503,
"upstream error",
"openai"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const result = await auth.markAccountUnavailable(connection.id, 503, "upstream error", "openai");
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.equal(result.cooldownMs, 0);
@@ -973,13 +984,8 @@ test("markAccountUnavailable reuses an existing connection-wide cooldown", async
rateLimitedUntil: retryAfter,
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
503,
"upstream error",
"openai"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const result = await auth.markAccountUnavailable(connection.id, 503, "upstream error", "openai");
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.ok(result.cooldownMs > 0);
@@ -1003,21 +1009,18 @@ test("markAccountUnavailable reuses an existing Codex scope cooldown", async ()
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
429,
"quota reached",
"codex",
"codex-spark-mini"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.ok(result.cooldownMs > 0);
assert.equal(updated.rateLimitedUntil, undefined);
(assert as any).equal(
(updated.providerSpecificData as any).codexScopeRateLimitedUntil.spark,
retryAfter
);
assert.equal(updated.providerSpecificData.codexScopeRateLimitedUntil.spark, retryAfter);
});
test("markAccountUnavailable uses a connection-wide cooldown for non-local 404 errors", async () => {
@@ -1029,13 +1032,13 @@ test("markAccountUnavailable uses a connection-wide cooldown for non-local 404 e
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
404,
"model not found",
"openai",
"gpt-missing"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.ok(result.cooldownMs > 0);
@@ -1050,17 +1053,17 @@ test("markAccountUnavailable auto-disables permanently banned accounts when the
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
401,
"Verify your account to continue",
"openai",
"gpt-4o"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.equal(updated.isActive, false);
assert.equal(updated.testStatus, "banned");
assert.equal(updated.testStatus, "unavailable");
});
test("markAccountUnavailable leaves permanently banned accounts active when auto-disable is disabled", async () => {
@@ -1070,17 +1073,17 @@ test("markAccountUnavailable leaves permanently banned accounts active when auto
});
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
401,
"Verify your account to continue",
"openai",
"gpt-4o"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.equal(updated.isActive, true);
assert.equal(updated.testStatus, "banned");
assert.equal(updated.testStatus, "unavailable");
});
test("markAccountUnavailable swallows auto-disable persistence errors", async () => {
@@ -1114,17 +1117,17 @@ test("markAccountUnavailable swallows auto-disable persistence errors", async ()
try {
const result = await auth.markAccountUnavailable(
(connection as any).id,
connection.id,
401,
"Verify your account to continue",
"openai",
"gpt-4o"
);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
const updated = await providersDb.getProviderConnectionById(connection.id);
assert.equal(result.shouldFallback, true);
assert.equal(updated.isActive, true);
assert.equal(updated.testStatus, "banned");
assert.equal(updated.testStatus, "unavailable");
} finally {
db.prepare = originalPrepare;
}