diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 174969573b..21d269d137 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1182,7 +1182,7 @@ }, "tests/unit/combo-routing-engine.test.ts": { "@typescript-eslint/no-explicit-any": { - "count": 269 + "count": 271 } }, "tests/unit/combo-same-provider-cascade.test.ts": { @@ -1750,6 +1750,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 @@ -2445,4 +2450,4 @@ "count": 5 } } -} \ No newline at end of file +} diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index c24c836e45..e0f3f5a797 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -573,7 +573,11 @@ export function recordModelLockoutFailure( status: number, fallbackCooldownMs: number, profile: ProviderProfile | null = null, - options: { exactCooldownMs?: number | null; maxCooldownMs?: number } = {} + options: { + exactCooldownMs?: number | null; + maxCooldownMs?: number; + exactCooldownIsUpstreamReset?: boolean; + } = {} ) { ensureCleanupTimer(); const key = getModelLockKey(provider, connectionId, model, reason, status); @@ -596,15 +600,18 @@ export function recordModelLockoutFailure( const failureCount = withinWindow ? previous.failureCount + 1 : 1; const baseCooldownMs = getModelLockBaseCooldown(status, fallbackCooldownMs, profile); - // Cap both exponential backoff and exact cooldowns (e.g. daily-quota - // until-midnight) against maxCooldownMs so user-configured caps are honored. + // 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 + ? 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 1a4b88de30..f5e207b719 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -2370,6 +2370,10 @@ export async function handleComboChat({ // the short base cooldown / exponential backoff when present. exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), maxCooldownMs: mlSettings.maxCooldownMs, + // #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; @@ -2417,6 +2421,9 @@ export async function handleComboChat({ // #1308/#6863: honor a long upstream reset over base/exponential cooldown. exactCooldownMs: selectLockoutCooldownMs(lockoutHintMs, mlSettings), maxCooldownMs: mlSettings.maxCooldownMs, + // #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 1531c9cb00..915af48c92 100644 --- a/open-sse/utils/reasoningPlaceholder.ts +++ b/open-sse/utils/reasoningPlaceholder.ts @@ -12,9 +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. + * + * 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 { - return value.replaceAll(NON_ANTHROPIC_THINKING_PLACEHOLDER, "").trim(); + const stripped = value.replaceAll(NON_ANTHROPIC_THINKING_PLACEHOLDER, ""); + return stripped.trim() === "" ? "" : stripped; } 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 2522c659a1..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,11 +80,12 @@ 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", ]) { assert.ok( SPAWN_CAPABLE_PREFIXES.includes(prefix), `SPAWN_CAPABLE_PREFIXES lost the spawn-capable prefix "${prefix}" during extraction` ); } - assert.equal(SPAWN_CAPABLE_PREFIXES.length, 10); + assert.equal(SPAWN_CAPABLE_PREFIXES.length, 11); });