mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
fix(providers): enrich model_cooldown 429 body with retry_after ISO + credential count (#6460) (#6523)
fix(providers): enrich model_cooldown 429 body with retry_after ISO + credential count (#6460). Integrated into release/v3.8.47. (thanks @chirag127)
This commit is contained in:
@@ -259,8 +259,8 @@
|
||||
"src/shared/validation/schemas.ts": 2523,
|
||||
"_rebaseline_2026_06_28_5275_correlation_id_extract": "Extraction of the safe CorrelationId subset of #5275 (hartmark) — request correlation id stored in call_logs (migration 109) and returned via the X-Correlation-Id response header, WITHOUT the combo/resilience or build/lazy-loading changes (those stay in #5275). Own growth: callLogs.ts 975->985 (correlation_id column on CallLogSummaryRow + read/map), usageHistory.ts 983->988 (correlationId metadata normalize), chat.ts 1575->1632 (withCorrelationId response wiring + combo-failure log carrying correlationId), chatHelpers.ts new 811 (withCorrelationId helper + reqId threading; was 791<cap pre-feature). Cohesive request/logging chokepoint wiring; structural shrink of chat.ts tracked in #3501.",
|
||||
"src/sse/handlers/chat.ts": 1751,
|
||||
"src/sse/handlers/chatHelpers.ts": 860,
|
||||
"src/sse/services/auth.ts": 2447,
|
||||
"src/sse/handlers/chatHelpers.ts": 866,
|
||||
"src/sse/services/auth.ts": 2448,
|
||||
"open-sse/executors/default.ts": 877,
|
||||
"open-sse/translator/request/openai-responses.ts": 902,
|
||||
"open-sse/executors/kiro.ts": 944,
|
||||
@@ -381,5 +381,6 @@
|
||||
"_rebaseline_2026_07_06_v3845_release_close": "Release v3.8.45 cycle-close rebaseline (captain, sess ce897453): 13 files grown by the cycle's merged fix/feature PRs (#6216 streaming fixes + request-logger UI grew RequestLoggerV2/chat/chatHelpers/auth/stream/response-sanitizer.test; #6251/#6253 dashboard UX grew combos page/modals/wizard/ComboDefaultsTab/ProxyRegistryManager/providerPageHelpers). Growth is legitimate merged-feature code, absorbed at release per Phase 0 drift policy; all remain frozen (cannot grow further).",
|
||||
"_rebaseline_2026_07_06_6118_zed_oauthmodal": "PR #6118 own growth: OAuthModal.tsx 989->993 (+4 = Zed hosted native-app sign-in modal branch). Cohesive UI growth for the zed-hosted OAuth provider; not extractable. The prior 6118 comment set the note but left the frozen value at 989.",
|
||||
"_rebaseline_2026_07_06_6351_glm_team_quota": "PR #6351 own growth (GLM team-plan quota fields threaded through the connection modals; new GlmTeamQuotaFields.tsx extracted): AddApiKeyModal.tsx ->951 (+9), EditConnectionModal.tsx ->1277 (+18). Absorbs the pre-existing session base-red on these frozen modals; release captain rebaseline-at-release supersedes.",
|
||||
"_rebaseline_2026_07_06_6499_unique_default_name": "PR #6499 own growth: AddApiKeyModal.tsx 952->959 (+7 = a unique default connection name so a second API key for the same provider does not reuse 'main' and trigger the backend name-based upsert that silently overwrote the first connection). The pure name derivation was extracted to computeConnectionDefaultName.ts (unit-tested) to keep the growth minimal; the contributor's original full-form-reset rewrite was trimmed to a spread reset to avoid dropping the GLM team-quota fields #6351 added and to hold the frozen god-file growth down. Release captain rebaseline-at-release supersedes."
|
||||
}
|
||||
"_rebaseline_2026_07_06_6499_unique_default_name": "PR #6499 own growth: AddApiKeyModal.tsx 952->959 (+7 = a unique default connection name so a second API key for the same provider does not reuse 'main' and trigger the backend name-based upsert that silently overwrote the first connection). The pure name derivation was extracted to computeConnectionDefaultName.ts (unit-tested) to keep the growth minimal; the contributor's original full-form-reset rewrite was trimmed to a spread reset to avoid dropping the GLM team-quota fields #6351 added and to hold the frozen god-file growth down. Release captain rebaseline-at-release supersedes.",
|
||||
"_rebaseline_2026_07_07_6523_chirag_cooldown_body": "PR #6523 (@chirag127, #6460) own growth: chatHelpers.ts 860->866 (+6 = retryAfterAt/credentialsCoolingCount fields on modelCooldownResponse) and auth.ts 2447->2448 (+1 = connectionsCount threaded through no-credentials fallback). Owner-approved rebaseline (file-size cap for contributor PR). Frozen (cannot grow further); release captain's rebaseline-at-release supersedes."
|
||||
}
|
||||
@@ -408,11 +408,23 @@ export function providerCircuitOpenResponse(
|
||||
export function buildModelCooldownBody({
|
||||
model,
|
||||
retryAfterSec,
|
||||
retryAfterAt,
|
||||
credentialsCoolingCount,
|
||||
}: {
|
||||
model?: string | null;
|
||||
retryAfterSec: number;
|
||||
retryAfterAt?: string | null;
|
||||
credentialsCoolingCount?: number | null;
|
||||
}): ModelCooldownErrorPayload {
|
||||
const resolvedModel = typeof model === "string" && model.trim().length > 0 ? model.trim() : null;
|
||||
const resolvedRetryAfterAt =
|
||||
typeof retryAfterAt === "string" && retryAfterAt.length > 0 ? retryAfterAt : null;
|
||||
const resolvedCoolingCount =
|
||||
typeof credentialsCoolingCount === "number" &&
|
||||
Number.isFinite(credentialsCoolingCount) &&
|
||||
credentialsCoolingCount > 0
|
||||
? Math.floor(credentialsCoolingCount)
|
||||
: null;
|
||||
|
||||
return {
|
||||
error: {
|
||||
@@ -423,6 +435,8 @@ export function buildModelCooldownBody({
|
||||
code: "model_cooldown",
|
||||
...(resolvedModel ? { model: resolvedModel } : {}),
|
||||
reset_seconds: Math.max(Math.ceil(retryAfterSec), 1),
|
||||
...(resolvedRetryAfterAt ? { retry_after: resolvedRetryAfterAt } : {}),
|
||||
...(resolvedCoolingCount ? { credentials_cooling: resolvedCoolingCount } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -430,16 +444,28 @@ export function buildModelCooldownBody({
|
||||
export function modelCooldownResponse({
|
||||
model,
|
||||
retryAfter,
|
||||
retryAfterAt,
|
||||
credentialsCoolingCount,
|
||||
}: {
|
||||
model?: string | null;
|
||||
retryAfter?: string | number | Date | null;
|
||||
retryAfterAt?: string | null;
|
||||
credentialsCoolingCount?: number | null;
|
||||
}) {
|
||||
const retryAfterSec = normalizeRetryAfterSeconds(retryAfter);
|
||||
const resolvedRetryAfterAt =
|
||||
typeof retryAfterAt === "string" && retryAfterAt.length > 0
|
||||
? retryAfterAt
|
||||
: typeof retryAfter === "string" && retryAfter.length > 0
|
||||
? retryAfter
|
||||
: null;
|
||||
return new Response(
|
||||
JSON.stringify(
|
||||
buildModelCooldownBody({
|
||||
model,
|
||||
retryAfterSec,
|
||||
retryAfterAt: resolvedRetryAfterAt,
|
||||
credentialsCoolingCount,
|
||||
})
|
||||
),
|
||||
{
|
||||
|
||||
@@ -577,6 +577,12 @@ export function handleNoCredentials(
|
||||
return modelCooldownResponse({
|
||||
model: cooldownModel,
|
||||
retryAfter: credentials.retryAfter,
|
||||
retryAfterAt:
|
||||
typeof credentials.retryAfter === "string" ? credentials.retryAfter : null,
|
||||
credentialsCoolingCount:
|
||||
typeof credentials.connectionsCount === "number"
|
||||
? credentials.connectionsCount
|
||||
: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1377,6 +1377,7 @@ export async function getProviderCredentials(
|
||||
lastErrorCode: allBlockedByModelCooldown ? 429 : earliestConn?.errorCode || null,
|
||||
cooldownScope: allBlockedByModelCooldown ? "model" : "connection",
|
||||
cooldownModel: allBlockedByModelCooldown ? requestedModel : null,
|
||||
connectionsCount: connections.length,
|
||||
};
|
||||
}
|
||||
const syntheticFallback = await maybeSyntheticNoAuthFallback(
|
||||
|
||||
@@ -5,5 +5,7 @@ export interface ModelCooldownErrorPayload {
|
||||
code: "model_cooldown";
|
||||
model?: string;
|
||||
reset_seconds: number;
|
||||
retry_after?: string;
|
||||
credentials_cooling?: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,3 +14,71 @@ test("types barrel supports the model cooldown error payload consumer", async ()
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("model cooldown body includes optional retry_after ISO + credentials_cooling count", async () => {
|
||||
const { buildModelCooldownBody } = await import("../../open-sse/utils/error.ts");
|
||||
|
||||
const iso = "2026-07-07T12:34:56.000Z";
|
||||
assert.deepEqual(
|
||||
buildModelCooldownBody({
|
||||
model: "openrouter/fusion",
|
||||
retryAfterSec: 30,
|
||||
retryAfterAt: iso,
|
||||
credentialsCoolingCount: 3,
|
||||
}),
|
||||
{
|
||||
error: {
|
||||
message: "All credentials for model openrouter/fusion are cooling down",
|
||||
type: "rate_limit_error",
|
||||
code: "model_cooldown",
|
||||
model: "openrouter/fusion",
|
||||
reset_seconds: 30,
|
||||
retry_after: iso,
|
||||
credentials_cooling: 3,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("model cooldown body omits retry_after / credentials_cooling when absent or invalid", async () => {
|
||||
const { buildModelCooldownBody } = await import("../../open-sse/utils/error.ts");
|
||||
|
||||
assert.deepEqual(
|
||||
buildModelCooldownBody({
|
||||
model: "x",
|
||||
retryAfterSec: 5,
|
||||
retryAfterAt: null,
|
||||
credentialsCoolingCount: 0,
|
||||
}),
|
||||
{
|
||||
error: {
|
||||
message: "All credentials for model x are cooling down",
|
||||
type: "rate_limit_error",
|
||||
code: "model_cooldown",
|
||||
model: "x",
|
||||
reset_seconds: 5,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("modelCooldownResponse emits HTTP 429 with Retry-After header and retry_after ISO in body (#6460)", async () => {
|
||||
const { modelCooldownResponse } = await import("../../open-sse/utils/error.ts");
|
||||
|
||||
const iso = "2026-07-07T12:34:56.000Z";
|
||||
const res = modelCooldownResponse({
|
||||
model: "openrouter/fusion",
|
||||
retryAfter: iso,
|
||||
credentialsCoolingCount: 4,
|
||||
});
|
||||
|
||||
assert.equal(res.status, 429);
|
||||
assert.ok(res.headers.get("Retry-After"), "Retry-After header must be set");
|
||||
const body = await res.json();
|
||||
assert.equal(body.error.code, "model_cooldown");
|
||||
assert.equal(body.error.type, "rate_limit_error");
|
||||
assert.equal(body.error.model, "openrouter/fusion");
|
||||
assert.equal(body.error.retry_after, iso);
|
||||
assert.equal(body.error.credentials_cooling, 4);
|
||||
assert.equal(typeof body.error.reset_seconds, "number");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user