diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 6d9ef3f219..255c7eeb5e 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1715,6 +1715,11 @@ "count": 2 } }, + "tests/unit/oauth-refresh-connection-dedup-8059.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, "tests/unit/observability-fase04.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index c10ade929f..0f43303e81 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -591,18 +591,7 @@ export function recordModelLockoutFailure( options: { exactCooldownMs?: number | null; maxCooldownMs?: number; - /** - * #6863 vs #7940: set true only when `exactCooldownMs` was parsed/verified from - * an actual upstream signal (Retry-After header, X-RateLimit-Reset, or a reset - * parsed from the error body — i.e. `usedUpstreamRetryHint`/`quotaResetHintMs` - * from `checkFallbackError`). A verified reset is honored exactly, even past - * `maxCooldownMs` — a real "Resets in 92h" must not be clamped down to minutes, - * or the router hammers 429 against quota that is known not to come back. - * Leave false/omitted for SYNTHETIC estimates (e.g. the quota_exhausted - * until-midnight default below, or plain exponential backoff) — those stay - * capped, per #7940. - */ - exactCooldownVerified?: boolean; + exactCooldownIsUpstreamReset?: boolean; } = {} ) { ensureCleanupTimer(); @@ -626,19 +615,18 @@ export function recordModelLockoutFailure( const failureCount = withinWindow ? previous.failureCount + 1 : 1; const baseCooldownMs = getModelLockBaseCooldown(status, fallbackCooldownMs, profile); - // Cap exponential backoff and SYNTHETIC exact cooldowns (e.g. the daily-quota - // until-midnight heuristic below) against maxCooldownMs so user-configured caps - // are honored (#7940). A caller-VERIFIED exact cooldown (#6863 — parsed from an - // actual upstream Retry-After/reset signal, see `exactCooldownVerified` above) - // bypasses the cap instead of being clamped to a window the upstream already - // told us is wrong. + // Cap both exponential backoff and computed exact cooldowns (e.g. daily-quota + // until-midnight, #7940/#7980) against maxCooldownMs so user-configured caps are + // honored — EXCEPT an authoritative parsed upstream reset (#6863, e.g. Antigravity + // "Resets in 92h27m28s"), which the upstream told us to wait and must be honored + // exactly, never clamped down to maxCooldownMs. const maxCooldownMs = typeof options.maxCooldownMs === "number" && options.maxCooldownMs > 0 ? options.maxCooldownMs : null; const cooldownMs = typeof options.exactCooldownMs === "number" && options.exactCooldownMs > 0 - ? maxCooldownMs !== null && !options.exactCooldownVerified + ? maxCooldownMs !== null && !options.exactCooldownIsUpstreamReset ? Math.min(options.exactCooldownMs, maxCooldownMs) : options.exactCooldownMs : Math.min( diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index e76bd3e4e9..58a74cfebc 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -2425,7 +2425,10 @@ export async function handleComboChat({ // upstream reset (lockoutHintVerified) bypasses it. exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), maxCooldownMs: mlSettings.maxCooldownMs, - exactCooldownVerified: lockoutHintVerified, + // #6863: a parsed upstream quota reset is authoritative — the upstream + // told us exactly when it resets, so honor it in full instead of + // clamping to maxCooldownMs (which only bounds computed backoff). + exactCooldownIsUpstreamReset: lockoutHintMs > mlSettings.baseCooldownMs, } ); lockoutRecorded = true; @@ -2479,7 +2482,9 @@ export async function handleComboChat({ // upstream reset (lockoutHintVerified) bypasses it. exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), maxCooldownMs: mlSettings.maxCooldownMs, - exactCooldownVerified: lockoutHintVerified, + // #6863: an authoritative parsed upstream reset must be honored in full, + // never clamped to maxCooldownMs (which only bounds computed backoff). + exactCooldownIsUpstreamReset: lockoutHintMs > mlSettings.baseCooldownMs, } ); } diff --git a/open-sse/utils/reasoningPlaceholder.ts b/open-sse/utils/reasoningPlaceholder.ts index c552301cdb..915af48c92 100644 --- a/open-sse/utils/reasoningPlaceholder.ts +++ b/open-sse/utils/reasoningPlaceholder.ts @@ -12,18 +12,15 @@ export function isInternalReasoningPlaceholder(value: unknown): boolean { /** * Strip the internal placeholder from user-visible content. Models sometimes * echo the sentinel through ordinary `message.content` / `delta.content` - * (#8081). Removes all occurrences and trims; returns "" when nothing - * meaningful remains so callers can skip emission entirely. + * (#8081). Removes all occurrences; returns "" when only whitespace remains so + * callers can skip emission entirely. * - * The trim only applies when the sentinel was actually present. This is - * called per streaming `delta.content` chunk, not on the fully-assembled - * message — tokenizers routinely emit sub-word tokens with a leading space - * as part of the token (e.g. " en", " riktig"), so unconditionally trimming - * every chunk silently ate the space between words for the (overwhelming) - * majority of chunks that never contain the sentinel at all, producing - * streamed text with words run together ("Bilden är en" -> "Bildenären"). + * IMPORTANT (#5786): this runs per-delta on the streaming path, where a delta's + * leading/trailing spaces are meaningful (e.g. "Hello, " + "world." + " Bye."). + * Only collapse to "" when the placeholder WAS the whole content — never trim + * real content, or streamed deltas glue together with their spaces eaten. */ export function stripInternalReasoningPlaceholder(value: string): string { - if (!value.includes(NON_ANTHROPIC_THINKING_PLACEHOLDER)) return value; - return value.replaceAll(NON_ANTHROPIC_THINKING_PLACEHOLDER, "").trim(); + const stripped = value.replaceAll(NON_ANTHROPIC_THINKING_PLACEHOLDER, ""); + return stripped.trim() === "" ? "" : stripped; } diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 3eaeb7a737..0d3906acc8 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -2064,13 +2064,6 @@ export async function markAccountUnavailable( ? fallbackResult.cooldownMs : (fallbackResult.quotaResetHintMs ?? null), maxCooldownMs: mlSettings.maxCooldownMs, - // #6863 vs #7940: exactCooldownMs above is only ever set from a genuine - // upstream signal (Retry-After/reset header or a parsed quotaResetHintMs) — - // never a synthetic estimate — so it must bypass maxCooldownMs instead of - // being clamped down to a window the upstream already told us is wrong. - exactCooldownVerified: - fallbackResult.usedUpstreamRetryHint === true || - typeof fallbackResult.quotaResetHintMs === "number", } ); // Update last error for observability (without changing terminal status) @@ -2140,10 +2133,6 @@ export async function markAccountUnavailable( exactCooldownMs: fallbackResult.usedUpstreamRetryHint === true ? fallbackResult.cooldownMs : null, maxCooldownMs: mlSettings.maxCooldownMs, - // #6863 vs #7940: only a genuine upstream retry hint bypasses maxCooldownMs; - // absent a hint, exactCooldownMs above is null and this falls through to the - // (still-capped) exponential-backoff branch. - exactCooldownVerified: fallbackResult.usedUpstreamRetryHint === true, } ); updateProviderConnection(connectionId, { diff --git a/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts b/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts index dc8782b993..8b1ee022eb 100644 --- a/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts +++ b/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts @@ -80,7 +80,7 @@ test("SPAWN_CAPABLE_PREFIXES is defined in the server-free constants leaf with t "/api/skills/collect/", "/api/headroom/start", "/api/headroom/stop", - "/api/vnc-session", // #7892: spawns Docker containers via child_process.spawn + "/api/vnc-session", ]) { assert.ok( SPAWN_CAPABLE_PREFIXES.includes(prefix),