diff --git a/changelog.d/fixes/13602-i18n-duplicated-flag-description-key.md b/changelog.d/fixes/13602-i18n-duplicated-flag-description-key.md new file mode 100644 index 000000000000..a82492e493f2 --- /dev/null +++ b/changelog.d/fixes/13602-i18n-duplicated-flag-description-key.md @@ -0,0 +1 @@ +- **fix(i18n):** drop the second copy of `featureFlagProxySkipRecentlyFailedDescription` that the 2026-09-15 batch merges left in 59 dashboard catalogs (a scripted keep-both conflict resolution concatenated the key both PRs carried; `JSON.parse` silently kept the last copy) and the duplicated `ERROR_TYPE_CONTRACT` import in `src/lib/db/callLogStats.ts` (TS2300); adds `tests/unit/i18n-catalogs-no-duplicate-keys.test.ts`, a raw-text guard that fails on any key declared twice in one object of `src/i18n/messages/*.json` or `bin/cli/locales/*.json` ([#13602](https://github.com/diegosouzapw/OmniRoute/pull/13602), [#13641](https://github.com/diegosouzapw/OmniRoute/pull/13641)) diff --git a/changelog.d/fixes/13609-mistral-401-ambiguous-auth.md b/changelog.d/fixes/13609-mistral-401-ambiguous-auth.md new file mode 100644 index 000000000000..58d4b00d5989 --- /dev/null +++ b/changelog.d/fixes/13609-mistral-401-ambiguous-auth.md @@ -0,0 +1 @@ +- **fix(providers):** opt-in `MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT` flag (default off): a bare Mistral 401 with no explicit auth signal (identical for a revoked key and an exhausted quota) cools the connection down instead of parking it as expired, at most 3 times per hour per connection before it parks, so a revoked key still converges; the ambiguity check is now one implementation shared by the connection test and the runtime ([#13609](https://github.com/diegosouzapw/OmniRoute/pull/13609)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/13615-opencode-transient-retry-delay.md b/changelog.d/fixes/13615-opencode-transient-retry-delay.md new file mode 100644 index 000000000000..fb389806e61d --- /dev/null +++ b/changelog.d/fixes/13615-opencode-transient-retry-delay.md @@ -0,0 +1 @@ +- **fix(opencode):** opt-in `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` flag (default off): once two consecutive opencode accounts fail with a transient upstream error, the rotation pauses before the next account (1.5 s doubling, capped at 6 s per pause and 10 s per request), releases the failed response body first and stops dispatching if the client disconnects during the pause; with the flag off failover stays immediate ([#13615](https://github.com/diegosouzapw/OmniRoute/pull/13615)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/13657-rate-limited-429-single-retry-drain.md b/changelog.d/fixes/13657-rate-limited-429-single-retry-drain.md new file mode 100644 index 000000000000..93f2bd016644 --- /dev/null +++ b/changelog.d/fixes/13657-rate-limited-429-single-retry-drain.md @@ -0,0 +1 @@ +- **fix(sse):** opt-in `OPENCODE_RATE_LIMITED_429_EARLY_STOP` flag (default off): an opencode 429 classified as a real rate limit (parseable `Retry-After`, or a body naming a rate/usage limit) stops the cross-account wave and returns that upstream 429 unchanged — body, `Retry-After` and quota headers intact, so the opencode quota error rules still apply; unclassified 429s keep rotating, and with the flag off every 429 rotates as before (#9611) ([#13657](https://github.com/diegosouzapw/OmniRoute/pull/13657)) — thanks @maxmad64bis diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 93eb1f9d32d2..3de72e8e616c 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_09_15_13609_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/accountFallback.ts->2507. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13602_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1214. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13580_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1202. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_13_13581_pool_egress_observation": "PR #13581 own growth: src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx 1475->1477 (+2 = the PoolEgressObservation import and its one-line mount under the pool members label). The observation itself lives outside the frozen file, all under cap: PoolEgressObservation.tsx, the dedicated GET /api/settings/proxies/pool/egress-observation route, src/lib/proxyPoolEgressObservation.ts and getPoolEgressObservation in src/lib/db/proxyLogs.ts. Only the mount point is irreducible. Covered by tests/unit/proxy-pool-egress-observation.test.ts, tests/unit/proxy-pool-egress-observation-route.test.ts and tests/unit/ui/PoolEgressObservation.test.tsx.", @@ -345,6 +346,7 @@ "_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)", "frozen": { "src/sse/handlers/chatHelpers.ts": 1214, + "_rebaseline_2026_09_15_13609_mistral_ambiguous_401": "PR #13609 rework (maxmad64bis, bare Mistral 401 soft lockout behind MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT, default off). open-sse/services/accountFallback.ts 2469->2501 (+32): +14 are the change itself (shared-predicate + flag imports, the documented ambiguousAuth field on the checkFallbackError return type, and the flag-gated 401 branch formatted normally instead of the PR's 139-char squeezed configuredRule line); +18 are the lint-staged prettier pass normalizing lines that were already unformatted on the release tip (multi-import, ISO_RETRY_RE, two regex arrays, persistAntigravityFamilyCooldownIfQuota call, applyErrorState guard, trailing commas) — pure formatting, no logic. src/sse/services/auth.ts 3556->3557 (+1): markAccountUnavailable passes connectionId to resolveTerminalConnectionStatus so the soft-strike bound is per connection. The predicate and strike tracker live in the leaf open-sse/services/accountFallback/mistralAmbiguousAuth.ts (under cap). Covered by tests/unit/provider-401-ambiguous-runtime.test.ts (flag off/on, end-to-end through markAccountUnavailable).", "_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.", "_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.", "_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, account: abc12345..."). Disabled by default so account identifiers are redacted from shared/multi-tenant process logs. Independent from Debug Mode; flipping Debug Mode does not reveal this. | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | boolean | `false` | When OIDC is enabled, disable password login so users can only authenticate via OIDC Single Sign-On. When disabled (default), both password login and OIDC are available. | -### Network (13) +### Network (15) | Key | Type | Default | Restart | Description | | ----------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -77,6 +77,8 @@ used when neither a DB override nor an environment variable is present. | `PROXY_POOL_EGRESS_OBSERVATION` | boolean | `false` | | Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h and how many connections used them. Read-only, computed from the proxy log, never used for routing. Off by default. | | `OPENCODE_RESPONSES_STALL_ROTATION` | boolean | `false` | | For the OpenCode executor, watch the first body byte of a streamed Responses reply (window: `RESPONSES_FIRST_BYTE_TIMEOUT_MS`, default `15000`). A 2xx Responses stream that stays silent past the window is treated as stalled: the account is cooled down and the request rotates to the next account once; a second stall fails fast. Off by default: stalled streams keep today's wait until the stream readiness timeout. | | `OPENCODE_USER_BLOCKED_ROTATION` | boolean | `false` | | OpenCode executor: on a 403/451 carrying a `user_blocked` refusal (not geo, not a Cloudflare fingerprint rejection), cool the refused account down and rotate to the next account at most once per request; a second refusal is returned as-is, without a success mark. Off by default: routing around an upstream user block can look like evasion and spread the flag across the fleet. | +| `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` | boolean | `false` | | OpenCode rotation: after two consecutive transient upstream failures (5xx or an empty 400), pause before the next account — 1.5s doubling per further failure, capped at 6s per pause and 10s per request, skipped on client disconnect; the failed body is released before waiting. Off by default: failover stays immediate. | +| `OPENCODE_RATE_LIMITED_429_EARLY_STOP` | boolean | `false` | | OpenCode rotation: stop the account wave at the first 429 classified as a real rate limit (parseable `Retry-After`, or a body naming a rate/usage limit) and return that upstream 429 unchanged. Unclassified 429s keep rotating. Off by default: the free tier is limited per egress IP (#9611), so every 429 rotates and an exhausted wave returns the last upstream 429. | | `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** | | `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Allow provider URLs pointing to private/internal networks. | | `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | boolean | `true` | | Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN). On by default (local-first); disable for strict public-only blocking. Cloud-metadata stays blocked. | @@ -92,7 +94,7 @@ used when neither a DB override nor an environment variable is present. | `CAPABILITY_FILTER_ENABLED` | boolean | `false` | Reject requests before dispatch when the target model lacks required capabilities (vision, tools, structured output, context window). Protects direct single-provider requests that bypass the combo-layer compatibility filter. | | `RADAR_ENABLED` | boolean | `false` | Enable the OmniRoute Radar module (catalog feed screens and sync). Off by default; enabling only unlocks the UI — data sync remains a separate opt-in. | -### Runtime (29) +### Runtime (30) | Key | Type | Default | Restart | Description | | ------------------------------------------- | ------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -125,6 +127,7 @@ used when neither a DB override nor an environment variable is present. | `FREE_BADGE_REQUIRES_PROVIDER_FREE_TIER` | boolean | `false` | | Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule. | | `RETRY_AFTER_PROVENANCE_ENABLED` | boolean | `false` | | On aggregated 429/503 unavailable responses, omit `Retry-After` when no concrete future retry time is known (instead of a synthetic 1s), add `error.retry_after_provenance` (`signal` \| `none`), and let combo drain paths read prose retry hints from JSON and plain-text upstream bodies. The field only appears on responses built by `unavailableResponse()`; other 429/503 bodies are unchanged. | | `PROTECTED_PRIORITY_INFRA_502_ENABLED` | boolean | `false` | | When a `priority` combo target marked fallback-only-on-quota-exhaustion stops the combo for a cause that is provably not quota (provider circuit breaker open, predictive latency skip), answer 502 instead of the quota-looking 503. Lockout, cooldown, unavailable, exhaustion and concurrency-cap stops keep 503. | +| `MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT` | boolean | `false` | | A bare Mistral 401 (`{"detail":"Unauthorized"}`, no explicit auth signal) is identical for a revoked key and for exhausted quota. When on, it cools the connection down instead of parking it as `expired`, at most 3 times per hour per connection; the next one parks it, so a revoked key still converges. Off by default: every bare Mistral 401 parks the connection as before. | ### CLI (5) @@ -206,7 +209,7 @@ Returns every flag with its effective value, source, and a summary. "requiresRestart": false, "warningLevel": "caution", }, - // ... all 66 flags + // ... all 69 flags ], "summary": { "total": 56, diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index ed9417cb5c63..9c18e7945adc 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -35,7 +35,12 @@ import { resolveResponsesStallWindowMs, } from "./opencodeResponsesStall.ts"; import { discardResponseBody } from "./opencodeResponseBody.ts"; -import { isRetriableUpstreamFailure } from "./opencodeTransientFailure.ts"; +import { + isRetriableUpstreamFailure, + releaseResponseBody, + sleepAbortable, + transientRetryDelayMs, +} from "./opencodeTransientFailure.ts"; import { hasProxyRefusals, isProxyAvoided, @@ -47,7 +52,10 @@ import { isNetworkRotationSharedEgressGuardEnabled, isProxySkipRecentlyFailedEnabled, isOpencodeUserBlockedRotationEnabled, + isOpencodeTransientFailoverBackoffEnabled, + isOpencodeRateLimited429EarlyStopEnabled, } from "@/shared/utils/featureFlags"; +import { classifyUpstream429 } from "./opencodeRateLimited.ts"; /** * The main OpenCode Zen host, shared by the `opencode` and `opencode-zen` @@ -311,6 +319,10 @@ export class OpencodeExecutor extends BaseExecutor { // pickRotatableAccount(), which needs a plain `{ nextAccountIdx }` shape — // TS's private-member nominal check rejects `this` there otherwise. nextAccountIdx = 0; + // Sleep used by the opt-in transient failover pause (#13615). Not `private`: + // tests swap in a recording fake instead of waiting on real timers. + transientPauseSleep: (ms: number, signal?: AbortSignal | null) => Promise = + sleepAbortable; constructor(provider: string) { super(provider, PROVIDERS[provider] || PROVIDERS.openai); @@ -623,6 +635,10 @@ export class OpencodeExecutor extends BaseExecutor { let abandonedResponse: Response | null = null; // OPENCODE_USER_BLOCKED_ROTATION: rotations spent on user_blocked refusals (max 1). let userBlockedRotations = 0; + // Consecutive transient failures (5xx / empty 400) and the pause time spent on + // them this request — only acted on when OPENCODE_TRANSIENT_FAILOVER_BACKOFF is on. + let transientStreak = 0; + let transientPausedMs = 0; for (let attempt = 0; attempt < this.accounts.length + emptyRejectionBudget; attempt++) { const isProxiedCandidate = (a: OpencodeAccountState): boolean => { @@ -676,6 +692,19 @@ export class OpencodeExecutor extends BaseExecutor { continue; } + // Opt-in (#13615): after repeated transient failures, release the failed body + // and wait (bounded) before the next account; a client abort stops the loop. + const pauseMs = transientRetryDelayMs(transientStreak, transientPausedMs); + if (pauseMs > 0 && lastResult !== null && isOpencodeTransientFailoverBackoffEnabled()) { + lastResult = { ...lastResult, response: releaseResponseBody(lastResult.response) }; + transientPausedMs += pauseMs; + log?.info?.( + "OPENCODE", + `${cid}${transientStreak} transient failures, pausing ${pauseMs}ms` + ); + if (!(await this.transientPauseSleep(pauseMs, input.signal))) break; + } + // #5217 (Gap 2): promoted debug→info so the per-request account/proxy // rotation selection is visible in the Console log view at the default // APP_LOG_LEVEL=info (users could not see which account/proxy was used). @@ -719,6 +748,7 @@ export class OpencodeExecutor extends BaseExecutor { if (!rotate) throw err; continue; } + transientStreak = 0; // A network exception (timeout, connection refused/reset) is only // account-scoped when this account has its OWN egress (a configured // proxy) — that's the case a dead/unreachable proxy justifies rotating @@ -752,6 +782,8 @@ export class OpencodeExecutor extends BaseExecutor { discardResponseBody(abandonedResponse); abandonedResponse = null; lastResult = result; + const priorTransientStreak = transientStreak; + transientStreak = 0; const status = result.response.status; if (status === 429) { @@ -761,6 +793,19 @@ export class OpencodeExecutor extends BaseExecutor { const setAsideMs = skipRecentlyFailed ? noteProxyRefusal(proxyEgressKey(account.proxy), "ip_quota_429") : null; + // Opt-in (#13657): a 429 that names a real rate limit stops the wave and + // the real upstream 429 is returned untouched (body, Retry-After, quota + // headers), so provider error rules still apply. Flag off → rotate. + if ( + isOpencodeRateLimited429EarlyStopEnabled() && + (await classifyUpstream429(result.response)) === "rate_limited" + ) { + log?.warn?.( + "OPENCODE", + `${cid}rate-limited 429 on account ${masked}, stopping the account wave` + ); + return result; + } log?.warn?.( "OPENCODE", `${cid}Rate limited (429) on account ${masked}` + @@ -774,6 +819,7 @@ export class OpencodeExecutor extends BaseExecutor { const key = proxyKeyOf(account.proxy); if (key !== null) geoTriedProxyKeys.add(key); else directTried = true; + transientStreak = priorTransientStreak + 1; log?.warn?.( "OPENCODE", `${cid}transient upstream ${status} on account ${masked} (proxy ${key ?? "direct"}), rotating to next…` @@ -849,6 +895,7 @@ export class OpencodeExecutor extends BaseExecutor { } if (bodyText !== null && isRetriableUpstreamFailure(400, bodyText)) { const chatcmplId = extractChatcmplId(bodyText); + transientStreak = priorTransientStreak + 1; log?.warn?.( "OPENCODE", `${cid}upstream empty rejection on account ${masked} (${chatcmplId}), rotating to next…` diff --git a/open-sse/executors/opencodeRateLimited.ts b/open-sse/executors/opencodeRateLimited.ts new file mode 100644 index 000000000000..0bf8e7dec3fb --- /dev/null +++ b/open-sse/executors/opencodeRateLimited.ts @@ -0,0 +1,92 @@ +/** + * opencodeRateLimited.ts — 429 classifier for the opencode executor loop (#13657). + * + * Leaf module: zero imports, no registry, no DB. Headers first: a parseable + * Retry-After alone marks a real rate limit; otherwise a bounded prefix of the + * body is matched against generic English rate-limit phrasings (derived from a + * captured 429 body). Anything else is a "burst" 429 that keeps the normal + * cross-account rotation. Only consulted when OPENCODE_RATE_LIMITED_429_EARLY_STOP + * is on; the classifier never rewrites the response it inspects. + */ + +const RATE_LIMITED_SIGNALS: ReadonlyArray = [ + /rate.?limited/i, + /usage.?limit/i, + /too many requests/i, +]; + +/** Bytes of a 429 body inspected by the classifier. */ +export const RATE_LIMIT_BODY_SNIFF_BYTES = 8192; + +/** Seconds until retry from a Retry-After value (delta-seconds or HTTP date), or null. */ +export function parseRetryAfterSeconds( + retryAfter: string | number | null | undefined, + now = Date.now() +): number | null { + if (typeof retryAfter === "number") { + return Number.isFinite(retryAfter) && retryAfter > 0 ? Math.ceil(retryAfter) : null; + } + if (typeof retryAfter !== "string") return null; + const text = retryAfter.trim(); + if (text === "") return null; + if (/^\d+$/.test(text)) return Math.max(Number(text), 1); + const ms = Date.parse(text); + if (Number.isFinite(ms)) return Math.max(Math.ceil((ms - now) / 1000), 1); + return null; +} + +export type RateLimit429Verdict = "rate_limited" | "burst"; + +export function classify429(input: { + retryAfter?: string | number | null; + bodyText?: string | null; +}): RateLimit429Verdict { + if (parseRetryAfterSeconds(input.retryAfter) !== null) return "rate_limited"; + const body = typeof input.bodyText === "string" ? input.bodyText : ""; + if (body !== "" && RATE_LIMITED_SIGNALS.some((re) => re.test(body))) return "rate_limited"; + return "burst"; +} + +/** + * Read at most `maxBytes` of a response body from a clone, then cancel the + * clone's reader. The original response keeps its full, unread body. Returns + * null when the body cannot be read. + */ +export async function readBodyPrefix( + response: Response, + maxBytes = RATE_LIMIT_BODY_SNIFF_BYTES +): Promise { + if (!response.body) return ""; + let reader: ReadableStreamDefaultReader | undefined; + try { + reader = response.clone().body?.getReader(); + } catch { + return null; + } + if (!reader) return ""; + const decoder = new TextDecoder(); + let text = ""; + let bytes = 0; + try { + while (bytes < maxBytes) { + const { done, value } = await reader.read(); + if (done || !value) break; + const chunk = + value.byteLength > maxBytes - bytes ? value.subarray(0, maxBytes - bytes) : value; + bytes += chunk.byteLength; + text += decoder.decode(chunk, { stream: true }); + } + return text + decoder.decode(); + } catch { + return null; + } finally { + void reader.cancel().catch(() => undefined); + } +} + +/** Classify an upstream 429: Retry-After header first, bounded body prefix only if needed. */ +export async function classifyUpstream429(response: Response): Promise { + const retryAfter = response.headers.get("retry-after"); + if (parseRetryAfterSeconds(retryAfter) !== null) return "rate_limited"; + return classify429({ retryAfter, bodyText: await readBodyPrefix(response) }); +} diff --git a/open-sse/executors/opencodeTransientFailure.ts b/open-sse/executors/opencodeTransientFailure.ts index 9af52a1fa4bc..320e5de8be3d 100644 --- a/open-sse/executors/opencodeTransientFailure.ts +++ b/open-sse/executors/opencodeTransientFailure.ts @@ -2,12 +2,14 @@ * opencodeTransientFailure.ts — retriable-upstream predicate for the opencode * executor loop. * - * Leaf module: one internal import only (isEmptyUpstreamRejection, same - * executors layer — no registry, no DB). 5xx short-circuits on status alone; - * the 400 arm delegates to the existing empty-rejection classifier. + * Leaf module: internal imports from the same executors layer only + * (isEmptyUpstreamRejection, discardResponseBody — no registry, no DB). 5xx + * short-circuits on status alone; the 400 arm delegates to the existing + * empty-rejection classifier. */ import { isEmptyUpstreamRejection } from "./accountRotation.ts"; +import { discardResponseBody } from "./opencodeResponseBody.ts"; export function isRetriableUpstreamFailure(status: number, bodyText?: string): boolean { if (status >= 500 && status < 600) return true; @@ -15,3 +17,68 @@ export function isRetriableUpstreamFailure(status: number, bodyText?: string): b if (typeof bodyText !== "string" || bodyText === "") return false; return isEmptyUpstreamRejection(status, bodyText); } + +// ── Failover pause after repeated transient failures (#13615, opt-in) ────── +// Gated by OPENCODE_TRANSIENT_FAILOVER_BACKOFF (default off). The first retry +// after a transient failure stays immediate (a distinct egress already guards +// a one-off flap); from the second consecutive transient failure on, the loop +// waits before the next account so a briefly overloaded upstream can recover. + +/** Consecutive transient failures before the first pause. */ +export const TRANSIENT_PAUSE_STREAK = 2; +/** First pause, same magnitude as BaseExecutor.WAF_RETRY_CONFIG.delayMs. */ +export const TRANSIENT_RETRY_BASE_DELAY_MS = 1500; +/** Upper bound of a single pause. */ +export const TRANSIENT_RETRY_MAX_DELAY_MS = 6000; +/** Upper bound of all pauses in one request. */ +export const TRANSIENT_RETRY_TOTAL_BUDGET_MS = 10_000; + +/** + * Pause before the next dispatch after `consecutiveFailures` transient failures + * in a row, given `pausedMs` already spent this request. 0 means dispatch now. + * Doubles per further failure (1.5s, 3s, 6s, 6s…) and never exceeds the + * per-pause cap or what is left of the per-request budget. + */ +export function transientRetryDelayMs(consecutiveFailures: number, pausedMs = 0): number { + if (!Number.isFinite(consecutiveFailures) || consecutiveFailures < TRANSIENT_PAUSE_STREAK) { + return 0; + } + const step = Math.min(consecutiveFailures - TRANSIENT_PAUSE_STREAK, 16); + const delay = Math.min(TRANSIENT_RETRY_BASE_DELAY_MS * 2 ** step, TRANSIENT_RETRY_MAX_DELAY_MS); + const left = TRANSIENT_RETRY_TOTAL_BUDGET_MS - Math.max(0, pausedMs); + return Math.max(0, Math.min(delay, left)); +} + +/** + * Sleep that resolves `false` as soon as `signal` aborts (or immediately when it + * already has), `true` once `ms` elapsed. The listener and timer are always + * released. + */ +export function sleepAbortable(ms: number, signal?: AbortSignal | null): Promise { + if (signal?.aborted) return Promise.resolve(false); + return new Promise((resolve) => { + const onAbort = () => { + clearTimeout(timer); + resolve(false); + }; + const timer = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(true); + }, ms); + signal?.addEventListener("abort", onAbort, { once: true }); + }); +} + +/** + * Cancel a failed response's body before a pause and return a body-less copy + * that keeps its status, status text and headers (what the exhaustion path may + * still surface once the loop ends). + */ +export function releaseResponseBody(response: Response): Response { + discardResponseBody(response); + return new Response(null, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); +} diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 818183e6b794..aa5a62f03f0f 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -104,6 +104,8 @@ import { capScaledCooldownMs } from "./accountFallback/cooldownCap.ts"; import { resolveApiKeyForbiddenFallback } from "./accountFallback/nonRetryableUpstream.ts"; import * as exactModelLock from "./accountFallback/exactModelLock.ts"; import { isCreditsExhaustedWithSharedWallet } from "./accountFallback/sharedWalletCredits.ts"; +import { isMistralAmbiguous401 } from "./accountFallback/mistralAmbiguousAuth.ts"; +import { isMistralAmbiguous401SoftLockoutEnabled } from "@/shared/utils/featureFlags"; export type ProviderProfile = { baseCooldownMs: number; useUpstreamRetryHints: boolean; @@ -1685,6 +1687,8 @@ export function checkFallbackError( permanent?: boolean; creditsExhausted?: boolean; dailyQuotaExhausted?: boolean; + /** #13609: bare Mistral 401 softened to a cooldown (MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT). */ + ambiguousAuth?: boolean; /** G-02: true when the error originates from an embedded service supervisor (not the upstream AI * provider itself). Callers should apply connection cooldown only — do NOT record a provider * circuit-breaker failure when this flag is set. */ @@ -2179,6 +2183,16 @@ export function checkFallbackError( resolveRuleMatchBody(provider, structuredError ?? null, errorStr) ) : null; + // #13609 (opt-in): a bare Mistral 401 is not proof of a dead key — back off + // instead; resolveTerminalConnectionStatus bounds how often (ambiguousAuth). + if ( + status === HTTP_STATUS.UNAUTHORIZED && + !providerMatch && + isMistralAmbiguous401(provider, errorStr) && + isMistralAmbiguous401SoftLockoutEnabled() + ) { + return { ...buildRetryableFallback(RateLimitReason.UNKNOWN), ambiguousAuth: true }; + } const cooldownMs = providerMatch?.cooldownMs ?? configuredRule.cooldownMs ?? 0; const ruleScope = providerMatch && honorsRuleLockScope(provider) ? providerMatch.scope : undefined; diff --git a/open-sse/services/accountFallback/mistralAmbiguousAuth.ts b/open-sse/services/accountFallback/mistralAmbiguousAuth.ts new file mode 100644 index 000000000000..5f1ba872a1d2 --- /dev/null +++ b/open-sse/services/accountFallback/mistralAmbiguousAuth.ts @@ -0,0 +1,58 @@ +/** + * Mistral ambiguous 401 (#7638 / #7718 / #13609). + * + * Mistral answers a revoked key and an exhausted quota with the same bare + * `401 {"detail":"Unauthorized"}`. This leaf module (zero imports) is the ONE + * implementation of that ambiguity check, shared by the connection-test + * diagnosis (`src/app/api/providers/[id]/test/mistralAmbiguousAuth.ts`) and the + * runtime fallback path (`checkFallbackError` + `resolveTerminalConnectionStatus`). + * + * It also holds the bound for the opt-in runtime soft lockout + * (MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT): a connection gets at most + * MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES soft cooldowns inside the strike window; + * the next bare 401 parks it as expired, so a genuinely revoked key converges. + */ + +const AUTH_SIGNALS = ["invalid api key", "token invalid", "revoked", "access denied"]; + +/** Soft cooldowns allowed per connection inside the strike window. */ +export const MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES = 3; +/** Window, measured from the first strike, in which soft strikes accumulate. */ +export const MISTRAL_AMBIGUOUS_401_STRIKE_WINDOW_MS = 60 * 60 * 1000; + +/** True for a Mistral 401 body that carries no explicit auth signal (callers check the status). */ +export function isMistralAmbiguous401( + provider: string | null | undefined, + errorText: string | null | undefined +): boolean { + if (provider !== "mistral") return false; + const normalized = String(errorText || "").toLowerCase(); + return !AUTH_SIGNALS.some((signal) => normalized.includes(signal)); +} + +const strikes = new Map(); + +/** + * Record one ambiguous bare 401 for `connectionId` and say whether it may still + * be softened (true) or must park the connection (false). Crossing the bound + * clears the entry, so a re-authenticated connection starts a fresh count. + */ +export function takeMistralAmbiguous401SoftStrike(connectionId: string, now = Date.now()): boolean { + const entry = strikes.get(connectionId); + const current = + entry && now - entry.firstAt < MISTRAL_AMBIGUOUS_401_STRIKE_WINDOW_MS + ? entry + : { count: 0, firstAt: now }; + current.count += 1; + if (current.count > MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES) { + strikes.delete(connectionId); + return false; + } + strikes.set(connectionId, current); + return true; +} + +/** Test hook: forget every recorded strike. */ +export function resetMistralAmbiguous401Strikes(): void { + strikes.clear(); +} diff --git a/src/app/api/providers/[id]/test/mistralAmbiguousAuth.ts b/src/app/api/providers/[id]/test/mistralAmbiguousAuth.ts index 8f47be63a51e..9f997ea0a941 100644 --- a/src/app/api/providers/[id]/test/mistralAmbiguousAuth.ts +++ b/src/app/api/providers/[id]/test/mistralAmbiguousAuth.ts @@ -1,3 +1,5 @@ +import { isMistralAmbiguous401 } from "@omniroute/open-sse/services/accountFallback/mistralAmbiguousAuth.ts"; + /** * #7638: Mistral's quota-exhausted response is `401 {"detail":"Unauthorized"}` — byte-identical * to a genuinely revoked key. Unlike other providers, a bare Mistral 401 with no auth-specific @@ -23,16 +25,6 @@ export interface ClassifyFailureArgs { provider?: string; } -function isMistralAmbiguous401(provider: string | undefined, normalized: string): boolean { - if (provider !== "mistral") return false; - const hasAuthSignal = - normalized.includes("invalid api key") || - normalized.includes("token invalid") || - normalized.includes("revoked") || - normalized.includes("access denied"); - return !hasAuthSignal; -} - /** Decides the diagnosis for a 401/403 status: ambiguous (Mistral-only) or the generic auth error. */ export function classifyAmbiguousOrAuthError( provider: string | undefined, diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index acc0eebbeaac..41df367baa1f 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "فعّل مسارات القبول الافتراضية التكيفية لكل مستأجر (tenant) لتوزيع المزودين (#9654): لم يعد انفجار حركة أحد المستأجرين يسبب خطأ 503 لمستأجر آخر. متغير البيئة OMNIROUTE_CHAT_VIRTUAL_LANES له الأولوية على هذا الإعداد في لوحة التحكم؛ تصبح التغييرات سارية بعد إعادة تشغيل الخادم.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index 663d028ab889..81972d8d4d75 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Təchizatçı göndərişi üçün hər bir icarəçi (tenant) üzrə adaptiv virtual qəbul zolaqlarını aktivləşdirin (#9654): bir icarəçinin ani yükü artıq digərinə 503 qaytarmır. OMNIROUTE_CHAT_VIRTUAL_LANES mühit dəyişəni bu idarəetmə paneli ayarından üstündür; dəyişikliklər server yenidən işə salındıqda qüvvəyə minir.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index 4049e2b21d14..ba02624d17d4 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Активирайте адаптивни виртуални ленти за допускане за всеки наемател (tenant) при изпращане към доставчици (#9654): скокът в натоварването на един наемател вече не връща 503 на друг. Променливата на средата OMNIROUTE_CHAT_VIRTUAL_LANES има предимство пред тази настройка в таблото; промените влизат в сила след рестартиране на сървъра.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 9437ef38ea67..6ddf474014cf 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "প্রোভাইডার ডিসপ্যাচের জন্য প্রতি-টেন্যান্ট অ্যাডাপ্টিভ ভার্চুয়াল অ্যাডমিশন লেন সক্ষম করুন (#9654): এক টেন্যান্টের বিস্ফোরণ আর অন্য টেন্যান্টে 503 ফেরায় না। OMNIROUTE_CHAT_VIRTUAL_LANES এনভায়রনমেন্ট ভেরিয়েবল এই ড্যাশবোর্ড সেটিংয়ের উপরে প্রাধান্য পায়; পরিবর্তনগুলি সার্ভার পুনরায় চালু হলে কার্যকর হয়।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index af621ae64f0a..9abb23fe98dd 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Povolte adaptivní virtuální vstupní pruhy pro každého tenanta při odesílání poskytovatelům (#9654): špička jednoho tenanta už nezpůsobí 503 u jiného. Proměnná prostředí OMNIROUTE_CHAT_VIRTUAL_LANES má přednost před tímto nastavením na řídicím panelu; změny se projeví po restartu serveru.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index f4e9a00f2b8c..ffabe002ee68 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktivér adaptive virtuelle adgangsbaner pr. tenant til providerudlevering (#9654): en tenants burst giver ikke længere en anden 503. Miljøvariablen OMNIROUTE_CHAT_VIRTUAL_LANES har forrang over denne dashboard-indstilling; ændringer træder i kraft ved genstart af serveren.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 011e8a818498..65b8e9deaeaa 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktivieren Sie adaptive virtuelle Zulassungsspuren pro Tenant für die Provider-Zustellung (#9654): Ein Burst eines Tenants führt nicht mehr zu 503 bei einem anderen. Die Umgebungsvariable OMNIROUTE_CHAT_VIRTUAL_LANES hat Vorrang vor dieser Dashboard-Einstellung; Änderungen werden erst nach einem Serverneustart wirksam.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index ab028b0ca61d..9a2e2b43c0bb 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ενεργοποίηση προσαρμοστικών εικονικών λωρίδων αποδοχής ανά ενοικιαστή για αποστολή παρόχου (#9654): η έκρηξη ενός ενοικιαστή δεν προκαλεί πλέον 503 σε άλλον. Η μεταβλητή περιβάλλοντος OMNIROUTE_CHAT_VIRTUAL_LANES υπερισχύει αυτής της παράκαμψης του πίνακα ελέγχου· οι αλλαγές τίθενται σε ισχύ κατά την επανεκκίνηση του διακομιστή.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index c1b6a74794c1..0a685beed5a9 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Enable per-tenant adaptive virtual admission lanes for provider dispatch (#9654): one tenant's burst no longer 503s another. The OMNIROUTE_CHAT_VIRTUAL_LANES env var wins over this dashboard override; changes take effect at server restart.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 3ce08c427ca9..18916f167f7e 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Activa carriles de admisión virtuales adaptativos por tenant para el envío de proveedores (#9654): el pico de un tenant ya no devuelve 503 a otro. La variable de entorno OMNIROUTE_CHAT_VIRTUAL_LANES tiene prioridad sobre esta opción del panel; los cambios surten efecto al reiniciar el servidor.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index 1e38d6129480..b72c7a30ee5a 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Lubage pakkujale edastamiseks rentnikupõhised kohanduvad virtuaalsed vastuvõturajad (#9654): ühe rentniku koormushoog ei põhjusta enam teisele tõrget 503. Keskkonnamuutuja OMNIROUTE_CHAT_VIRTUAL_LANES alistab selle juhtpaneeli sätte; muudatused jõustuvad serveri taaskäivitamisel.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 78f22c2633ce..68f6e65028de 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "خط‌های پذیرش مجازی تطبیقی به‌ازای هر مستاجر (tenant) را برای ارسال به ارائه‌دهندگان فعال کنید (#9654): افزایش ناگهانی بار یک مستاجر دیگر خطای 503 را برای مستاجر دیگر ایجاد نمی‌کند. متغیر محیطی OMNIROUTE_CHAT_VIRTUAL_LANES بر این تنظیم داشبورد اولویت دارد؛ تغییرات پس از راه‌اندازی مجدد سرور اعمال می‌شوند.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 5c0776995ca7..0c70a65d107d 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ota käyttöön mukautuvat virtuaaliset sisäänottokaistat vuokraajaa (tenant) kohti palveluntarjoajien välitystä varten (#9654): yhden vuokraajan kuormapiikki ei enää aiheuta 503-virhettä toiselle. Ympäristömuuttuja OMNIROUTE_CHAT_VIRTUAL_LANES ohittaa tämän hallintapaneelin asetuksen; muutokset tulevat voimaan palvelimen uudelleenkäynnistyksessä.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 6190cc79ff1b..cceef76e5ba5 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Activez des voies d'admission virtuelles adaptatives par tenant pour la répartition des fournisseurs (#9654) : le pic d'un tenant ne renvoie plus 503 à un autre. La variable d'environnement OMNIROUTE_CHAT_VIRTUAL_LANES prime sur ce réglage du tableau de bord ; les modifications prennent effet au redémarrage du serveur.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index b2594f676a5f..a3dcf8d2e569 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Cumasaigh lánaí iontrála oiriúnaitheacha fíorúla in aghaidh an tionónta le haghaidh seolta soláthraí (#9654): ní chruthaíonn pléascadh tionónta amháin 503 do thionónta eile a thuilleadh. Tá an athróg timpeallachta OMNIROUTE_CHAT_VIRTUAL_LANES níos cumhachtaí ná an sárú deais seo; tagann athruithe i bhfeidhm ag atosú freastalaí.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 85eb72dfc87d..9328e707f7eb 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "પ્રોવાઇડર ડિસ્પેચ માટે પ્રતિ-ટેનન્ટ અનુકૂલનશીલ વર્ચ્યુઅલ એડમિશન લેન સક્ષમ કરો (#9654): એક ટેનન્ટનો બર્સ્ટ હવે બીજા ટેનન્ટને 503 આપતો નથી. OMNIROUTE_CHAT_VIRTUAL_LANES એન્વાયર્નમેન્ટ વેરિયેબલ આ ડેશબોર્ડ સેટિંગ કરતાં વધુ પ્રાધાન્ય ધરાવે છે; ફેરફારો સર્વર પુનઃપ્રારંભ પર અસરકારક થાય છે.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index a275126e2eec..bcd41f8dc9d3 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "הפעל נתיבי קבלה וירטואליים אדפטיביים לכל דייר (tenant) עבור שליחת ספקים (#9654): פרץ עומס של דייר אחד כבר לא מחזיר 503 לדייר אחר. משתנה הסביבה OMNIROUTE_CHAT_VIRTUAL_LANES גובר על הגדרה זו בלוח הבקרה; השינויים נכנסים לתוקף לאחר הפעלת השרת מחדש.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index c1c1103479b1..5f0e06fdf515 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "प्रदाता डिस्पैच के लिए प्रति-टेनेंट अनुकूली वर्चुअल एडमिशन लेन सक्षम करें (#9654): एक टेनेंट का बर्स्ट अब दूसरे टेनेंट को 503 नहीं देता। OMNIROUTE_CHAT_VIRTUAL_LANES पर्यावरण चर इस डैशबोर्ड सेटिंग पर प्राथमिकता रखता है; परिवर्तन सर्वर पुनः आरंभ पर प्रभावी होते हैं।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index 8e5fd6c68847..e2672a03cdca 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Omogući adaptivne virtualne prijamne trake po korisniku za raspodjelu pružatelja (#9654): opterećenje jednog korisnika više neće uzrokovati 503 grešku drugome. Varijabla okoline OMNIROUTE_CHAT_VIRTUAL_LANES ima prednost nad ovim nadjačavanjem nadzorne ploče; promjene stupaju na snagu pri ponovnom pokretanju poslužitelja.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index eb72dc44ed65..4a4478aa56b4 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Tegye lehetővé a bérlőnkénti adaptív virtuális beléptetősávokat a szolgáltatók felé történő továbbításhoz (#9654): az egyik bérlő kiugró terhelése már nem okoz 503-as hibát egy másiknál. Az OMNIROUTE_CHAT_VIRTUAL_LANES környezeti változó felülírja ezt a vezérlőpult-beállítást; a változtatások a szerver újraindításakor lépnek életbe.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 5ec7c719e01f..0fc0cc23024c 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktifkan jalur penerimaan virtual adaptif per-tenant untuk pengiriman penyedia (#9654): lonjakan satu tenant tidak lagi mengembalikan 503 ke tenant lain. Variabel lingkungan OMNIROUTE_CHAT_VIRTUAL_LANES menang atas pengaturan dasbor ini; perubahan berlaku setelah server dimulai ulang.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index e1cdadec1ba0..21c08e71ad2a 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Attiva corsie di ammissione virtuali adattive per tenant per l'invio ai provider (#9654): il picco di un tenant non restituisce più 503 a un altro. La variabile d'ambiente OMNIROUTE_CHAT_VIRTUAL_LANES ha la precedenza su questa impostazione della dashboard; le modifiche hanno effetto al riavvio del server.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index fb2c8658bdb9..9c888b165405 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "プロバイダーへのディスパッチ用に、テナントごとの適応型仮想受付レーンを有効にします(#9654):あるテナントのバーストが他のテナントに503を返さなくなります。OMNIROUTE_CHAT_VIRTUAL_LANES環境変数はこのダッシュボード設定より優先されます。変更はサーバー再起動時に反映されます。", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index df7fa2ac3a1d..34960ce503d0 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "បើកផ្លូវចូលនិម្មិតដែលសម្របខ្លួនតាម tenant នីមួយៗ សម្រាប់ការបញ្ជូនទៅ provider (#9654)៖ ការកើនឡើងខ្លាំងភ្លាមៗរបស់ tenant មួយ នឹងលែងបណ្ដាលឱ្យ tenant មួយទៀតទទួល 503។ env var OMNIROUTE_CHAT_VIRTUAL_LANES មានអាទិភាពលើការកំណត់ជំនួសពី dashboard នេះ ហើយការផ្លាស់ប្ដូរនឹងមានប្រសិទ្ធភាពនៅពេលចាប់ផ្ដើម server ឡើងវិញ។", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index e089761223b3..5e87887aadf7 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "ಪ್ರೊವೈಡರ್ ಡಿಸ್ಪ್ಯಾಚ್ ಗಾಗಿ ಪ್ರತಿ-ಟೆನಂಟ್ ಅಡಾಪ್ಟಿವ್ ವರ್ಚುವಲ್ ಅಡ್ಮಿಷನ್ ಲೇನ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ (#9654): ಒಂದು ಟೆನಂಟ್ನ ಬರ್ಸ್ಟ್ ಇನ್ನು ಮುಂದೆ ಮತ್ತೊಂದನ್ನು 503 ಮಾಡುವುದಿಲ್ಲ. OMNIROUTE_CHAT_VIRTUAL_LANES ಎನ್ವಿ ವೇರಿಯಬಲ್ ಈ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಓವರ್ರೈಡ್ ಮೇಲೆ ಗೆಲ್ಲುತ್ತದೆ; ಬದಲಾವಣೆಗಳು ಸರ್ವರ್ ರೀಸ್ಟಾರ್ಟ್ ನಲ್ಲಿ ಜಾರಿಗೆ ಬರುತ್ತವೆ.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index d80802fa59e4..9ba9dc3094a1 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "공급자 디스패치를 위해 테넌트별 적응형 가상 승인 레인을 활성화합니다(#9654): 한 테넌트의 폭증이 더 이상 다른 테넌트에 503을 반환하지 않습니다. OMNIROUTE_CHAT_VIRTUAL_LANES 환경 변수가 이 대시보드 설정보다 우선하며, 변경 사항은 서버 재시작 시 적용됩니다.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index 40952458036f..c7c6f53711a8 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Įjungti kiekvienam nuomotojui pritaikomas adaptyvias virtualias priėmimo juostas teikėjų siuntimui (#9654): vieno nuomotojo srautas nebesukelia 503 klaidos kitam. Aplinkos kintamasis OMNIROUTE_CHAT_VIRTUAL_LANES turi pirmenybę prieš šį skydelio nustatymą; pakeitimai įsigalioja po serverio paleidimo iš naujo.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index 2ddff1c191c1..cc37c74fa60b 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Iespējot katra nomnieka adaptīvas virtuālās uzņemšanas joslas nodrošinātāju izsūtīšanai (#9654): viena nomnieka slodzes lēciens vairs neizraisa 503 kļūdu citam. OMNIROUTE_CHAT_VIRTUAL_LANES vides mainīgais ir prioritārāks par šo paneļa iestatījumu; izmaiņas stājas spēkā pēc servera pārstartēšanas.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index feb1bb157696..f23874a8a05a 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "പ്രൊവൈഡർ ഡിസ്പാച്ചിനായി ഓരോ ടെനന്റിനും അനുയോജ്യമായി മാറുന്ന വെർച്വൽ അഡ്മിഷൻ ലെയിനുകൾ പ്രവർത്തനക്ഷമമാക്കുക (#9654): ഇനി ഒരു ടെനന്റിന്റെ പെട്ടെന്നുള്ള അഭ്യർത്ഥന വർധന മറ്റൊരാൾക്ക് 503 പിശക് സൃഷ്ടിക്കില്ല. ഈ ഡാഷ്ബോർഡ് ഓവർറൈഡിനേക്കാൾ OMNIROUTE_CHAT_VIRTUAL_LANES env var-ന് മുൻഗണനയുണ്ട്; സെർവർ പുനരാരംഭിക്കുമ്പോൾ മാറ്റങ്ങൾ പ്രാബല്യത്തിൽ വരും.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index daf19a6caeb2..71358f01755d 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "प्रदाता डिस्पॅचसाठी प्रति-टेनंट अनुकूली व्हर्च्युअल अॅडमिशन लेन सक्षम करा (#9654): एका टेनंटचा बर्स्ट यापुढे दुसऱ्या टेनंटला 503 देत नाही. OMNIROUTE_CHAT_VIRTUAL_LANES पर्यावरण चल या डॅशबोर्ड सेटिंगपेक्षा वरचढ आहे; बदल सर्व्हर रीस्टार्ट केल्यावर प्रभावी होतात.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index d62c0f735764..95c1e7cb66c4 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktifkan lorong kemasukan maya adaptif setiap-tenant untuk penghantaran pembekal (#9654): lonjakan satu tenant tidak lagi memberikan 503 kepada tenant lain. Pemboleh ubah persekitaran OMNIROUTE_CHAT_VIRTUAL_LANES mengatasi tetapan papan pemuka ini; perubahan berkuat kuasa apabila pelayan dimulakan semula.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index ce6561f74b4c..b7539064a520 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ippermetti korsiji virtwali adattivi tad-dħul għal kull tenant għad-dispaċċ tal-fornituri (#9654): żieda f'daqqa fit-traffiku ta' tenant wieħed ma tibqax tikkawża żball 503 għal ieħor. Il-varjabbli tal-ambjent OMNIROUTE_CHAT_VIRTUAL_LANES jieħu preċedenza fuq din is-sovrasKitba tad-dashboard; il-bidliet jidħlu fis-seħħ meta jerġa' jinbeda s-server.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index cb89cf7fc2ce..25c91e45a18d 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "provider dispatch (#9654) အတွက် tenant တစ်ခုချင်းစီအလိုက် အလိုက်သင့်ပြောင်းလဲနိုင်သော virtual admission lanes များကို ဖွင့်ပါ။ tenant တစ်ခု၏ ရုတ်တရက်မြင့်တက်လာသော အသုံးပြုမှုကြောင့် အခြား tenant တွင် 503 ဖြစ်ပေါ်တော့မည်မဟုတ်ပါ။ OMNIROUTE_CHAT_VIRTUAL_LANES env var သည် ဤ dashboard override ထက် ဦးစားပေးသက်ရောက်ပြီး ပြောင်းလဲမှုများသည် server ပြန်လည်စတင်ချိန်တွင် အသက်ဝင်မည်ဖြစ်သည်။", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index 3b67620b2136..0b6318a9ebd5 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "प्रदायक डिस्प्याच (#9654) का लागि प्रत्येक टेनेन्टअनुसार अनुकूल हुने भर्चुअल एडमिसन लेनहरू सक्षम गर्नुहोस्: अब एउटा टेनेन्टको अचानक बढेको ट्राफिकले अर्कोलाई 503 गराउँदैन। OMNIROUTE_CHAT_VIRTUAL_LANES env var ले यस ड्यासबोर्ड ओभरराइडभन्दा प्राथमिकता पाउँछ; परिवर्तनहरू सर्भर पुनः सुरु भएपछि लागू हुन्छन्।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index e89c107ef0d9..05c5611a3b4b 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Schakel adaptieve virtuele toegangsbanen per tenant in voor provider-dispatch (#9654): een piek van de ene tenant geeft de andere niet langer een 503. De omgevingsvariabele OMNIROUTE_CHAT_VIRTUAL_LANES wint het van deze dashboard-instelling; wijzigingen gaan in bij een serverherstart.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 1bd5cf3bfe9b..c0bd2006edad 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktiver adaptive virtuelle tilgangsfelt per tenant for leverandørdistribusjon (#9654): et utbrudd fra én tenant gir ikke lenger en annen 503. Miljøvariabelen OMNIROUTE_CHAT_VIRTUAL_LANES overstyrer denne innstillingen i dashbordet; endringer trer i kraft ved omstart av serveren.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index ad34930fbd0b..5ab1756cc23e 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "ପ୍ରଦାତା ଡିସ୍ପାଚ୍ (#9654) ପାଇଁ ପ୍ରତି-ଟେନାଣ୍ଟ ଅନୁକୂଳନଶୀଳ ଭର୍ଚୁଆଲ୍ ଆଡମିଶନ୍ ଲେନ୍ଗୁଡ଼ିକ ସକ୍ଷମ କରନ୍ତୁ: ଗୋଟିଏ ଟେନାଣ୍ଟର ହଠାତ୍ ଟ୍ରାଫିକ୍ ବୃଦ୍ଧି ଆଉ ଅନ୍ୟ ଟେନାଣ୍ଟ ପାଇଁ 503 ତ୍ରୁଟି ସୃଷ୍ଟି କରିବ ନାହିଁ। OMNIROUTE_CHAT_VIRTUAL_LANES env var ଏହି ଡ୍ୟାସ୍ବୋର୍ଡ ଓଭର୍ରାଇଡ୍ଠାରୁ ପ୍ରାଥମିକତା ପାଏ; ସର୍ଭର୍ ପୁନଃଚାଳନ ପରେ ପରିବର୍ତ୍ତନଗୁଡ଼ିକ କାର୍ଯ୍ୟକାରୀ ହୁଏ।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index 5f17b437a6db..f3443a610009 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "ਪ੍ਰਦਾਤਾ ਡਿਸਪੈਚ (#9654) ਲਈ ਪ੍ਰਤੀ-ਟੈਨੈਂਟ ਅਨੁਕੂਲ ਵਰਚੁਅਲ ਐਡਮਿਸ਼ਨ ਲੇਨ ਸਮਰੱਥ ਕਰੋ: ਹੁਣ ਇੱਕ ਟੈਨੈਂਟ ਦਾ ਅਚਾਨਕ ਵਧਿਆ ਲੋਡ ਦੂਜੇ ਲਈ 503 ਪੈਦਾ ਨਹੀਂ ਕਰੇਗਾ। OMNIROUTE_CHAT_VIRTUAL_LANES env var ਨੂੰ ਇਸ ਡੈਸ਼ਬੋਰਡ ਓਵਰਰਾਈਡ ਉੱਤੇ ਤਰਜੀਹ ਮਿਲਦੀ ਹੈ; ਤਬਦੀਲੀਆਂ ਸਰਵਰ ਮੁੜ ਚਾਲੂ ਹੋਣ 'ਤੇ ਲਾਗੂ ਹੁੰਦੀਆਂ ਹਨ।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 3231c158d0ab..6e219167e167 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Paganahin ang adaptive virtual admission lanes para sa bawat tenant sa pagpapadala ng provider (#9654): ang pag-akyat ng trapiko ng isang tenant ay hindi na nagbibigay ng 503 sa iba. Ang environment variable na OMNIROUTE_CHAT_VIRTUAL_LANES ay mas nangingibabaw sa setting na ito sa dashboard; magkakabisa ang mga pagbabago sa pag-restart ng server.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index e3132e637525..8990f83e1286 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Włącz adaptacyjne wirtualne pasma przyjęć dla każdego tenanta przy wysyłce do dostawców (#9654): przeciążenie jednego tenanta nie powoduje już błędu 503 u innego. Zmienna środowiskowa OMNIROUTE_CHAT_VIRTUAL_LANES ma pierwszeństwo przed tym ustawieniem w panelu; zmiany wchodzą w życie po restarcie serwera.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index bc85934a8ca7..8f680263e365 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -995,7 +995,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ative faixas de admissão virtuais adaptativas por tenant para o despacho de provedores (#9654): o pico de um tenant não gera mais 503 para outro. A variável de ambiente OMNIROUTE_CHAT_VIRTUAL_LANES tem precedência sobre esta configuração do painel; as alterações entram em vigor ao reiniciar o servidor.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "Pools de proxy e a rotação por conta do opencode deixam de reutilizar um proxy que acabou de falhar (sonda TCP recusada, ou um 429 recebido por ele) por um período por processo que dobra a cada repetição, até um limite. Nenhum status de proxy é gravado; com todos os candidatos deixados de lado, a escolha não muda. Desligado por padrão: a ordem de seleção é exatamente a rotação simples.", - "featureFlagProxySkipRecentlyFailedDescription": "Pools de proxy e a rotação por conta do opencode deixam de reutilizar um proxy que acabou de falhar (sonda TCP recusada, ou um 429 recebido por ele) por um período por processo que dobra a cada repetição, até um limite. Nenhum status de proxy é gravado; com todos os candidatos deixados de lado, a escolha não muda. Desligado por padrão: a ordem de seleção é exatamente a rotação simples.", "featureFlagProxyPoolEgressObservationDescription": "Mostra, abaixo de um pool de proxy no painel, quantos IPs de saída observados atenderam seus membros nas últimas 24 h, quantas conexões os usaram e o máximo visto atrás de um mesmo IP. Somente leitura, calculado a partir do log de proxy, nunca usado para roteamento. Desligado por padrão: o editor de pool não muda e a rota de observação responde null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "Na varredura de saúde de proxy, permite que uma sonda recusada pelo destino (401/403/429: o proxy retransmitiu, o destino recusou este IP de saída) zere a sequência de falhas consecutivas do proxy, como uma sonda atendida. Desligado por padrão: a recusa continua neutra e mantém a sequência. Um 5xx continua inconclusivo em qualquer caso, e uma recusa nunca remove, desativa ou reativa um proxy.", "sidebar": { diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index cdea0ba4b8e8..d428b35d828e 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -995,7 +995,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ative filas de admissão virtuais adaptativas por tenant para o encaminhamento de fornecedores (#9654): um pico de tráfego de um tenant já não gera 503 noutro. A variável de ambiente OMNIROUTE_CHAT_VIRTUAL_LANES sobrepõe-se a esta definição do painel; as alterações entram em vigor ao reiniciar o servidor.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 906e6cb0be2d..72505ae08db5 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Activați benzile de admitere virtuale adaptive per-tenant pentru expedierea către furnizori (#9654): un vârf de trafic al unui tenant nu mai returnează 503 altui tenant. Variabila de mediu OMNIROUTE_CHAT_VIRTUAL_LANES are prioritate față de această setare din panou; modificările intră în vigoare la repornirea serverului.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 956b75d6c2b0..4a6947620ab8 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Включите адаптивные виртуальные полосы допуска для каждого тенанта при маршрутизации к провайдерам (#9654): всплеск нагрузки одного тенанта больше не вызывает 503 у другого. Переменная окружения OMNIROUTE_CHAT_VIRTUAL_LANES имеет приоритет над этой настройкой в панели; изменения вступают в силу после перезапуска сервера.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index fb970ee27953..b650ff5fef53 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "සපයන්නා වෙත යැවීම සඳහා එක් එක් ටෙනන්ට්ට අනුව අනුවර්තනය වන අතථ්ය ප්රවේශ මංතීරු සබල කරන්න (#9654): එක් ටෙනන්ට් කෙනෙකුගේ හදිසි ඉල්ලීම් වැඩිවීමක් තවදුරටත් වෙනත් අයෙකුට 503 දෝෂයක් ඇති නොකරයි. OMNIROUTE_CHAT_VIRTUAL_LANES පරිසර විචල්යය මෙම උපකරණ පුවරු අතික්රමණයට වඩා ප්රමුඛ වේ; වෙනස්කම් සේවාදායකය නැවත ආරම්භ කළ විට ක්රියාත්මක වේ.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 06cc1d934541..49c8f233d668 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Povoľte adaptívne virtuálne vstupné pruhy pre každého nájomcu (tenant) pri odosielaní poskytovateľom (#9654): špička jedného nájomcu už nespôsobí 503 u iného. Premenná prostredia OMNIROUTE_CHAT_VIRTUAL_LANES má prednosť pred týmto nastavením v riadiacom paneli; zmeny sa prejavia po reštarte servera.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index 22d34add46c2..dc2ebee38fc4 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Omogoči prilagodljive navidezne sprejemne pasove za posameznega najemnika pri posredovanju ponudniku (#9654): nenaden porast zahtev enega najemnika ne povzroča več napak 503 pri drugem. Spremenljivka okolja OMNIROUTE_CHAT_VIRTUAL_LANES ima prednost pred to nastavitvijo nadzorne plošče; spremembe začnejo veljati po ponovnem zagonu strežnika.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index 1a2d81ddb942..6aa55b927f58 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Омогући по-закупцу адаптивне виртуелне линије пријема за расподелу провајдера (#9654): нагли скок захтева једног закупца више не изазива 503 грешку код другог. Env варијабла OMNIROUTE_CHAT_VIRTUAL_LANES има приоритет над овим прекидачем у контролној табли; промене се примењују при поновном покретању сервера.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index d509e3dfbc90..24f39a19fc29 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktivera adaptiva virtuella åtkomstfiler per tenant för providerutskick (#9654): en tenants burst ger inte längre en annan 503. Miljövariabeln OMNIROUTE_CHAT_VIRTUAL_LANES har företräde framför den här inställningen i instrumentpanelen; ändringarna träder i kraft vid omstart av servern.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 3c0018a144ec..629174be5245 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Washa njia za uandikishaji pepe zinazobadilika kwa kila mpangaji (tenant) kwa utumaji wa watoa huduma (#9654): mlipuko wa mpangaji mmoja hautoi tena 503 kwa mwingine. Kigezo cha mazingira cha OMNIROUTE_CHAT_VIRTUAL_LANES kinashinda mpangilio huu wa dashibodi; mabadiliko yanatumika wakati seva inapoanzishwa upya.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 99a3a6cdcd9b..d9c7b74fe18b 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "வழங்குநர் அனுப்பீட்டிற்கு ஒவ்வொரு குத்தகைதாரருக்கும் (tenant) தகவமைப்பு மெய்நிகர் சேர்க்கைப் பாதைகளை இயக்கு (#9654): ஒரு குத்தகைதாரரின் அதிகரிப்பு இனி மற்றொருவருக்கு 503 ஐ அளிக்காது. OMNIROUTE_CHAT_VIRTUAL_LANES சூழல் மாறி இந்த டாஷ்போர்டு அமைப்பை விட முன்னுரிமை பெறுகிறது; மாற்றங்கள் சேவையகம் மறுதொடக்கத்தில் நடைமுறைக்கு வரும்.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index d8e4783f7c33..d0ace81b9f7e 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "ప్రొవైడర్ డిస్పాచ్ కోసం ప్రతి-టెనెంట్ అడాప్టివ్ వర్చువల్ అడ్మిషన్ లేన్లను ప్రారంభించండి (#9654): ఒక టెనెంట్ బర్స్ట్ ఇకపై మరొక టెనెంట్కు 503 ఇవ్వదు. OMNIROUTE_CHAT_VIRTUAL_LANES ఎన్విరాన్మెంట్ వేరియబుల్ ఈ డాష్బోర్డ్ సెట్టింగ్ కంటే ప్రాధాన్యత పొందుతుంది; మార్పులు సర్వర్ పునఃప్రారంభంలో ప్రభావం చూపుతాయి.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index b7eef7ba239b..9ad2fe5e8907 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "เปิดใช้เลนรับเข้าเสมือนแบบปรับตัวต่อเทนแนนต์สำหรับการส่งไปยังผู้ให้บริการ (#9654): การพุ่งสูงของเทนแนนต์หนึ่งจะไม่ทำให้อีกเทนแนนต์ได้รับ 503 อีกต่อไป ตัวแปรสภาพแวดล้อม OMNIROUTE_CHAT_VIRTUAL_LANES มีผลเหนือการตั้งค่าแดชบอร์ดนี้ การเปลี่ยนแปลงมีผลเมื่อรีสตาร์ทเซิร์ฟเวอร์", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 921a100dba9c..a2c1a7125b9d 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Sağlayıcı gönderimi için kiracı başına uyarlanabilir sanal kabul şeritlerini etkinleştirin (#9654): bir kiracının ani yükü artık diğerinde 503 hatasına neden olmaz. OMNIROUTE_CHAT_VIRTUAL_LANES ortam değişkeni bu panel ayarına göre önceliklidir; değişiklikler sunucu yeniden başlatıldığında geçerli olur.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index ed07ebe6cc62..601254f7a2d8 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Увімкніть адаптивні віртуальні смуги допуску для кожного тенанта під час надсилання провайдерам (#9654): сплеск навантаження одного тенанта більше не викликає 503 в іншого. Змінна середовища OMNIROUTE_CHAT_VIRTUAL_LANES має пріоритет над цим налаштуванням у панелі; зміни набувають чинності після перезапуску сервера.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 21764b19b68a..3f782246af5c 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "پرووائیڈر بھیجنے کے لیے فی ٹیننٹ انکولی ورچوئل ایڈمیشن لین فعال کریں (#9654): ایک ٹیننٹ کا اچانک بوجھ اب دوسرے ٹیننٹ کو 503 نہیں دیتا۔ OMNIROUTE_CHAT_VIRTUAL_LANES ماحولیاتی متغیر اس ڈیش بورڈ سیٹنگ پر فوقیت رکھتا ہے؛ تبدیلیاں سرور دوبارہ شروع ہونے پر اثر انداز ہوتی ہیں۔", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index a1c085f7cf38..426c36546dd1 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -995,7 +995,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "Bật làn tiếp nhận ảo thích ứng cho từng đối tượng thuê (tenant) để phân phối nhà cung cấp (#9654): một đợt bùng phát của tenant này không còn trả 503 cho tenant khác. Biến môi trường OMNIROUTE_CHAT_VIRTUAL_LANES được ưu tiên hơn cài đặt bảng điều khiển này; các thay đổi có hiệu lực khi khởi động lại máy chủ.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "Các pool proxy và cơ chế xoay vòng theo tài khoản của opencode ngừng dùng lại một proxy vừa thất bại (thăm dò TCP bị từ chối, hoặc nhận 429 qua proxy đó) trong một khoảng thời gian theo tiến trình, tăng gấp đôi sau mỗi lần lặp lại, tối đa đến một giới hạn. Không ghi trạng thái proxy nào; khi mọi ứng viên đều bị tạm gác, lựa chọn vẫn như cũ. Tắt theo mặc định: thứ tự chọn giữ nguyên xoay vòng thông thường.", - "featureFlagProxySkipRecentlyFailedDescription": "Các pool proxy và cơ chế xoay vòng theo tài khoản của opencode ngừng dùng lại một proxy vừa thất bại (thăm dò TCP bị từ chối, hoặc nhận 429 qua proxy đó) trong một khoảng thời gian theo tiến trình, tăng gấp đôi sau mỗi lần lặp lại, tối đa đến một giới hạn. Không ghi trạng thái proxy nào; khi mọi ứng viên đều bị tạm gác, lựa chọn vẫn như cũ. Tắt theo mặc định: thứ tự chọn giữ nguyên xoay vòng thông thường.", "featureFlagProxyPoolEgressObservationDescription": "Hiển thị, bên dưới một nhóm proxy trong bảng điều khiển, số IP đầu ra quan sát được đã phục vụ các thành viên của nhóm trong 24 giờ qua, số kết nối đã dùng chúng và số lớn nhất thấy sau cùng một IP. Chỉ đọc, tính từ nhật ký proxy, không bao giờ dùng để định tuyến. Tắt theo mặc định: trình chỉnh sửa nhóm không đổi và tuyến quan sát trả về null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "Trong lượt kiểm tra sức khỏe proxy, cho phép một lần thăm dò bị đích từ chối (401/403/429: proxy đã chuyển tiếp, đích từ chối IP đầu ra này) đặt lại chuỗi lỗi liên tiếp của proxy, giống như một lần thăm dò được phục vụ. Tắt theo mặc định: lần từ chối vẫn trung lập và giữ nguyên chuỗi. Lỗi 5xx vẫn không kết luận trong mọi trường hợp, và lần từ chối không bao giờ xóa, vô hiệu hóa hay kích hoạt lại proxy.", "sidebar": { diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 3947dd5aae66..7f363529a869 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "为提供者调度启用按租户的自适应虚拟准入通道(#9654):一个租户的突发流量不再导致另一个租户收到 503。OMNIROUTE_CHAT_VIRTUAL_LANES 环境变量优先于此仪表板设置;更改在服务器重启后生效。", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index eec21fe8649d..5da99c77fe9e 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -994,7 +994,6 @@ "featureFlagChatVirtualLanesEnabledDescription": "為提供者調度啟用按租戶的自適應虛擬准入通道(#9654):一個租戶的突發流量不再導致另一個租戶收到 503。OMNIROUTE_CHAT_VIRTUAL_LANES 環境變數優先於此儀表板設定;變更在伺服器重新啟動後生效。", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "featureFlagProxyHealthBlockedResetsStreakDescription": "__MISSING__:In the proxy health sweep, let a probe the target refused (401/403/429: the proxy relayed, the destination refused this egress IP) reset the consecutive-failure streak of the proxy, like a served probe. Off by default: a refusal stays neutral and keeps the streak. A 5xx stays inconclusive either way, and a refusal never removes, disables or re-activates a proxy.", "sidebar": { diff --git a/src/lib/db/callLogStats.ts b/src/lib/db/callLogStats.ts index 35f726391916..24debd628e62 100644 --- a/src/lib/db/callLogStats.ts +++ b/src/lib/db/callLogStats.ts @@ -1,6 +1,5 @@ import { getDbInstance } from "./core"; import { ERROR_TYPE_CONTRACT } from "@omniroute/open-sse/services/errorClassifier.ts"; -import { ERROR_TYPE_CONTRACT } from "@omniroute/open-sse/services/errorClassifier.ts"; import { SEARCH_CREDENTIAL_FALLBACKS, SEARCH_PROVIDERS, diff --git a/src/shared/constants/featureFlagDefinitions.ts b/src/shared/constants/featureFlagDefinitions.ts index 6d4c3ad4f5ee..ee089d74a58c 100644 --- a/src/shared/constants/featureFlagDefinitions.ts +++ b/src/shared/constants/featureFlagDefinitions.ts @@ -239,6 +239,30 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ requiresRestart: false, warningLevel: "caution", }, + { + key: "OPENCODE_TRANSIENT_FAILOVER_BACKOFF", + label: "OpenCode Transient Failover Backoff", + description: + "For the OpenCode multi-account rotation, pause before dispatching to the next account once two consecutive attempts failed with a transient upstream error (5xx or an empty 400 rejection). The pause starts at 1.5s, doubles per further consecutive failure, is capped at 6s per pause and 10s per request, is skipped when the client disconnects, and the failed response body is released before waiting. Off by default: failover stays immediate.", + descriptionI18nKey: "featureFlagOpencodeTransientFailoverBackoffDescription", + category: "network", + defaultValue: "false", + type: "boolean", + requiresRestart: false, + warningLevel: "caution", + }, + { + key: "OPENCODE_RATE_LIMITED_429_EARLY_STOP", + label: "OpenCode Rate-Limited 429 Early Stop", + description: + "For the OpenCode multi-account rotation, stop the account wave at the first 429 classified as a real rate limit (a parseable Retry-After header, or a body naming a rate/usage limit) and return that upstream 429 unchanged (status, body, Retry-After and quota headers), instead of trying every remaining account. Unclassified 429s keep rotating. Off by default: the free tier is limited per egress IP (#9611), so every 429 rotates to the next account, and an exhausted wave returns the last upstream 429.", + descriptionI18nKey: "featureFlagOpencodeRateLimited429EarlyStopDescription", + category: "network", + defaultValue: "false", + type: "boolean", + requiresRestart: false, + warningLevel: "caution", + }, { key: "MITM_DISABLE_TLS_VERIFY", label: "Disable TLS Verify (MITM)", @@ -690,6 +714,18 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ requiresRestart: false, warningLevel: "caution", }, + { + key: "MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT", + label: "Mistral Ambiguous 401 Soft Lockout", + description: + 'A bare Mistral 401 ({"detail":"Unauthorized"}, no explicit auth signal) is byte-identical for a revoked key and for exhausted quota. When enabled, such a 401 cools the connection down instead of parking it as expired, up to 3 times within an hour; the next one still parks it as expired, so a revoked key converges. Off by default: every bare Mistral 401 parks the connection as expired, as before.', + descriptionI18nKey: "featureFlagMistralAmbiguous401SoftLockoutDescription", + category: "runtime", + defaultValue: "false", + type: "boolean", + requiresRestart: false, + warningLevel: "caution", + }, // ──────────────── CLI (5) ──────────────── { diff --git a/src/shared/utils/featureFlags.ts b/src/shared/utils/featureFlags.ts index 8ade30cb86dd..4f003e9e8ac2 100644 --- a/src/shared/utils/featureFlags.ts +++ b/src/shared/utils/featureFlags.ts @@ -255,6 +255,57 @@ export function isOpencodeUserBlockedRotationEnabled(): boolean { } } +/** + * OpenCode transient-failure failover pause (#13615). Opt-in: when off, failover to the next + * account stays immediate exactly as before. + * Fail closed: an unreadable flag store keeps the pre-flag behavior (disabled). + */ +export function isOpencodeTransientFailoverBackoffEnabled(): boolean { + try { + return isFeatureFlagEnabled("OPENCODE_TRANSIENT_FAILOVER_BACKOFF"); + } catch (error) { + console.error( + "[featureFlags] Failed to resolve OPENCODE_TRANSIENT_FAILOVER_BACKOFF, defaulting to disabled:", + error instanceof Error ? error.message : error + ); + return false; + } +} + +/** + * Mistral bare-401 bounded soft lockout (#13609). Opt-in: when off, a bare Mistral 401 parks + * the connection as expired exactly as before. + * Fail closed: an unreadable flag store keeps the pre-flag behavior (disabled). + */ +export function isMistralAmbiguous401SoftLockoutEnabled(): boolean { + try { + return isFeatureFlagEnabled("MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT"); + } catch (error) { + console.error( + "[featureFlags] Failed to resolve MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT, defaulting to disabled:", + error instanceof Error ? error.message : error + ); + return false; + } +} + +/** + * OpenCode classified-429 early stop (#13657). Opt-in: when off, every 429 rotates to the + * next account exactly as before. + * Fail closed: an unreadable flag store keeps the pre-flag behavior (disabled). + */ +export function isOpencodeRateLimited429EarlyStopEnabled(): boolean { + try { + return isFeatureFlagEnabled("OPENCODE_RATE_LIMITED_429_EARLY_STOP"); + } catch (error) { + console.error( + "[featureFlags] Failed to resolve OPENCODE_RATE_LIMITED_429_EARLY_STOP, defaulting to disabled:", + error instanceof Error ? error.message : error + ); + return false; + } +} + export function isServerOwnedToolLoopEnabled( reader: (key: string) => boolean = isFeatureFlagEnabled ): boolean { diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index a9be1beefa13..f1f5cb9dfec4 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -3157,11 +3157,12 @@ export async function markAccountUnavailable( let terminalStatus = resolveTerminalConnectionStatus( status, - result as { permanent?: boolean; creditsExhausted?: boolean }, + result as { permanent?: boolean; creditsExhausted?: boolean; ambiguousAuth?: boolean }, providerErrorType, provider, isPerModelQuotaProvider, - errorText + errorText, + connectionId ); // A still-valid access token after a successful refresh is not "expired". // A follow-up 401 (timeout, hop, race) must cooldown, not park the account. diff --git a/src/sse/services/authTerminalStatus.ts b/src/sse/services/authTerminalStatus.ts index b8afed537bd3..6ef2f50f8b3b 100644 --- a/src/sse/services/authTerminalStatus.ts +++ b/src/sse/services/authTerminalStatus.ts @@ -1,5 +1,6 @@ import { PROVIDER_ERROR_TYPES } from "@omniroute/open-sse/services/errorClassifier.ts"; import { isCreditsExhausted } from "@omniroute/open-sse/services/accountFallback.ts"; +import { takeMistralAmbiguous401SoftStrike } from "@omniroute/open-sse/services/accountFallback/mistralAmbiguousAuth.ts"; import { resolveProviderId, WEB_COOKIE_PROVIDERS } from "@/shared/constants/providers"; // #8200: cookie-auth providers (perplexity-web, grok-web, ...) use a rotating browser @@ -71,11 +72,12 @@ function isExpiredAuthFailure( export function resolveTerminalConnectionStatus( status: number, - result: { permanent?: boolean; creditsExhausted?: boolean }, + result: { permanent?: boolean; creditsExhausted?: boolean; ambiguousAuth?: boolean }, providerErrorType: string | null = null, provider: string | null = null, isPerModelQuotaProvider = false, - errorText: string = "" + errorText: string = "", + connectionId: string | null = null ): string | null { if (shouldParkCreditsExhausted(status, result, isPerModelQuotaProvider, errorText)) { return "credits_exhausted"; @@ -87,6 +89,12 @@ export function resolveTerminalConnectionStatus( return "banned"; } if (isExpiredAuthFailure(status, providerErrorType, provider)) { + // #13609: checkFallbackError only sets ambiguousAuth for a bare Mistral 401 + // with MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT on. Bounded per connection: past + // the strike limit the connection parks as expired like any other 401. + if (status === 401 && result.ambiguousAuth && connectionId) { + if (takeMistralAmbiguous401SoftStrike(connectionId)) return null; + } return "expired"; } return null; diff --git a/tests/unit/feature-flags-settings.test.ts b/tests/unit/feature-flags-settings.test.ts index 2b24d0f7b739..c521b3f79361 100644 --- a/tests/unit/feature-flags-settings.test.ts +++ b/tests/unit/feature-flags-settings.test.ts @@ -40,7 +40,7 @@ const { // the dead ONEPROXY_ENABLED (readerless since the 1proxy purge, #12091) // brought it back to 53. UNIVERSAL_CONTEXT_HANDOFF_ENABLED bumped it to 54. // #13641 added SEARCH_STATS_HIDE_DELETED_CONNECTIONS, bumping the count to 56. -const EXPECTED_FEATURE_FLAG_COUNT = 66; +const EXPECTED_FEATURE_FLAG_COUNT = 69; // ────────────────────────────────────────────────────── // Test group 1 — Flag definitions registry @@ -220,6 +220,39 @@ describe("featureFlagDefinitions", () => { assert.strictEqual(def.requiresRestart, false); }); + it("defines OPENCODE_TRANSIENT_FAILOVER_BACKOFF as an opt-in network boolean flag disabled by default", () => { + const def = FEATURE_FLAG_DEFINITIONS.find( + (d) => d.key === "OPENCODE_TRANSIENT_FAILOVER_BACKOFF" + ); + assert.ok(def, "OPENCODE_TRANSIENT_FAILOVER_BACKOFF should exist"); + assert.strictEqual(def.category, "network"); + assert.strictEqual(def.type, "boolean"); + assert.strictEqual(def.defaultValue, "false"); + assert.strictEqual(def.requiresRestart, false); + }); + + it("defines MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT as an opt-in runtime boolean flag disabled by default", () => { + const def = FEATURE_FLAG_DEFINITIONS.find( + (d) => d.key === "MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT" + ); + assert.ok(def, "MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT should exist"); + assert.strictEqual(def.category, "runtime"); + assert.strictEqual(def.type, "boolean"); + assert.strictEqual(def.defaultValue, "false"); + assert.strictEqual(def.requiresRestart, false); + }); + + it("defines OPENCODE_RATE_LIMITED_429_EARLY_STOP as an opt-in network boolean flag disabled by default", () => { + const def = FEATURE_FLAG_DEFINITIONS.find( + (d) => d.key === "OPENCODE_RATE_LIMITED_429_EARLY_STOP" + ); + assert.ok(def, "OPENCODE_RATE_LIMITED_429_EARLY_STOP should exist"); + assert.strictEqual(def.category, "network"); + assert.strictEqual(def.type, "boolean"); + assert.strictEqual(def.defaultValue, "false"); + assert.strictEqual(def.requiresRestart, false); + }); + it("defines network rotation shared-egress guard as a network boolean flag enabled by default", () => { const def = FEATURE_FLAG_DEFINITIONS.find( (d) => d.key === "NETWORK_ROTATION_SHARED_EGRESS_GUARD" diff --git a/tests/unit/i18n-catalogs-no-duplicate-keys.test.ts b/tests/unit/i18n-catalogs-no-duplicate-keys.test.ts new file mode 100644 index 000000000000..2b963454d36a --- /dev/null +++ b/tests/unit/i18n-catalogs-no-duplicate-keys.test.ts @@ -0,0 +1,123 @@ +/** + * Regression guard: no i18n catalog (dashboard `src/i18n/messages/*.json`, CLI + * `bin/cli/locales/*.json`) may declare the same key twice inside one object. + * + * `JSON.parse` silently keeps the LAST copy, so a duplicated key never fails a + * parse, a coverage gate, prettier or a focused test — it only shows up as a + * translation that "does not update" or as a diff that grows on every merge. + * The 2026-09-15 batch merges left `featureFlagProxySkipRecentlyFailedDescription` + * twice in 59 locales (a scripted keep-both conflict resolution concatenated the + * key both PRs carried). This test reads the raw text, not the parsed object. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readdirSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); +const CATALOG_DIRS = ["src/i18n/messages", "bin/cli/locales"]; + +interface ObjectFrame { + type: "obj"; + keys: Set; + expectKey: boolean; + lastKey: string; + path: string[]; +} +interface ArrayFrame { + type: "arr"; + path: string[]; +} +type Frame = ObjectFrame | ArrayFrame; + +/** Returns the dotted paths of every key declared more than once in its object. */ +export function findDuplicateKeys(text: string): string[] { + const dups: string[] = []; + const stack: Frame[] = []; + let i = 0; + const n = text.length; + + const readString = (): string => { + // text[i] is the opening quote; escapes are kept raw — identity only matters here + let j = i + 1; + let out = ""; + while (j < n) { + const c = text[j]; + if (c === "\\") { + out += c + text[j + 1]; + j += 2; + continue; + } + if (c === '"') break; + out += c; + j++; + } + i = j + 1; + return out; + }; + + const childPath = (): string[] => { + const parent = stack[stack.length - 1]; + if (!parent) return []; + return parent.type === "obj" ? [...parent.path, parent.lastKey] : [...parent.path, "[]"]; + }; + + while (i < n) { + const c = text[i]; + if (c === '"') { + const s = readString(); + const top = stack[stack.length - 1]; + if (top && top.type === "obj" && top.expectKey) { + if (top.keys.has(s)) dups.push([...top.path, s].join(".")); + top.keys.add(s); + top.expectKey = false; + top.lastKey = s; + } + continue; + } + if (c === "{") { + stack.push({ type: "obj", keys: new Set(), expectKey: true, lastKey: "", path: childPath() }); + } else if (c === "[") { + stack.push({ type: "arr", path: childPath() }); + } else if (c === "}" || c === "]") { + stack.pop(); + } else if (c === ",") { + const top = stack[stack.length - 1]; + if (top && top.type === "obj") top.expectKey = true; + } + i++; // colons, whitespace and primitive values carry no key information + } + return dups; +} + +test("findDuplicateKeys reports repeated keys per object and nothing else", () => { + assert.deepEqual(findDuplicateKeys('{"a": 1, "a": 2}'), ["a"]); + assert.deepEqual( + findDuplicateKeys('{"a": {"x": 1}, "b": {"x": 1}, "c": ["x", "x"], "d": "a"}'), + [] + ); + assert.deepEqual(findDuplicateKeys('{"a": {"x": 1, "x": 2}}'), ["a.x"]); + assert.deepEqual(findDuplicateKeys('{"k": "v", "k": "v"}'), ["k"]); + assert.deepEqual(findDuplicateKeys('{"esc\\"aped": 1, "esc\\"aped": 2}'), ['esc\\"aped']); + assert.deepEqual(findDuplicateKeys('{"n": [{"a": 1}, {"a": 1}]}'), []); +}); + +for (const dir of CATALOG_DIRS) { + test(`${dir}: no catalog declares the same key twice in one object`, () => { + const files = readdirSync(path.join(ROOT, dir)) + .filter((f) => f.endsWith(".json")) + .sort(); + assert.ok(files.length > 0, `no catalogs found under ${dir}`); + const offenders: string[] = []; + for (const f of files) { + const dups = [...new Set(findDuplicateKeys(readFileSync(path.join(ROOT, dir, f), "utf8")))]; + if (dups.length > 0) offenders.push(`${f}: ${dups.join(", ")}`); + } + assert.deepEqual( + offenders, + [], + `duplicated keys — JSON.parse keeps only the last copy:\n${offenders.join("\n")}` + ); + }); +} diff --git a/tests/unit/opencode-rate-limited-classify.test.ts b/tests/unit/opencode-rate-limited-classify.test.ts new file mode 100644 index 000000000000..43cbcc3b595e --- /dev/null +++ b/tests/unit/opencode-rate-limited-classify.test.ts @@ -0,0 +1,277 @@ +import { describe, it, beforeEach, afterEach, before, after } from "node:test"; +import assert from "node:assert"; +import net from "node:net"; +import { + classify429, + classifyUpstream429, + parseRetryAfterSeconds, + readBodyPrefix, + RATE_LIMIT_BODY_SNIFF_BYTES, +} from "../../open-sse/executors/opencodeRateLimited.ts"; +import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; +import type { ExecutorLog, ProviderCredentials } from "../../open-sse/executors/base.ts"; +import { resolveProxyForRequest } from "../../open-sse/utils/proxyFetch.ts"; +import { getProviderErrorRuleMatch } from "../../open-sse/config/providerErrorRules.ts"; +import { resetDbInstance } from "../../src/lib/db/core.ts"; + +// #13657 rework: the 429 classifier is kept; stopping the cross-account wave at a +// classified 429 is opt-in (OPENCODE_RATE_LIMITED_429_EARLY_STOP, default off — +// the free tier is per egress IP, #9611). Whatever ends the wave, the client gets +// the REAL last upstream 429 (status, body, Retry-After, quota headers), never a +// synthetic drain, so the opencode provider error rules keep matching it. +const FLAG = "OPENCODE_RATE_LIMITED_429_EARLY_STOP"; +const log: ExecutorLog = { debug() {}, info() {}, warn() {}, error() {} }; +const FPS = ["x", "y", "z"].map((c) => c.repeat(32)); +const MONTHLY_BODY = JSON.stringify({ + error: { + message: + "[429] Monthly usage limit reached. Resets in 13 days. To continue using this model now, enable usage from your available balance.", + }, +}); + +describe("classify429 / parseRetryAfterSeconds", () => { + it("a parseable Retry-After alone classifies rate_limited", () => { + assert.strictEqual(classify429({ retryAfter: "30" }), "rate_limited"); + assert.strictEqual(classify429({ retryAfter: 45 }), "rate_limited"); + const future = new Date(Date.now() + 120_000).toUTCString(); + assert.strictEqual(classify429({ retryAfter: future }), "rate_limited"); + }); + + it("a body naming a rate or usage limit classifies rate_limited", () => { + assert.strictEqual(classify429({ bodyText: "Rate limited, slow down" }), "rate_limited"); + assert.strictEqual(classify429({ bodyText: "Too many requests" }), "rate_limited"); + assert.strictEqual(classify429({ bodyText: MONTHLY_BODY }), "rate_limited"); + }); + + it("anything else is a burst", () => { + assert.strictEqual(classify429({}), "burst"); + assert.strictEqual(classify429({ bodyText: '{"error":"boom"}' }), "burst"); + assert.strictEqual(classify429({ retryAfter: "not-a-date" }), "burst"); + assert.strictEqual(classify429({ retryAfter: "" }), "burst"); + }); + + it("parses delta-seconds and HTTP dates against an injected clock", () => { + const now = Date.parse("2026-09-15T00:00:00Z"); + assert.strictEqual(parseRetryAfterSeconds("30", now), 30); + assert.strictEqual(parseRetryAfterSeconds("Tue, 15 Sep 2026 00:02:00 GMT", now), 120); + assert.strictEqual(parseRetryAfterSeconds("not-a-date", now), null); + assert.strictEqual(parseRetryAfterSeconds(-5, now), null); + }); +}); + +describe("readBodyPrefix / classifyUpstream429", () => { + it("reads only a bounded prefix and leaves the original body intact", async () => { + const body = "a".repeat(RATE_LIMIT_BODY_SNIFF_BYTES) + " rate limited"; + const response = new Response(body, { status: 429 }); + const prefix = await readBodyPrefix(response); + assert.strictEqual( + prefix?.length, + RATE_LIMIT_BODY_SNIFF_BYTES, + "signal past the cap is unseen" + ); + assert.strictEqual(await classifyUpstream429(response), "burst"); + assert.strictEqual(response.bodyUsed, false); + assert.strictEqual(await response.text(), body, "the caller still gets the full body"); + }); + + it("checks the header before touching the body", async () => { + const response = new Response("Too many requests", { + status: 429, + headers: { "Retry-After": "7" }, + }); + assert.strictEqual(await classifyUpstream429(response), "rate_limited"); + assert.strictEqual(await response.text(), "Too many requests"); + }); +}); + +describe("OpencodeExecutor 429 wave", () => { + const servers: net.Server[] = []; + const ports: number[] = []; + let originalFetch: typeof globalThis.fetch; + let priorFlag: string | undefined; + let observed: string[]; + + before(async () => { + for (let i = 0; i < FPS.length; i++) { + const server = net.createServer((s) => s.destroy()); + servers.push(server); + ports.push( + await new Promise((resolve) => + server.listen(0, "127.0.0.1", () => resolve((server.address() as net.AddressInfo).port)) + ) + ); + } + }); + + after(() => { + servers.forEach((s) => s.close()); + resetDbInstance(); + }); + + beforeEach(() => { + originalFetch = globalThis.fetch; + priorFlag = process.env[FLAG]; + observed = []; + }); + + afterEach(() => { + globalThis.fetch = originalFetch; + if (priorFlag === undefined) delete process.env[FLAG]; + else process.env[FLAG] = priorFlag; + }); + + type Step = { status: number; body?: string; headers?: Record }; + + function installFetch(plan: Step[]) { + let call = 0; + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const resolved = resolveProxyForRequest(url); + observed.push(resolved.proxyUrl ? new URL(resolved.proxyUrl).port : "direct"); + const step = plan[Math.min(call, plan.length - 1)]; + call++; + return new Response(step.body ?? JSON.stringify({ ok: step.status === 200, call }), { + status: step.status, + headers: { "Content-Type": "application/json", ...(step.headers ?? {}) }, + }); + }) as typeof globalThis.fetch; + } + + function credentials(count: number): ProviderCredentials { + const fingerprints = FPS.slice(0, count); + return { + apiKey: null, + accessToken: null, + connectionId: "noauth", + providerSpecificData: { + fingerprints, + accountProxies: fingerprints.map((fp, i) => ({ + fingerprint: fp, + proxy: { type: "http", host: "127.0.0.1", port: ports[i] }, + })), + }, + }; + } + + async function run(exec: OpencodeExecutor, count: number) { + const result = (await exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials: credentials(count), + log, + })) as { response: Response }; + return result.response; + } + + function cooled(exec: OpencodeExecutor): number { + const accounts = (exec as unknown as { accounts: Array<{ cooldownUntil: number }> }).accounts; + return accounts.filter((a) => a.cooldownUntil > Date.now()).length; + } + + const RATE_LIMITED: Step = { + status: 429, + body: MONTHLY_BODY, + headers: { "Retry-After": "30", "x-ratelimit-remaining-requests": "0" }, + }; + + it("flag off: a classified 429 still rotates to the next account (#9611)", async () => { + delete process.env[FLAG]; + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([RATE_LIMITED, { status: 200 }]); + + const response = await run(exec, 2); + + assert.strictEqual(response.status, 200); + assert.strictEqual(observed.length, 2); + await response.body?.cancel(); + }); + + it("flag off: an exhausted wave returns the last real upstream 429 untouched", async () => { + delete process.env[FLAG]; + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([ + { status: 429, body: '{"error":"first"}' }, + { status: 429, body: '{"error":"second"}' }, + RATE_LIMITED, + ]); + + const response = await run(exec, 3); + + assert.strictEqual(observed.length, 3); + assert.strictEqual(response.status, 429); + assert.strictEqual(response.headers.get("retry-after"), "30"); + assert.strictEqual(response.headers.get("x-ratelimit-remaining-requests"), "0"); + assert.strictEqual(response.headers.get("x-opencode-retry-state"), null, "nothing synthetic"); + assert.strictEqual(await response.text(), MONTHLY_BODY); + }); + + describe("flag on", () => { + beforeEach(() => { + process.env[FLAG] = "true"; + }); + + it("stops at the first classified 429 and returns it untouched", async () => { + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([RATE_LIMITED, { status: 200 }]); + + const response = await run(exec, 3); + + assert.strictEqual(observed.length, 1, "no further account is tried"); + assert.strictEqual(response.status, 429); + assert.strictEqual(response.headers.get("retry-after"), "30"); + assert.strictEqual(response.headers.get("x-ratelimit-remaining-requests"), "0"); + assert.strictEqual(cooled(exec), 1, "the rate-limited account is cooled down"); + const text = await response.text(); + assert.strictEqual(text, MONTHLY_BODY, "upstream body preserved"); + const rule = getProviderErrorRuleMatch( + "opencode-zen", + 429, + Object.fromEntries(response.headers.entries()), + JSON.parse(text) + ); + assert.strictEqual(rule?.reason, "quota_exhausted", "provider error rules still match"); + assert.ok((rule?.cooldownMs ?? 0) > 24 * 60 * 60 * 1000, "the 13-day reset still applies"); + }); + + it("a body-only signal stops without inventing a Retry-After", async () => { + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([{ status: 429, body: '{"error":"Rate limited"}' }, { status: 200 }]); + + const response = await run(exec, 2); + + assert.strictEqual(observed.length, 1); + assert.strictEqual(response.status, 429); + assert.strictEqual(response.headers.get("retry-after"), null); + assert.strictEqual(await response.text(), '{"error":"Rate limited"}'); + }); + + it("an unclassified (burst) 429 keeps rotating", async () => { + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([{ status: 429, body: '{"error":"boom"}' }, { status: 200 }]); + + const response = await run(exec, 2); + + assert.strictEqual(response.status, 200); + assert.strictEqual(observed.length, 2); + await response.body?.cancel(); + }); + + it("an all-burst wave still returns the last real upstream 429", async () => { + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([ + { status: 429, body: '{"error":"a"}' }, + { status: 429, body: '{"error":"b"}', headers: { "x-upstream": "last" } }, + ]); + + const response = await run(exec, 2); + + assert.strictEqual(observed.length, 2); + assert.strictEqual(response.status, 429); + assert.strictEqual(response.headers.get("x-upstream"), "last"); + assert.strictEqual(await response.text(), '{"error":"b"}'); + }); + }); +}); diff --git a/tests/unit/opencode-transient-retry-delay.test.ts b/tests/unit/opencode-transient-retry-delay.test.ts new file mode 100644 index 000000000000..c4bfb76a78ea --- /dev/null +++ b/tests/unit/opencode-transient-retry-delay.test.ts @@ -0,0 +1,299 @@ +import { describe, it, beforeEach, afterEach, before, after } from "node:test"; +import assert from "node:assert"; +import net from "node:net"; +import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; +import { BaseExecutor } from "../../open-sse/executors/base.ts"; +import type { ExecutorLog, ProviderCredentials } from "../../open-sse/executors/base.ts"; +import { + TRANSIENT_RETRY_BASE_DELAY_MS, + TRANSIENT_RETRY_MAX_DELAY_MS, + TRANSIENT_RETRY_TOTAL_BUDGET_MS, + transientRetryDelayMs, + sleepAbortable, +} from "../../open-sse/executors/opencodeTransientFailure.ts"; +import { resolveProxyForRequest } from "../../open-sse/utils/proxyFetch.ts"; +import { resetDbInstance } from "../../src/lib/db/core.ts"; + +// #13615 rework: the failover pause is opt-in (OPENCODE_TRANSIENT_FAILOVER_BACKOFF, +// default off), bounded (per-pause cap + per-request budget), honors the client +// abort signal and releases the failed body before waiting. The executor's sleep +// is injected, so no test waits on a real 1.5s timer. +const FLAG = "OPENCODE_TRANSIENT_FAILOVER_BACKOFF"; +const log: ExecutorLog = { debug() {}, info() {}, warn() {}, error() {} }; +const FPS = ["a", "b", "c", "d", "e", "f", "g"].map((c) => c.repeat(32)); + +const servers: net.Server[] = []; +const ports: number[] = []; + +function listen(server: net.Server): Promise { + return new Promise((resolve) => { + server.listen(0, "127.0.0.1", () => resolve((server.address() as net.AddressInfo).port)); + }); +} + +before(async () => { + for (let i = 0; i < FPS.length; i++) { + const server = net.createServer((s) => s.destroy()); + servers.push(server); + ports.push(await listen(server)); + } +}); + +after(() => { + servers.forEach((s) => s.close()); + resetDbInstance(); +}); + +function credentialsFor(count: number): ProviderCredentials { + const fingerprints = FPS.slice(0, count); + return { + apiKey: null, + accessToken: null, + connectionId: "noauth", + providerSpecificData: { + fingerprints, + accountProxies: fingerprints.map((fp, i) => ({ + fingerprint: fp, + proxy: { type: "http", host: "127.0.0.1", port: ports[i] }, + })), + }, + }; +} + +const GEO_BODY = JSON.stringify({ + error: { type: "RegionError", message: "This model is not available in your country." }, +}); +// Empty upstream rejection: 400 without an error field (see isEmptyUpstreamRejection). +const EMPTY_BODY = + '{"id":"chatcmpl_44fn2g6e7kk","object":"chat.completion","created":1787419957,"model":"muse-spark-1.2-contributor-free","choices":[{"index":0,"message":{"role":"assistant"},"finish_reason":null}]}'; + +describe("transient failover pause helpers", () => { + it("uses its argument: nothing before the second failure, then bounded doubling", () => { + assert.strictEqual(TRANSIENT_RETRY_BASE_DELAY_MS, BaseExecutor.WAF_RETRY_CONFIG.delayMs); + assert.strictEqual(transientRetryDelayMs(0), 0); + assert.strictEqual(transientRetryDelayMs(1), 0); + assert.strictEqual(transientRetryDelayMs(2), 1500); + assert.strictEqual(transientRetryDelayMs(3), 3000); + assert.strictEqual(transientRetryDelayMs(4), TRANSIENT_RETRY_MAX_DELAY_MS); + assert.strictEqual(transientRetryDelayMs(50), TRANSIENT_RETRY_MAX_DELAY_MS); + assert.strictEqual(transientRetryDelayMs(Number.NaN), 0); + }); + + it("never exceeds what is left of the per-request budget", () => { + assert.strictEqual(transientRetryDelayMs(4, TRANSIENT_RETRY_TOTAL_BUDGET_MS - 1000), 1000); + assert.strictEqual(transientRetryDelayMs(4, TRANSIENT_RETRY_TOTAL_BUDGET_MS), 0); + assert.strictEqual(transientRetryDelayMs(2, TRANSIENT_RETRY_TOTAL_BUDGET_MS + 5), 0); + }); + + it("sleepAbortable resolves true after the delay and false on abort", async () => { + assert.strictEqual(await sleepAbortable(5), true); + assert.strictEqual(await sleepAbortable(5, new AbortController().signal), true); + const controller = new AbortController(); + const pending = sleepAbortable(60_000, controller.signal); + controller.abort(); + assert.strictEqual(await pending, false); + const aborted = new AbortController(); + aborted.abort(); + assert.strictEqual(await sleepAbortable(60_000, aborted.signal), false); + }); +}); + +describe("opencode rotation with OPENCODE_TRANSIENT_FAILOVER_BACKOFF", () => { + let originalFetch: typeof globalThis.fetch; + let priorFlag: string | undefined; + let observed: string[]; + let upstream: Response[]; + let sleeps: number[]; + // Filled per test: what each dispatched attempt answers. + let events: string[]; + + beforeEach(() => { + originalFetch = globalThis.fetch; + priorFlag = process.env[FLAG]; + process.env[FLAG] = "true"; + observed = []; + upstream = []; + sleeps = []; + events = []; + }); + + afterEach(() => { + globalThis.fetch = originalFetch; + if (priorFlag === undefined) delete process.env[FLAG]; + else process.env[FLAG] = priorFlag; + }); + + function installFetch(plan: Array<{ status: number; body?: string }>) { + let call = 0; + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const resolved = resolveProxyForRequest(url); + observed.push(resolved.proxyUrl ? new URL(resolved.proxyUrl).port : "direct"); + const step = plan[Math.min(call, plan.length - 1)]; + call++; + events.push(`dispatch:${step.status}`); + const response = new Response(step.body ?? JSON.stringify({ ok: step.status === 200 }), { + status: step.status, + headers: { "Content-Type": "application/json", "x-upstream-call": String(call) }, + }); + upstream.push(response); + return response; + }) as typeof globalThis.fetch; + } + + function newExecutor(onSleep?: (ms: number) => boolean): OpencodeExecutor { + const exec = new OpencodeExecutor("opencode-zen"); + exec.transientPauseSleep = async (ms, signal) => { + sleeps.push(ms); + events.push(`sleep:${ms}`); + if (signal?.aborted) return false; + return onSleep ? onSleep(ms) : true; + }; + return exec; + } + + async function run(exec: OpencodeExecutor, count: number, signal: AbortSignal | null = null) { + const result = (await exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal, + credentials: credentialsFor(count), + log, + })) as { response: Response }; + return result.response; + } + + it("flag off: failover stays immediate even after a long transient streak", async () => { + delete process.env[FLAG]; + const exec = newExecutor(); + installFetch([{ status: 500 }, { status: 502 }, { status: 503 }, { status: 200 }]); + + const response = await run(exec, 4); + + assert.strictEqual(response.status, 200); + assert.strictEqual(observed.length, 4); + assert.deepStrictEqual(sleeps, [], "no pause without the flag"); + assert.strictEqual(upstream[0].bodyUsed, false, "flag off never touches failed bodies"); + await response.body?.cancel(); + }); + + it("the first retry after one transient failure is immediate", async () => { + const exec = newExecutor(); + installFetch([{ status: 500 }, { status: 200 }]); + + const response = await run(exec, 2); + + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(sleeps, []); + await response.body?.cancel(); + }); + + it("pauses before the third account, after releasing the failed body", async () => { + const exec = newExecutor(); + installFetch([{ status: 500 }, { status: 500 }, { status: 200 }]); + + const response = await run(exec, 3); + + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(events, ["dispatch:500", "dispatch:500", "sleep:1500", "dispatch:200"]); + assert.strictEqual(upstream[1].bodyUsed, true, "the failed body is cancelled before sleeping"); + await response.body?.cancel(); + }); + + it("backs off with its argument, bounded by the per-request budget", async () => { + const exec = newExecutor(); + installFetch([ + { status: 500 }, + { status: 500 }, + { status: 500 }, + { status: 500 }, + { status: 200 }, + ]); + + const response = await run(exec, 5); + + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(sleeps, [1500, 3000, 5500], "1.5s, 3s, then the 10s budget remainder"); + await response.body?.cancel(); + }); + + it("stops pausing once the per-request budget is spent", async () => { + const exec = newExecutor(); + installFetch([ + { status: 500 }, + { status: 500 }, + { status: 500 }, + { status: 500 }, + { status: 500 }, + { status: 500 }, + { status: 200 }, + ]); + + const response = await run(exec, 7); + + assert.strictEqual(response.status, 200); + assert.strictEqual(observed.length, 7); + assert.strictEqual( + sleeps.reduce((a, b) => a + b, 0), + TRANSIENT_RETRY_TOTAL_BUDGET_MS, + "total pause time is bounded" + ); + await response.body?.cancel(); + }); + + it("a mixed streak (500 then empty 400) pauses; a 429 or geo 403 resets it", async () => { + const mixed = newExecutor(); + installFetch([{ status: 500 }, { status: 400, body: EMPTY_BODY }, { status: 200 }]); + const mixedResponse = await run(mixed, 3); + assert.strictEqual(mixedResponse.status, 200); + assert.deepStrictEqual(sleeps, [1500]); + await mixedResponse.body?.cancel(); + + for (const breaker of [{ status: 429 }, { status: 403, body: GEO_BODY }]) { + sleeps = []; + events = []; + const exec = newExecutor(); + installFetch([{ status: 500 }, breaker, { status: 500 }, { status: 200 }]); + const response = await run(exec, 4); + assert.strictEqual(response.status, 200); + assert.deepStrictEqual(sleeps, [], `${breaker.status} breaks the streak`); + await response.body?.cancel(); + } + }); + + it("a client abort during the pause dispatches nothing more", async () => { + const controller = new AbortController(); + const exec = newExecutor(() => { + controller.abort(); + return false; + }); + installFetch([{ status: 500 }, { status: 500 }, { status: 200 }]); + + const response = await run(exec, 3, controller.signal); + + assert.strictEqual(observed.length, 2, "no third dispatch after the abort"); + assert.strictEqual(response.status, 500, "the last failure status is surfaced"); + assert.strictEqual(response.headers.get("x-upstream-call"), "2", "its headers are kept"); + }); + + it("an already-aborted signal skips the pause and the dispatch", async () => { + const controller = new AbortController(); + const exec = newExecutor(); + installFetch([{ status: 500 }, { status: 500 }, { status: 200 }]); + let calls = 0; + const realFetch = globalThis.fetch; + globalThis.fetch = (async (...args: Parameters) => { + calls++; + const response = await realFetch(...args); + if (calls === 2) controller.abort(); + return response; + }) as typeof globalThis.fetch; + + const response = await run(exec, 3, controller.signal); + + assert.strictEqual(calls, 2); + assert.strictEqual(response.status, 500); + }); +}); diff --git a/tests/unit/provider-401-ambiguous-runtime.test.ts b/tests/unit/provider-401-ambiguous-runtime.test.ts new file mode 100644 index 000000000000..15422df01c85 --- /dev/null +++ b/tests/unit/provider-401-ambiguous-runtime.test.ts @@ -0,0 +1,227 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// #13609 rework: a bare Mistral 401 is byte-identical for a revoked key and an +// exhausted quota (#7638). With MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT on, it cools the +// connection down instead of parking it as expired — at most 3 times per hour per +// connection, then it parks, so a revoked key still converges. Flag off (default): +// every bare Mistral 401 parks the connection as before. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-13609-mistral-401-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const FLAG = "MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT"; +const BARE = '{"detail":"Unauthorized"}'; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const auth = await import("../../src/sse/services/auth.ts"); +const { checkFallbackError } = await import("../../open-sse/services/accountFallback.ts"); +const { resolveTerminalConnectionStatus } = + await import("../../src/sse/services/authTerminalStatus.ts"); +const { classifyProviderError, PROVIDER_ERROR_TYPES } = + await import("../../open-sse/services/errorClassifier.ts"); +const { setOperatorProviderErrorRules } = + await import("../../open-sse/config/providerErrorRules.ts"); +const { + isMistralAmbiguous401, + takeMistralAmbiguous401SoftStrike, + resetMistralAmbiguous401Strikes, + MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES, + MISTRAL_AMBIGUOUS_401_STRIKE_WINDOW_MS, +} = await import("../../open-sse/services/accountFallback/mistralAmbiguousAuth.ts"); +const { classifyFailure } = await import("../../src/app/api/providers/[id]/test/route.ts"); + +const priorFlag = process.env[FLAG]; + +function setFlag(value: string | undefined) { + if (value === undefined) delete process.env[FLAG]; + else process.env[FLAG] = value; +} + +test.beforeEach(() => { + resetMistralAmbiguous401Strikes(); + setFlag(undefined); +}); + +test.after(() => { + setFlag(priorFlag); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function bareMistral401() { + return checkFallbackError(401, BARE, 0, null, "mistral", null, null, null); +} + +test("one shared predicate: the connection-test diagnosis and the runtime agree", () => { + assert.equal(isMistralAmbiguous401("mistral", BARE), true); + assert.equal(isMistralAmbiguous401("mistral", "Invalid API key"), false); + assert.equal(isMistralAmbiguous401("openai", BARE), false); + assert.equal( + classifyFailure({ error: BARE, statusCode: 401, provider: "mistral" }).type, + "upstream_ambiguous_auth_or_quota" + ); + assert.equal( + classifyFailure({ error: "Token invalid", statusCode: 401, provider: "mistral" }).type, + "upstream_auth_error" + ); +}); + +test("flag off: a bare Mistral 401 stays an auth_error and resolves expired", () => { + const r = bareMistral401(); + assert.equal(r.reason, "auth_error"); + assert.equal(r.ambiguousAuth, undefined); + const type = classifyProviderError(401, BARE, "mistral"); + assert.equal( + resolveTerminalConnectionStatus(401, r, type, "mistral", false, BARE, "c1"), + "expired" + ); +}); + +test("flag on: a bare Mistral 401 backs off instead of asserting an auth failure", () => { + setFlag("true"); + const r = bareMistral401(); + assert.notEqual(r.reason, "auth_error"); + assert.equal(r.ambiguousAuth, true); + assert.equal(r.shouldFallback, true); + assert.ok(!r.permanent); + assert.ok(r.cooldownMs > 0, "a real cooldown, not an immediate reselect"); +}); + +test("flag on: explicit auth signals, other providers and operator rules are unchanged", () => { + setFlag("true"); + for (const body of ["Invalid API key", "token invalid", "revoked", "access denied"]) { + const r = checkFallbackError(401, body, 0, null, "mistral", null, null, null); + assert.equal(r.reason, "auth_error", body); + assert.equal(r.ambiguousAuth, undefined, body); + } + assert.equal( + checkFallbackError(401, BARE, 0, null, "openai", null, null, null).reason, + "auth_error" + ); + setOperatorProviderErrorRules({ + mistral: [{ status: 401, match: "unauthorized", scope: "connection", cooldownMs: 99999 }], + }); + try { + const r = bareMistral401(); + assert.equal(r.reason, "quota_exhausted"); + assert.equal(r.cooldownMs, 99999); + assert.equal(r.ambiguousAuth, undefined); + } finally { + setOperatorProviderErrorRules({}); + } +}); + +test("strike bound: 3 soft strikes per window, the 4th parks and restarts the count", () => { + const t0 = 1_000_000; + for (let i = 1; i <= MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES; i++) { + assert.equal(takeMistralAmbiguous401SoftStrike("conn", t0 + i), true, `strike ${i}`); + } + assert.equal(takeMistralAmbiguous401SoftStrike("conn", t0 + 10), false, "bound reached"); + assert.equal(takeMistralAmbiguous401SoftStrike("conn", t0 + 11), true, "fresh count after park"); + assert.equal(takeMistralAmbiguous401SoftStrike("other", t0 + 12), true, "per connection"); + // Strikes older than the window do not accumulate. + resetMistralAmbiguous401Strikes(); + for (let i = 0; i < MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES; i++) { + takeMistralAmbiguous401SoftStrike("slow", t0); + } + assert.equal( + takeMistralAmbiguous401SoftStrike("slow", t0 + MISTRAL_AMBIGUOUS_401_STRIKE_WINDOW_MS), + true + ); +}); + +test("resolveTerminalConnectionStatus ignores ambiguousAuth without a connection id or for other types", () => { + const r = { ambiguousAuth: true }; + assert.equal( + resolveTerminalConnectionStatus( + 401, + r, + PROVIDER_ERROR_TYPES.UNAUTHORIZED, + "mistral", + false, + BARE + ), + "expired" + ); + assert.equal( + resolveTerminalConnectionStatus( + 401, + {}, + PROVIDER_ERROR_TYPES.UNAUTHORIZED, + "mistral", + false, + BARE, + "c" + ), + "expired" + ); +}); + +async function createMistralConnection() { + const conn = await providersDb.createProviderConnection({ + provider: "mistral", + authType: "apikey", + apiKey: "mistral-test-key", + isActive: true, + testStatus: "active", + }); + return String(conn.id); +} + +async function expireCooldown(connId: string) { + // What selection-time auto-decay does once rateLimitedUntil has passed. + await providersDb.updateProviderConnection(connId, { + rateLimitedUntil: null, + testStatus: "active", + }); +} + +test("markAccountUnavailable, flag off: one bare Mistral 401 parks the connection as expired", async () => { + const connId = await createMistralConnection(); + + await auth.markAccountUnavailable(connId, 401, BARE, "mistral", "mistral-large-latest"); + + const after = await providersDb.getProviderConnectionById(connId); + assert.equal(after.testStatus, "expired"); +}); + +test("markAccountUnavailable, flag on: cooldown for 3 bare 401s, then expired", async () => { + setFlag("true"); + const connId = await createMistralConnection(); + + for (let strike = 1; strike <= MISTRAL_AMBIGUOUS_401_MAX_SOFT_STRIKES; strike++) { + await auth.markAccountUnavailable(connId, 401, BARE, "mistral", "mistral-large-latest"); + const cooling = await providersDb.getProviderConnectionById(connId); + assert.equal(cooling.testStatus, "unavailable", `strike ${strike} cools down`); + assert.ok( + new Date(String(cooling.rateLimitedUntil)).getTime() > Date.now(), + `strike ${strike} sets a future rateLimitedUntil` + ); + await expireCooldown(connId); + } + + await auth.markAccountUnavailable(connId, 401, BARE, "mistral", "mistral-large-latest"); + const parked = await providersDb.getProviderConnectionById(connId); + assert.equal(parked.testStatus, "expired", "a persistent bare 401 still converges"); +}); + +test("markAccountUnavailable, flag on: an explicit auth signal parks on the first 401", async () => { + setFlag("true"); + const connId = await createMistralConnection(); + + await auth.markAccountUnavailable( + connId, + 401, + "Invalid API key", + "mistral", + "mistral-large-latest" + ); + + const after = await providersDb.getProviderConnectionById(connId); + assert.equal(after.testStatus, "expired"); +}); diff --git a/tests/unit/server-owned-tool-loop-flag.test.ts b/tests/unit/server-owned-tool-loop-flag.test.ts index 3cd0e4d72d83..efd78172870c 100644 --- a/tests/unit/server-owned-tool-loop-flag.test.ts +++ b/tests/unit/server-owned-tool-loop-flag.test.ts @@ -68,7 +68,7 @@ describe("isServerOwnedToolLoopEnabled wrapper", () => { describe("feature-flags-settings count update", () => { it("flag count matches updated expected value", () => { - assert.equal(FEATURE_FLAG_DEFINITIONS.length, 66); + assert.equal(FEATURE_FLAG_DEFINITIONS.length, 69); }); });