mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
rdself flagged in #2116 that the per-failure-kind breaker cooldown lacks a user-controlled switch and should default conservatively for reverse-proxy / CLI-backed providers where forwarded 429 metadata is unreliable. This PR adds the toggle, the per-provider default policy, and surfaces it in the Resilience settings UI. Why --- A provider routed through cliproxyapi / lm-studio / vllm / etc may produce 429 metadata that the upstream layer fabricated. Letting that drive circuit-breaker cooldown duration is unsafe by default. Direct cloud providers (openai, anthropic, groq, cerebras, mistral, gemini, etc.) are the safe ones to trust. What ---- - New helper src/shared/utils/providerHints.ts: defaultUseUpstream429BreakerHints(providerId) returns false for the UPSTREAM_PROXY_PROVIDERS / SELF_HOSTED_CHAT_PROVIDER_IDS / isLocalProvider / isClaudeCodeCompatibleProvider sets; true for everything else. resolveUseUpstream429BreakerHints() picks user override when set, otherwise the per-provider default. - Schema: connectionCooldownProfileSchema gains useUpstream429BreakerHints: z.boolean().nullable().optional(). null is the explicit unset sentinel. - Settings layer: ConnectionCooldownProfileSettings adds useUpstream429BreakerHints?: boolean. Normalize preserves undefined (no toBoolean coercion) and treats null as "delete the key". The unset state survives all partial-merge round-trips. JSON serialization omits the key when undefined. - API route: PATCH /api/resilience detects useUpstream429BreakerHints transitions (stored override change in either oauth or apikey profile) and calls resetAllCircuitBreakers() so the registry stops serving cached options. - UI: ConnectionCooldownCard renderProfile gains a tri-state \<select\> with options Default (per provider) / Always on / Always off. Read-only rendering mirrors. Save handler converts undefined → null before PATCH so JSON.stringify does not drop the key. - Three wire-up call sites pass cooldownByKind + classifyError to getCircuitBreaker only when useHints === true: * open-sse/services/accountFallback.ts (configureProviderBreaker) * src/sse/handlers/chat.ts (~L516) * src/sse/handlers/chatHelpers.ts (~L151) - classify429.ts gains classify429FromError(err) adapter that maps the common HTTP-error shapes (axios-style err.response.status, low-level err.status, message fallback) to FailureKind so callers can use it directly as the breaker's classifyError option. Backwards compatibility ----------------------- Default behaviour is byte-identical when neither schema field nor user override is touched, since useUpstream429BreakerHints stays undefined and the helper returns the same per-provider policy that the breaker would have applied with cooldownByKind unset. Existing code paths that never construct a breaker with this option see no change. Tests ----- 39 tests pass across 4 unit suites: - tests/unit/provider-hints.test.ts (6 tests): per-provider defaults for cloud / cliproxyapi / self-hosted / claude-code prefix; user override truth-table in both directions including the v1 regression test where proxy-with-override-true must resolve to true. - tests/unit/resilience-settings-upstream429-breaker.test.ts (7 tests): defaults absent; explicit boolean stored; null sentinel deletes the key; key absent from JSON after delete; partial-merge omitting key leaves existing value; toBoolean coercion explicitly avoided; mixed-provider round-trip preserves disjoint per-profile settings. - tests/unit/classify429.test.ts (carried from #2116, still passes). - tests/unit/circuit-breaker-failure-kind.test.ts (carried, still passes). Iteration history (codex audits) -------------------------------- This plan went through 5 codex review rounds: - v1 → 5 concerns (default-vs-gate logic, naming, missed third call site, accountFallback layer separation, compat surface scope) - v2 → HIGH: normalization could swallow per-provider default; 4 minor - v3 → HIGH: binary BooleanField could not represent the unset state - v4 → HIGH: PATCH partial-merge semantics — omitted key ≠ unset - v5 → APPROVED with the null sentinel pattern Audit trail and grep output for getCircuitBreaker call sites are in the PR description. Open questions for the maintainer (see PR body) ----------------------------------------------- 1. Hard gate vs default for proxy/CLI providers (v5 ships default-off, user can override). 2. Cooldown values when toggle on are hardcoded for v1. 3. Reset granularity: resetAllCircuitBreakers() is coarse-grained but matches existing patterns; per-profile reset is a follow-up. 4. The 3 getCircuitBreaker-with-options call sites duplicate logic — a separate DRY refactor PR is suggested.