diff --git a/.env.example b/.env.example index e6c8f35c8f0..f4e037814a1 100644 --- a/.env.example +++ b/.env.example @@ -2131,6 +2131,12 @@ APP_LOG_TO_FILE=true # Used by: open-sse/services/rateLimitManager.ts # RATE_LIMIT_MAX_WAIT_MS=15000 +# Limiter-managed execution backstop (Bottleneck `expiration`): bounds a job's +# post-dispatch execution, never queue wait. Must stay ABOVE upstream +# fetch-start timeouts on non-incremental gateways. Default: 600000 (10 min) +# Used by: open-sse/services/rateLimitManager.ts +# RATE_LIMIT_EXECUTION_MAX_WAIT_MS=600000 + # Rate limit queue admission cap: reject with 429 queue_full once this many requests # are already queued (0 = disabled/unbounded, the default). Used by: open-sse/services/rateLimitManager.ts # RATE_LIMIT_MAX_QUEUE_DEPTH=0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34874b0c7fe..11e5b2f7d8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -976,7 +976,11 @@ jobs: # 10min was sized before #7114 added the lcov reporter (Codecov/Sonar need it); # merging 8 shard JSONs + text+json+lcov now takes ~10-12min — three consecutive # release-tip runs died at exactly 10m as job-timeout "cancelled" (2026-07-15/16). - timeout-minutes: 20 + # 30, not 20 (2026-08-29): the informational Codecov upload below hung for the rest of + # the budget on two consecutive main runs (33207760653, 33215115341); the job ended + # `cancelled` and dragged the whole run's conclusion to `cancelled` although every + # blocking job was green. The upload step now has its own ceiling; this is headroom. + timeout-minutes: 30 needs: test-unit if: ${{ !cancelled() && needs.test-unit.result == 'success' && !contains(github.event.pull_request.labels.*.name, 'hotfix') }} env: @@ -1055,6 +1059,10 @@ jobs: # (if-no-files-found: warn) — Sonar consumes the same file. - name: Upload coverage to Codecov (informational) if: always() + # Informational means informational: its own ceiling and continue-on-error, so a + # stalled upload can neither eat the job's budget nor turn a green job cancelled. + timeout-minutes: 5 + continue-on-error: true uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: coverage/lcov.info diff --git a/.github/workflows/electron-release.yml b/.github/workflows/electron-release.yml index e899a664eaa..913071d3118 100644 --- a/.github/workflows/electron-release.yml +++ b/.github/workflows/electron-release.yml @@ -10,6 +10,11 @@ on: description: "Release version (e.g., v1.6.8)" required: true type: string + publish_npm: + description: "Also run the npm publish leg (turn off when re-attaching desktop assets to a release whose npm package already shipped)" + required: false + default: true + type: boolean # Least-privilege default: read-only at the top level; each job grants the writes it # needs (build/release upload assets, publish-npm forwards npm provenance / packages @@ -76,6 +81,9 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false + # workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a + # tag push this resolves to the same commit. + ref: ${{ needs.validate.outputs.version }} - name: Setup Node uses: actions/setup-node@v7 with: @@ -161,6 +169,9 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false + # workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a + # tag push this resolves to the same commit. + ref: ${{ needs.validate.outputs.version }} - name: Setup Node uses: actions/setup-node@v7 with: @@ -347,6 +358,8 @@ jobs: with: persist-credentials: false fetch-depth: 0 + # Source archives + SBOM come from the tag being released, not the dispatching branch. + ref: ${{ needs.validate.outputs.version }} # `merge-multiple` is deliberately OFF. It resolves same-name collisions by ARRIVAL # ORDER, and the two macOS jobs each emit their own `latest-mac.yml` listing only their @@ -462,11 +475,20 @@ jobs: publish-npm: name: Publish to npm needs: [validate, release] + # A re-dispatch that only re-attaches desktop assets must not publish the npm package again. + if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish_npm }} permissions: # Must be `write`, not `read`: this job calls the reusable npm-publish.yml whose # `publish` job needs `contents: write` (gh release upload — attach the SBOM, #3874). # A reusable workflow's job cannot request more permission than the caller grants, # so a `read` here makes GitHub reject the run at startup (startup_failure). + # + # `actions: read` for the same reason: the called `publish` job downloads the next-build + # artefact and requests it. v3.8.50 (run 33005490476) died at startup with "The nested + # job 'publish' is requesting 'actions: read', but is only allowed 'actions: none'" — and + # because `release` lives in this same workflow, the tag shipped with ZERO assets. Keep + # this block a superset of every job's permissions in npm-publish.yml. + actions: read contents: write id-token: write # npm provenance (forwarded to the reusable workflow) packages: write # publish to npm.pkg.github.com diff --git a/changelog.d/fixes/12025-decouple-execution-expiration-from-queue-wait.md b/changelog.d/fixes/12025-decouple-execution-expiration-from-queue-wait.md new file mode 100644 index 00000000000..120e43d7612 --- /dev/null +++ b/changelog.d/fixes/12025-decouple-execution-expiration-from-queue-wait.md @@ -0,0 +1 @@ +- **fix(resilience):** decouple the limiter-managed execution backstop from the queue-wait budget — new `requestQueue.executionMaxWaitMs` (env `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`, default 600000 = 10 min) now feeds Bottleneck's post-dispatch `expiration`, while `requestQueue.maxWaitMs` keeps its documented queue-wait semantics. Previously the queue-wait budget doubled as the execution expiration, so legitimate long-running calls on non-incremental gateways (whole generation buffered before the first upstream byte, e.g. Console Go / Command Code tiers serving GLM models) were killed mid-flight at the queue budget with a false 504 `RATE_LIMIT_EXECUTION_TIMEOUT` — the local limiter undercut the provider-aware upstream fetch-start timeouts. The surfaced 504 message now names `requestQueue.executionMaxWaitMs`; the error keeps the #4165 guarantees (disclaims an upstream timeout, preserves the Bottleneck error as `cause`, branded code + trusted provenance, classified request-scoped so combo falls back). A real queue-wait bound (the `Promise.race` around `limiter.schedule()` sketched in #9533) remains future work. (#12025) diff --git a/changelog.d/fixes/12026-preserve-error-in-oversized-call-log-artifacts.md b/changelog.d/fixes/12026-preserve-error-in-oversized-call-log-artifacts.md new file mode 100644 index 00000000000..18426a96860 --- /dev/null +++ b/changelog.d/fixes/12026-preserve-error-in-oversized-call-log-artifacts.md @@ -0,0 +1 @@ +- **fix(diagnostics):** preserve the error field (truncated to 4KB with a `[truncated: …]` suffix) in every call-log artifact size-limit fallback stage. Previously the minimal fallback replaced the error with `[omitted: call log artifact size limit exceeded]`, so an oversized artifact row showed nothing about WHY the request failed — e.g. 91 of 847 opencode-go 504 rows on one production instance were undiagnosable from the dashboard. Oversized request/response bodies are still omitted exactly as before; the error cap is independent of the payload sizes that tripped the fallback. (#12026) diff --git a/changelog.d/fixes/v3850-electron-release-assets.md b/changelog.d/fixes/v3850-electron-release-assets.md new file mode 100644 index 00000000000..255efd829cc --- /dev/null +++ b/changelog.d/fixes/v3850-electron-release-assets.md @@ -0,0 +1 @@ +- Electron release workflow: the `publish-npm` job now grants `actions: read` to the reusable `npm-publish.yml` it calls (its `publish` job requests it), which is what made GitHub refuse the whole v3.8.50 run at startup and ship the release with zero desktop assets; a `workflow_dispatch` now builds the requested tag instead of the dispatching branch and can skip the npm leg (`publish_npm=false`) when only re-attaching assets diff --git a/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md b/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md new file mode 100644 index 00000000000..7331426eb66 --- /dev/null +++ b/changelog.d/maintenance/ci-coverage-codecov-step-timeout.md @@ -0,0 +1 @@ +- `Coverage` job on `ci.yml`: the informational Codecov upload gets its own 5-minute ceiling and `continue-on-error`, and the job budget grows from 20 to 30 minutes (the 8-shard c8 merge alone takes ~10) — a stalled upload no longer ends the job `cancelled` and drags a fully green `main` run's conclusion down with it diff --git a/open-sse/services/rateLimitManager.ts b/open-sse/services/rateLimitManager.ts index bd793ae1cae..281a0c4f620 100644 --- a/open-sse/services/rateLimitManager.ts +++ b/open-sse/services/rateLimitManager.ts @@ -176,6 +176,16 @@ export function resolveRequestQueueMaxWaitMs( return resolveOverride(override, legacyDefault); } +/** + * Limiter-managed execution backstop (Bottleneck `expiration`). Starts only + * after a job leaves QUEUED; bounds execution, never queue wait. Kept strictly + * separate from the queue-wait budget (`maxWaitMs`) so the backstop cannot + * undercut upstream fetch-start timeouts on non-incremental gateways. + */ +export function resolveExecutionMaxWaitMs(): number { + return currentRequestQueueSettings.executionMaxWaitMs; +} + function buildLimiterDefaults() { // 0 or missing values mean "infinite" / no rate limit applies. This treats // the global request-queue settings the same way per-connection overrides @@ -562,10 +572,13 @@ export async function withRateLimit(provider, connectionId, model, fn, signal = await awaitProviderDefaultSlot(provider, connectionId, signal, maxWaitMs); const limiter = getLimiter(provider, connectionId, model); - // Bottleneck's `expiration` starts only after a job leaves QUEUED. The - // legacy maxWaitMs setting therefore bounds limiter-managed execution; it - // is not a queue-wait deadline. - const executionExpirationMs = maxWaitMs; + // Bottleneck's `expiration` starts only after a job leaves QUEUED, so it + // bounds limiter-managed execution — not queue wait. It is therefore fed by + // the dedicated execution backstop (`requestQueue.executionMaxWaitMs`), + // never by the queue-wait budget: non-incremental gateways legitimately run + // for minutes before first bytes, and an expiration at the queue budget + // killed them mid-flight (false 504s on opencode-go/glm-5.3-flash). + const executionExpirationMs = resolveExecutionMaxWaitMs(); const scheduleOpts = executionExpirationMs && executionExpirationMs > 0 ? { expiration: executionExpirationMs } : {}; @@ -641,7 +654,7 @@ export async function withRateLimit(provider, connectionId, model, fn, signal = throw markLocalRateLimitError( new Error( `Request exceeded OmniRoute's local rate-limit execution expiration ` + - `(legacy resilienceSettings.requestQueue.maxWaitMs=${executionExpirationMs}ms) for ` + + `(resilienceSettings.requestQueue.executionMaxWaitMs=${executionExpirationMs}ms) for ` + `${model ? `${provider}/${model}` : provider}. Bottleneck applies this deadline only ` + `after dispatch; it does not bound queue wait and is not an upstream-generated timeout.`, { cause: err } diff --git a/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx b/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx index f2c8c7a114c..a2882d7cc05 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx @@ -14,6 +14,7 @@ type RequestQueueSettings = { minTimeBetweenRequestsMs: number; concurrentRequests: number; maxWaitMs: number; + executionMaxWaitMs: number; }; type ConnectionCooldownProfileSettings = { @@ -254,6 +255,13 @@ function RequestQueueCard({ suffix="ms" onChange={(maxWaitMs) => setDraft((prev) => ({ ...prev, maxWaitMs }))} /> + setDraft((prev) => ({ ...prev, executionMaxWaitMs }))} + /> ) : ( <> @@ -289,6 +297,12 @@ function RequestQueueCard({ {formatMs(value.maxWaitMs)} +
+
{t("resilienceMaxExecutionWait")}
+
+ {formatMs(value.executionMaxWaitMs)} +
+
)} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 793e051ce07..29710962426 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "تتحكم هذه الطبقة في الصف والسرعة فقط. لا يقوم بتخزين فترات التهدئة أو قواطع الدائرة المفتوحة.", "resilienceAutoEnableApiKeyProvidersDesc": "لتمكين حماية قائمة الانتظار بشكل افتراضي لاتصالات مفتاح API النشطة.", "resilienceMaxQueueWait": "الحد الأقصى لوقت الانتظار في قائمة الانتظار", + "resilienceMaxExecutionWait": "مهلة التنفيذ (حد التنفيذ الاحتياطي لتحديد المعدل)", "resilienceConnectionCooldownScope": "اتصال فردي", "resilienceConnectionCooldownTrigger": "عندما يُرجع الاتصال فشلًا عابرًا في المنبع", "resilienceConnectionCooldownEffect": "يتخطى هذا الاتصال مؤقتًا ويزيد من التراجع في حالة الفشل المتكرر", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index c8069c859b2..eb5bf5df769 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "This layer only controls queueing and pacing. It does not store cooldowns or open circuit breakers.", "resilienceAutoEnableApiKeyProvidersDesc": "Enables queue protection by default for active API key connections.", "resilienceMaxQueueWait": "Maximum queue wait time", + "resilienceMaxExecutionWait": "İcra vaxt limiti (sürət limiti ehtiyat həddi)", "resilienceConnectionCooldownScope": "Individual connection", "resilienceConnectionCooldownTrigger": "When a connection returns a transient upstream failure", "resilienceConnectionCooldownEffect": "Temporarily skips that connection and increases backoff for repeated failures", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index 0190d6f22e0..0b945a4ebf7 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Този слой контролира само опашката и темпото. Той не съхранява охлаждания или отворени прекъсвачи.", "resilienceAutoEnableApiKeyProvidersDesc": "Активира защита на опашката по подразбиране за активни API ключ връзки.", "resilienceMaxQueueWait": "Максимално време за изчакване на опашка", + "resilienceMaxExecutionWait": "Таймаут на изпълнението (резервен лимит на скоростта)", "resilienceConnectionCooldownScope": "Индивидуална връзка", "resilienceConnectionCooldownTrigger": "Когато връзката върне преходна грешка нагоре по веригата", "resilienceConnectionCooldownEffect": "Временно пропуска тази връзка и увеличава забавянето при повтарящи се повреди", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 475e2b9b8ef..054b103493f 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "এই স্তরটি শুধুমাত্র সারিবদ্ধ এবং পেসিং নিয়ন্ত্রণ করে। এটি কুলডাউন বা খোলা সার্কিট ব্রেকার সংরক্ষণ করে না।", "resilienceAutoEnableApiKeyProvidersDesc": "সক্রিয় API কী সংযোগের জন্য ডিফল্টরূপে সারি সুরক্ষা সক্ষম করে৷", "resilienceMaxQueueWait": "সর্বোচ্চ সারি অপেক্ষার সময়", + "resilienceMaxExecutionWait": "এক্সিকিউশন টাইমআউট (রেট-লিমিট ব্যাকস্টপ)", "resilienceConnectionCooldownScope": "স্বতন্ত্র সংযোগ", "resilienceConnectionCooldownTrigger": "যখন একটি সংযোগ একটি ক্ষণস্থায়ী আপস্ট্রিম ব্যর্থতা প্রদান করে", "resilienceConnectionCooldownEffect": "সাময়িকভাবে সেই সংযোগটি এড়িয়ে যায় এবং বারবার ব্যর্থতার জন্য ব্যাকঅফ বাড়ায়", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index f7d7208c315..12ea3d9ea95 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Tato vrstva řídí pouze řazení a rychlost zobrazování. Neukládá cooldowny ani přerušené jističe.", "resilienceAutoEnableApiKeyProvidersDesc": "Ve výchozím nastavení povoluje ochranu fronty pro aktivní připojení klíče API.", "resilienceMaxQueueWait": "Maximální doba čekání ve frontě", + "resilienceMaxExecutionWait": "Časový limit provádění (pojistný limit rychlosti)", "resilienceConnectionCooldownScope": "Individuální připojení", "resilienceConnectionCooldownTrigger": "Když připojení vrátí přechodné selhání proti proudu", "resilienceConnectionCooldownEffect": "Dočasně toto připojení vynechá a zvýší backoff pro opakované selhání", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 0637ac422fc..5985be8040c 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Dette lag styrer kun kø og pacing. Den opbevarer ikke nedkøling eller åbne afbrydere.", "resilienceAutoEnableApiKeyProvidersDesc": "Aktiverer købeskyttelse som standard for aktive API-nøgleforbindelser.", "resilienceMaxQueueWait": "Maksimal ventetid i kø", + "resilienceMaxExecutionWait": "Eksekveringstimeout (rate-limit-sikkerhed)", "resilienceConnectionCooldownScope": "Individuel tilslutning", "resilienceConnectionCooldownTrigger": "Når en forbindelse returnerer en forbigående opstrømsfejl", "resilienceConnectionCooldownEffect": "Springer midlertidigt den forbindelse over og øger backoff for gentagne fejl", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index af5ea5f277e..551cfb07037 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Diese Ebene steuert nur Warteschlange und Taktung. Sie speichert keine Cooldowns und öffnet keine Circuit Breaker.", "resilienceAutoEnableApiKeyProvidersDesc": "Aktiviert den Queue-Schutz standardmäßig für aktive API-Key-Verbindungen.", "resilienceMaxQueueWait": "Maximale Wartezeit in der Queue", + "resilienceMaxExecutionWait": "Ausführungs-Timeout (Rate-Limit-Rückfallebene)", "resilienceConnectionCooldownScope": "Einzelne Verbindung", "resilienceConnectionCooldownTrigger": "Wenn eine Verbindung einen vorübergehenden Upstream-Fehler zurückgibt", "resilienceConnectionCooldownEffect": "Überspringt diese Verbindung vorübergehend und erhöht den Backoff bei wiederholten Fehlern", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index f3cab6d48cb..1b9506d1797 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -8016,6 +8016,7 @@ "resilienceRequestQueueDesc": "This layer only controls queueing and pacing. It does not store cooldowns or open circuit breakers.", "resilienceAutoEnableApiKeyProvidersDesc": "Enables queue protection by default for active API key connections.", "resilienceMaxQueueWait": "Maximum queue wait time", + "resilienceMaxExecutionWait": "Execution timeout (rate-limit backstop)", "resilienceConnectionCooldownScope": "Individual connection", "resilienceConnectionCooldownTrigger": "When a connection returns a transient upstream failure", "resilienceConnectionCooldownEffect": "Temporarily skips that connection and increases backoff for repeated failures", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 19bbc5cc953..b59b33aeb04 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Esta capa solo controla las colas y el ritmo. No almacena tiempos de reutilización ni disyuntores abiertos.", "resilienceAutoEnableApiKeyProvidersDesc": "Habilita la protección de colas de forma predeterminada para conexiones de clave API activas.", "resilienceMaxQueueWait": "Tiempo máximo de espera en cola", + "resilienceMaxExecutionWait": "Tiempo de espera de ejecución (límite de respaldo)", "resilienceConnectionCooldownScope": "Conexión individual", "resilienceConnectionCooldownTrigger": "Cuando una conexión devuelve un error ascendente transitorio", "resilienceConnectionCooldownEffect": "Omite temporalmente esa conexión y aumenta la interrupción en caso de fallas repetidas", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index cade5d953cd..9746e6d73c1 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "این لایه فقط صف و سرعت را کنترل می کند. خنک کننده ها یا کلیدهای مدار باز را ذخیره نمی کند.", "resilienceAutoEnableApiKeyProvidersDesc": "حفاظت از صف را به طور پیش فرض برای اتصالات کلید API فعال فعال می کند.", "resilienceMaxQueueWait": "حداکثر زمان انتظار صف", + "resilienceMaxExecutionWait": "زمان انتظار اجرا (حد پشتیبان نرخ)", "resilienceConnectionCooldownScope": "ارتباط فردی", "resilienceConnectionCooldownTrigger": "هنگامی که یک اتصال یک شکست گذرا در بالادست را برمی گرداند", "resilienceConnectionCooldownEffect": "به طور موقت از آن اتصال پرش می شود و برای خرابی های مکرر، عقب نشینی را افزایش می دهد", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 7f7df831117..a5944ffd91a 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Tämä taso ohjaa vain jonotusta ja tahdistusta. Se ei tallenna jäähtymiä tai avoimia katkaisijoita.", "resilienceAutoEnableApiKeyProvidersDesc": "Ottaa oletuksena käyttöön jonosuojauksen aktiivisille API-avainyhteyksille.", "resilienceMaxQueueWait": "Suurin jonon odotusaika", + "resilienceMaxExecutionWait": "Suorituksen aikakatkaisu (nopeusrajan varakerro)", "resilienceConnectionCooldownScope": "Yksilöllinen yhteys", "resilienceConnectionCooldownTrigger": "Kun yhteys palauttaa ohimenevän ylävirran häiriön", "resilienceConnectionCooldownEffect": "Ohittaa väliaikaisesti kyseisen yhteyden ja lisää takaisinkytkentää toistuvien vikojen varalta", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 10aebbd560d..c417402863e 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Cette couche contrôle uniquement la file d’attente et le rythme. Il ne stocke pas les temps de recharge ni les disjoncteurs ouverts.", "resilienceAutoEnableApiKeyProvidersDesc": "Active la protection de la file d'attente par défaut pour les connexions de clé API actives.", "resilienceMaxQueueWait": "Temps d'attente maximum dans la file d'attente", + "resilienceMaxExecutionWait": "Délai d'exécution (garde-fou de limitation de débit)", "resilienceConnectionCooldownScope": "Connexion individuelle", "resilienceConnectionCooldownTrigger": "Lorsqu'une connexion renvoie un échec transitoire en amont", "resilienceConnectionCooldownEffect": "Ignore temporairement cette connexion et augmente l'intervalle en cas d'échecs répétés", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 6aa2e0490b2..313930c59cd 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "આ સ્તર માત્ર કતાર અને પેસિંગને નિયંત્રિત કરે છે. તે કૂલડાઉન અથવા ઓપન સર્કિટ બ્રેકર્સ સ્ટોર કરતું નથી.", "resilienceAutoEnableApiKeyProvidersDesc": "સક્રિય API કી કનેક્શન્સ માટે ડિફૉલ્ટ રૂપે કતાર સુરક્ષાને સક્ષમ કરે છે.", "resilienceMaxQueueWait": "મહત્તમ કતાર પ્રતીક્ષા સમય", + "resilienceMaxExecutionWait": "એક્ઝિક્યુશન ટાઇમઆઉટ (રેટ-લિમિટ બેકસ્ટોપ)", "resilienceConnectionCooldownScope": "વ્યક્તિગત જોડાણ", "resilienceConnectionCooldownTrigger": "જ્યારે કનેક્શન ક્ષણિક અપસ્ટ્રીમ નિષ્ફળતા આપે છે", "resilienceConnectionCooldownEffect": "અસ્થાયી રૂપે તે જોડાણને છોડી દે છે અને પુનરાવર્તિત નિષ્ફળતાઓ માટે બેકઓફ વધે છે", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index f2c2a841c42..f131381bdf3 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "שכבה זו שולטת רק בתור ובקצב. הוא אינו מאחסן התקררות או מפסקים פתוחים.", "resilienceAutoEnableApiKeyProvidersDesc": "מאפשר הגנת תור כברירת מחדל עבור חיבורי מפתח API פעילים.", "resilienceMaxQueueWait": "זמן המתנה מקסימלי בתור", + "resilienceMaxExecutionWait": "פסק זמן לביצוע (מגבלת גיבוי של הגבלת קצב)", "resilienceConnectionCooldownScope": "חיבור אישי", "resilienceConnectionCooldownTrigger": "כאשר חיבור מחזיר כשל חולף במעלה הזרם", "resilienceConnectionCooldownEffect": "מדלג באופן זמני על החיבור הזה ומגביר את החזרה לכשלים חוזרים", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 937217f33de..aed380a659c 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "यह परत केवल कतार और गति को नियंत्रित करती है। यह कूलडाउन या ओपन सर्किट ब्रेकर को स्टोर नहीं करता है।", "resilienceAutoEnableApiKeyProvidersDesc": "सक्रिय एपीआई कुंजी कनेक्शन के लिए डिफ़ॉल्ट रूप से कतार सुरक्षा सक्षम करता है।", "resilienceMaxQueueWait": "अधिकतम कतार प्रतीक्षा समय", + "resilienceMaxExecutionWait": "निष्पादन टाइमआउट (रेट-लिमिट बैकस्टॉप)", "resilienceConnectionCooldownScope": "व्यक्तिगत संबंध", "resilienceConnectionCooldownTrigger": "जब कोई कनेक्शन क्षणिक अपस्ट्रीम विफलता लौटाता है", "resilienceConnectionCooldownEffect": "अस्थायी रूप से उस कनेक्शन को छोड़ देता है और बार-बार विफलताओं के लिए बैकऑफ़ बढ़ाता है", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 1b4bbf6256d..d11ac74d7ac 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Ez a réteg csak a sorban állást és az ütemezést szabályozza. Nem tárolja a lehűléseket vagy a megszakítókat.", "resilienceAutoEnableApiKeyProvidersDesc": "Alapértelmezés szerint engedélyezi a sorvédelmet az aktív API-kulcs kapcsolatokhoz.", "resilienceMaxQueueWait": "Maximális várakozási idő a sorban", + "resilienceMaxExecutionWait": "Végrehajtási időkorlát (sebességkorlát tartalék)", "resilienceConnectionCooldownScope": "Egyéni kapcsolat", "resilienceConnectionCooldownTrigger": "Amikor egy kapcsolat tranziens upstream meghibásodást ad vissza", "resilienceConnectionCooldownEffect": "Ideiglenesen kihagyja ezt a kapcsolatot, és ismétlődő hibák esetén növeli a visszalépést", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index f40f223d25c..c76464597c5 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Lapisan ini hanya mengontrol antrian dan mondar-mandir. Itu tidak menyimpan cooldown atau pemutus sirkuit terbuka.", "resilienceAutoEnableApiKeyProvidersDesc": "Mengaktifkan perlindungan antrean secara default untuk koneksi kunci API aktif.", "resilienceMaxQueueWait": "Waktu tunggu antrian maksimum", + "resilienceMaxExecutionWait": "Batas waktu eksekusi (batas pengaman rate-limit)", "resilienceConnectionCooldownScope": "Koneksi individu", "resilienceConnectionCooldownTrigger": "Ketika koneksi mengembalikan kegagalan hulu sementara", "resilienceConnectionCooldownEffect": "Melewati koneksi itu untuk sementara dan meningkatkan backoff jika terjadi kegagalan berulang", diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index c1be37cea58..e403e08c8ce 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Lapisan ini hanya mengontrol antrian dan mondar-mandir. Itu tidak menyimpan cooldown atau pemutus sirkuit terbuka.", "resilienceAutoEnableApiKeyProvidersDesc": "Mengaktifkan perlindungan antrean secara default untuk koneksi kunci API aktif.", "resilienceMaxQueueWait": "Waktu tunggu antrian maksimum", + "resilienceMaxExecutionWait": "Batas waktu eksekusi (batas pengaman rate-limit)", "resilienceConnectionCooldownScope": "Koneksi individu", "resilienceConnectionCooldownTrigger": "Ketika koneksi mengembalikan kegagalan hulu sementara", "resilienceConnectionCooldownEffect": "Melewati koneksi itu untuk sementara dan meningkatkan backoff jika terjadi kegagalan berulang", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index fd17c1684af..e7a10d58594 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Questo livello controlla solo la coda e il ritmo. Non memorizza tempi di raffreddamento o interruttori automatici aperti.", "resilienceAutoEnableApiKeyProvidersDesc": "Abilita la protezione della coda per impostazione predefinita per le connessioni con chiave API attive.", "resilienceMaxQueueWait": "Tempo massimo di attesa in coda", + "resilienceMaxExecutionWait": "Timeout di esecuzione (limite di sicurezza di rate-limit)", "resilienceConnectionCooldownScope": "Connessione individuale", "resilienceConnectionCooldownTrigger": "Quando una connessione restituisce un errore upstream temporaneo", "resilienceConnectionCooldownEffect": "Ignora temporaneamente la connessione e aumenta il backoff in caso di errori ripetuti", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 3fccad57839..0331aa90aa0 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "この層はキューイングとペーシングのみを制御します。クールダウンやオープンサーキットブレーカーは保存されません。", "resilienceAutoEnableApiKeyProvidersDesc": "アクティブな API キー接続に対してデフォルトでキュー保護を有効にします。", "resilienceMaxQueueWait": "キューの最大待機時間", + "resilienceMaxExecutionWait": "実行タイムアウト(レート制限のバックストップ)", "resilienceConnectionCooldownScope": "個別接続", "resilienceConnectionCooldownTrigger": "接続が一時的なアップストリーム障害を返した場合", "resilienceConnectionCooldownEffect": "一時的にその接続をスキップし、失敗が繰り返される場合はバックオフを増加します。", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 546dedcbc4e..4f30e1a5c4f 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "이 레이어는 대기열 처리와 간격 조절만 제어합니다. 쿨다운을 저장하거나 회로 차단기를 열지는 않습니다.", "resilienceAutoEnableApiKeyProvidersDesc": "활성 API 키 연결에 대해 기본적으로 대기열 보호를 활성화합니다.", "resilienceMaxQueueWait": "최대 대기열 대기 시간", + "resilienceMaxExecutionWait": "실행 시간 초과 (레이트 리밋 백스톱)", "resilienceConnectionCooldownScope": "개별 연결", "resilienceConnectionCooldownTrigger": "연결이 일시적인 업스트림 오류를 반환하는 경우", "resilienceConnectionCooldownEffect": "해당 연결을 일시적으로 건너뛰고 반복되는 실패에 대한 백오프를 높입니다.", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 93f75dc03fc..c72c13b7aa2 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "हा स्तर फक्त रांग आणि पेसिंग नियंत्रित करतो. हे कूलडाऊन किंवा ओपन सर्किट ब्रेकर संचयित करत नाही.", "resilienceAutoEnableApiKeyProvidersDesc": "सक्रिय API की कनेक्शनसाठी डीफॉल्टनुसार रांग संरक्षण सक्षम करते.", "resilienceMaxQueueWait": "कमाल रांगेत प्रतीक्षा वेळ", + "resilienceMaxExecutionWait": "अंमलबजावणी कालमर्यादा (दर-मर्यादा बॅकस्टॉप)", "resilienceConnectionCooldownScope": "वैयक्तिक कनेक्शन", "resilienceConnectionCooldownTrigger": "जेव्हा कनेक्शन एक क्षणिक अपस्ट्रीम अपयश परत करते", "resilienceConnectionCooldownEffect": "ते कनेक्शन तात्पुरते वगळते आणि वारंवार अपयशी झाल्यास बॅकऑफ वाढते", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 0d5690f3cec..2db48f3ebd7 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Lapisan ini hanya mengawal baris gilir dan pacing. Ia tidak menyimpan cooldown atau pemutus litar terbuka.", "resilienceAutoEnableApiKeyProvidersDesc": "Mendayakan perlindungan baris gilir secara lalai untuk sambungan kunci API aktif.", "resilienceMaxQueueWait": "Masa menunggu giliran maksimum", + "resilienceMaxExecutionWait": "Tamat masa pelaksanaan (had keselamatan kadar)", "resilienceConnectionCooldownScope": "Sambungan individu", "resilienceConnectionCooldownTrigger": "Apabila sambungan mengembalikan kegagalan huluan sementara", "resilienceConnectionCooldownEffect": "Melangkau sambungan itu buat sementara waktu dan meningkatkan mundur untuk kegagalan berulang", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index a3836208c99..a37a3daedf9 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Deze laag regelt alleen wachtrijen en tempo. Er worden geen cooldowns of open stroomonderbrekers opgeslagen.", "resilienceAutoEnableApiKeyProvidersDesc": "Schakelt standaard wachtrijbeveiliging in voor actieve API-sleutelverbindingen.", "resilienceMaxQueueWait": "Maximale wachtrijwachttijd", + "resilienceMaxExecutionWait": "Uitvoeringstime-out (rate-limit terugval)", "resilienceConnectionCooldownScope": "Individuele verbinding", "resilienceConnectionCooldownTrigger": "Wanneer een verbinding een tijdelijke stroomopwaartse fout retourneert", "resilienceConnectionCooldownEffect": "Sla die verbinding tijdelijk over en vergroot de back-off bij herhaalde fouten", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 361aaa04b3e..e2f57538abe 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Dette laget kontrollerer kun kø og pacing. Den lagrer ikke nedkjøling eller åpne kretsbrytere.", "resilienceAutoEnableApiKeyProvidersDesc": "Aktiverer købeskyttelse som standard for aktive API-nøkkeltilkoblinger.", "resilienceMaxQueueWait": "Maksimal ventetid i kø", + "resilienceMaxExecutionWait": "Kjøringstidsavbrudd (sikkerhetsgrense for rate-limit)", "resilienceConnectionCooldownScope": "Individuell tilknytning", "resilienceConnectionCooldownTrigger": "Når en tilkobling returnerer en forbigående oppstrømsfeil", "resilienceConnectionCooldownEffect": "Hopper midlertidig over den tilkoblingen og øker backoff for gjentatte feil", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 8e669376424..b24912889d0 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Kinokontrol lang ng layer na ito ang queuing at pacing. Hindi ito nag-iimbak ng mga cooldown o bukas na mga circuit breaker.", "resilienceAutoEnableApiKeyProvidersDesc": "Pinapagana ang proteksyon ng queue bilang default para sa mga aktibong koneksyon sa API key.", "resilienceMaxQueueWait": "Pinakamataas na oras ng paghihintay sa pila", + "resilienceMaxExecutionWait": "Timeout ng pagpapatakbo (rate-limit na backstop)", "resilienceConnectionCooldownScope": "Indibidwal na koneksyon", "resilienceConnectionCooldownTrigger": "Kapag ang isang koneksyon ay nagbalik ng isang lumilipas na upstream failure", "resilienceConnectionCooldownEffect": "Pansamantalang nilalaktawan ang koneksyon na iyon at pinapataas ang backoff para sa mga paulit-ulit na pagkabigo", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 902df00e693..a808f95af46 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Ta warstwa kontroluje tylko kolejkowanie i tempo wywołań. Nie przechowuje okresów schładzania ani nie otwiera bezpieczników (circuit breakers).", "resilienceAutoEnableApiKeyProvidersDesc": "Domyślnie włącza ochronę kolejki dla aktywnych połączeń klucza API.", "resilienceMaxQueueWait": "Maksymalny czas oczekiwania w kolejce", + "resilienceMaxExecutionWait": "Limit czasu wykonania (zabezpieczenie limitu szybkości)", "resilienceConnectionCooldownScope": "Pojedyncze połączenie", "resilienceConnectionCooldownTrigger": "Gdy połączenie zwraca tymczasowy błąd upstreamu", "resilienceConnectionCooldownEffect": "Tymczasowo pomija to połączenie i zwiększa opóźnienie (backoff) przy powtarzających się błędach", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index e8e7689dd8a..4bf5223ea1f 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -8016,6 +8016,7 @@ "resilienceRequestQueueDesc": "Esta camada controla apenas o enfileiramento e o ritmo. Ele não armazena resfriamentos ou disjuntores abertos.", "resilienceAutoEnableApiKeyProvidersDesc": "Ativa a proteção de fila por padrão para conexões de chave de API ativas.", "resilienceMaxQueueWait": "Tempo máximo de espera na fila", + "resilienceMaxExecutionWait": "Tempo limite de execução (dispositivo de segurança de rate-limit)", "resilienceConnectionCooldownScope": "Conexão individual", "resilienceConnectionCooldownTrigger": "Quando uma conexão retorna uma falha transitória de upstream", "resilienceConnectionCooldownEffect": "Ignora temporariamente essa conexão e aumenta a espera para falhas repetidas", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 62796727cfc..e2a52ff2fea 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Esta camada controla apenas o enfileiramento e o ritmo. Ele não armazena resfriamentos ou disjuntores abertos.", "resilienceAutoEnableApiKeyProvidersDesc": "Ativa a proteção de fila por padrão para conexões de chave de API ativas.", "resilienceMaxQueueWait": "Tempo máximo de espera na fila", + "resilienceMaxExecutionWait": "Tempo limite de execução (mecanismo de segurança de rate-limit)", "resilienceConnectionCooldownScope": "Conexão individual", "resilienceConnectionCooldownTrigger": "Quando uma conexão retorna uma falha transitória de upstream", "resilienceConnectionCooldownEffect": "Ignora temporariamente essa conexão e aumenta a espera para falhas repetidas", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 3bff02e3d66..ce997023fcf 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Acest strat controlează doar coada și ritmul. Nu stochează răcirile sau întreruptoarele de circuit deschise.", "resilienceAutoEnableApiKeyProvidersDesc": "Activează protecția cozilor în mod implicit pentru conexiunile cheie API active.", "resilienceMaxQueueWait": "Timp maxim de așteptare la coadă", + "resilienceMaxExecutionWait": "Timeout de execuție (limită de siguranță pentru rata de cereri)", "resilienceConnectionCooldownScope": "Conexiune individuală", "resilienceConnectionCooldownTrigger": "Când o conexiune returnează o eroare tranzitorie în amonte", "resilienceConnectionCooldownEffect": "Omite temporar acea conexiune și crește backoff-ul pentru eșecuri repetate", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 8f8f35334b6..5c1f792e962 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Этот уровень управляет только очередью и скоростью. Он не сохраняет время восстановления или разомкнутые выключатели.", "resilienceAutoEnableApiKeyProvidersDesc": "По умолчанию включает защиту очереди для активных подключений ключей API.", "resilienceMaxQueueWait": "Максимальное время ожидания в очереди", + "resilienceMaxExecutionWait": "Тайм-аут выполнения (аварийный предел частоты запросов)", "resilienceConnectionCooldownScope": "Индивидуальное подключение", "resilienceConnectionCooldownTrigger": "Когда соединение возвращает временный сбой в восходящем направлении", "resilienceConnectionCooldownEffect": "Временно пропускает это соединение и увеличивает отсрочку в случае повторяющихся сбоев.", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index a20d4ecf300..7f79f15d50f 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Táto vrstva riadi iba radenie a tempo. Neuchováva cooldowny ani otvorené ističe.", "resilienceAutoEnableApiKeyProvidersDesc": "Predvolene povoľuje ochranu frontu pre aktívne pripojenia kľúča API.", "resilienceMaxQueueWait": "Maximálna doba čakania vo fronte", + "resilienceMaxExecutionWait": "Časový limit vykonávania (poistný limit rýchlosti)", "resilienceConnectionCooldownScope": "Individuálne pripojenie", "resilienceConnectionCooldownTrigger": "Keď spojenie vráti prechodné zlyhanie proti prúdu", "resilienceConnectionCooldownEffect": "Dočasne preskočí toto pripojenie a zvýši backoff pre opakované zlyhania", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index a2fadf490e6..c9ce9ea8d65 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Det här lagret styr bara köer och pacing. Den lagrar inte nedkylningar eller öppna strömbrytare.", "resilienceAutoEnableApiKeyProvidersDesc": "Aktiverar köskydd som standard för aktiva API-nyckelanslutningar.", "resilienceMaxQueueWait": "Maximal väntetid i kö", + "resilienceMaxExecutionWait": "Exekveringstimeout (säkerhetsgräns för rate-limit)", "resilienceConnectionCooldownScope": "Individuell anslutning", "resilienceConnectionCooldownTrigger": "När en anslutning returnerar ett övergående uppströmsfel", "resilienceConnectionCooldownEffect": "Hopar tillfälligt över den anslutningen och ökar backoff för upprepade fel", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 9ddd685ebc9..e685552d1e8 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Safu hii inadhibiti tu kupanga foleni na mwendo. Haihifadhi baridi au vivunja mzunguko wazi.", "resilienceAutoEnableApiKeyProvidersDesc": "Huwasha ulinzi wa foleni kwa chaguo-msingi kwa miunganisho ya vitufe vya API inayotumika.", "resilienceMaxQueueWait": "Muda wa juu zaidi wa kusubiri kwenye foleni", + "resilienceMaxExecutionWait": "Muda wa kukatika wa utekelezaji (kikomo cha akiba cha kiwango)", "resilienceConnectionCooldownScope": "Muunganisho wa mtu binafsi", "resilienceConnectionCooldownTrigger": "Muunganisho unaporudisha hitilafu ya muda ya juu ya mkondo", "resilienceConnectionCooldownEffect": "Huruka muunganisho huo kwa muda na huongeza urejesho kwa kushindwa mara kwa mara", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index fe03c5ad9f9..eecbfd99da6 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "இந்த அடுக்கு வரிசை மற்றும் வேகத்தை மட்டுமே கட்டுப்படுத்துகிறது. இது கூல்டவுன்கள் அல்லது ஓபன் சர்க்யூட் பிரேக்கர்களை சேமிக்காது.", "resilienceAutoEnableApiKeyProvidersDesc": "செயலில் உள்ள API விசை இணைப்புகளுக்கு இயல்பாக வரிசை பாதுகாப்பை இயக்குகிறது.", "resilienceMaxQueueWait": "அதிகபட்ச வரிசையில் காத்திருக்கும் நேரம்", + "resilienceMaxExecutionWait": "செயல்படுத்தல் நேர வரம்பு (வீத-வரம்பு பின்னிறுத்தல்)", "resilienceConnectionCooldownScope": "தனிப்பட்ட இணைப்பு", "resilienceConnectionCooldownTrigger": "ஒரு இணைப்பு தற்காலிகமான அப்ஸ்ட்ரீம் தோல்வியை வழங்கும் போது", "resilienceConnectionCooldownEffect": "அந்த இணைப்பைத் தற்காலிகமாகத் தவிர்த்துவிட்டு, மீண்டும் மீண்டும் தோல்வியடைவதால், பின்வாங்கலை அதிகரிக்கிறது", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 2353afbda30..7cd98725607 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "ఈ లేయర్ క్యూయింగ్ మరియు పేసింగ్‌ను మాత్రమే నియంత్రిస్తుంది. ఇది కూల్‌డౌన్‌లు లేదా ఓపెన్ సర్క్యూట్ బ్రేకర్‌లను నిల్వ చేయదు.", "resilienceAutoEnableApiKeyProvidersDesc": "సక్రియ API కీ కనెక్షన్‌ల కోసం డిఫాల్ట్‌గా క్యూ రక్షణను ప్రారంభిస్తుంది.", "resilienceMaxQueueWait": "గరిష్ట క్యూ నిరీక్షణ సమయం", + "resilienceMaxExecutionWait": "ఎగ్జిక్యూషన్ టైమ్‌అవుట్ (రేటు-పరిమితి బ్యాక్‌స్టాప్)", "resilienceConnectionCooldownScope": "వ్యక్తిగత కనెక్షన్", "resilienceConnectionCooldownTrigger": "కనెక్షన్ తాత్కాలిక అప్‌స్ట్రీమ్ వైఫల్యాన్ని అందించినప్పుడు", "resilienceConnectionCooldownEffect": "ఆ కనెక్షన్‌ని తాత్కాలికంగా దాటవేసి, పునరావృత వైఫల్యాల కోసం బ్యాక్‌ఆఫ్‌ని పెంచుతుంది", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 41f194ca504..66b9d992d85 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "เลเยอร์นี้ควบคุมเฉพาะการเข้าคิวและการเว้นจังหวะเท่านั้น ไม่เก็บคูลดาวน์หรือเบรกเกอร์วงจรเปิด", "resilienceAutoEnableApiKeyProvidersDesc": "เปิดใช้งานการป้องกันคิวตามค่าเริ่มต้นสำหรับการเชื่อมต่อคีย์ API ที่ใช้งานอยู่", "resilienceMaxQueueWait": "เวลารอคิวสูงสุด", + "resilienceMaxExecutionWait": "หมดเวลาการดำเนินการ (ขีดจำกัดสำรองของอัตราคำขอ)", "resilienceConnectionCooldownScope": "การเชื่อมต่อส่วนบุคคล", "resilienceConnectionCooldownTrigger": "เมื่อการเชื่อมต่อส่งคืนความล้มเหลวอัปสตรีมชั่วคราว", "resilienceConnectionCooldownEffect": "ข้ามการเชื่อมต่อนั้นชั่วคราว และเพิ่มแบ็คออฟหากเกิดความล้มเหลวซ้ำๆ", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index d518612a5f2..0e404aafe37 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Bu katman yalnızca kuyruklamayı ve ilerleme hızını kontrol eder. Soğutma sürelerini veya açık devre kesicileri saklamaz.", "resilienceAutoEnableApiKeyProvidersDesc": "Etkin API anahtarı bağlantıları için varsayılan olarak kuyruk korumasını etkinleştirir.", "resilienceMaxQueueWait": "Maksimum kuyruk bekleme süresi", + "resilienceMaxExecutionWait": "Yürütme zaman aşımı (hız sınırı emniyet supabı)", "resilienceConnectionCooldownScope": "Bireysel bağlantı", "resilienceConnectionCooldownTrigger": "Bir bağlantı geçici bir yukarı akış hatası döndürdüğünde", "resilienceConnectionCooldownEffect": "Bu bağlantıyı geçici olarak atlar ve tekrarlanan arızalarda geri çekilmeyi artırır", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index a8cb9f20484..9d0e0dab2d9 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "Цей рівень контролює лише чергування та темп. Він не зберігає перезарядки або розімкнуті вимикачі.", "resilienceAutoEnableApiKeyProvidersDesc": "Вмикає захист черги за замовчуванням для активних підключень ключа API.", "resilienceMaxQueueWait": "Максимальний час очікування в черзі", + "resilienceMaxExecutionWait": "Тайм-аут виконання (аварійний ліміт частоти запитів)", "resilienceConnectionCooldownScope": "Індивідуальне підключення", "resilienceConnectionCooldownTrigger": "Коли підключення повертає тимчасову помилку висхідного потоку", "resilienceConnectionCooldownEffect": "Тимчасово пропускає це з’єднання та збільшує час відстрочки у разі повторних збоїв", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index b19418e08fd..7595962a66a 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "یہ پرت صرف قطار اور پیسنگ کو کنٹرول کرتی ہے۔ یہ کولڈاؤنز یا اوپن سرکٹ بریکرز کو ذخیرہ نہیں کرتا ہے۔", "resilienceAutoEnableApiKeyProvidersDesc": "فعال API کلیدی کنکشنز کے لیے بطور ڈیفالٹ قطار کے تحفظ کو فعال کرتا ہے۔", "resilienceMaxQueueWait": "زیادہ سے زیادہ قطار انتظار کا وقت", + "resilienceMaxExecutionWait": "عمل کی وقت ختم (ریٹ-لیمیٹ بیک اسٹاپ)", "resilienceConnectionCooldownScope": "انفرادی تعلق", "resilienceConnectionCooldownTrigger": "جب کوئی کنکشن عارضی اپ اسٹریم کی ناکامی لوٹاتا ہے۔", "resilienceConnectionCooldownEffect": "عارضی طور پر اس کنکشن کو چھوڑ دیتا ہے اور بار بار ناکامیوں کے لیے بیک آف کو بڑھاتا ہے۔", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 20bf2d9c092..9239db9b786 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -8016,6 +8016,7 @@ "resilienceRequestQueueDesc": "Lớp này chỉ kiểm soát việc xếp hàng và nhịp độ. Nó không lưu trữ thời gian hồi hoặc mở bộ ngắt mạch.", "resilienceAutoEnableApiKeyProvidersDesc": "Bật tính năng bảo vệ hàng đợi theo mặc định cho các kết nối bằng khóa API đang hoạt động.", "resilienceMaxQueueWait": "Thời gian chờ xếp hàng tối đa", + "resilienceMaxExecutionWait": "Thời gian chờ thực thi (giới hạn dự phòng của tốc độ)", "resilienceConnectionCooldownScope": "Kết nối riêng lẻ", "resilienceConnectionCooldownTrigger": "Khi một kết nối trả về lỗi upstream tạm thời", "resilienceConnectionCooldownEffect": "Tạm thời bỏ qua kết nối đó và tăng thời gian chờ tăng dần đối với các lỗi lặp lại.", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index ac3f4e21239..684a866abb2 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "该层仅控制排队和节奏。它不存储冷却时间或打开断路器。", "resilienceAutoEnableApiKeyProvidersDesc": "默认情况下为活动 API 密钥连接启用队列保护。", "resilienceMaxQueueWait": "最大队列等待时间", + "resilienceMaxExecutionWait": "执行超时(速率限制兜底)", "resilienceConnectionCooldownScope": "单独连接", "resilienceConnectionCooldownTrigger": "当连接返回暂时性上游故障时", "resilienceConnectionCooldownEffect": "暂时跳过该连接并增加重复失败的退避时间", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index fd2696f23af..e3515a0b76c 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -8004,6 +8004,7 @@ "resilienceRequestQueueDesc": "該層僅控制排隊和節奏。它不儲存冷卻時間或開啟斷路器。", "resilienceAutoEnableApiKeyProvidersDesc": "預設情況下為活動 API 金鑰連線啟用佇列保護。", "resilienceMaxQueueWait": "最大佇列等待時間", + "resilienceMaxExecutionWait": "執行逾時(速率限制後備)", "resilienceConnectionCooldownScope": "單獨連線", "resilienceConnectionCooldownTrigger": "當連線返回暫時性上游故障時", "resilienceConnectionCooldownEffect": "暫時跳過該連線並增加重複失敗的退避時間", diff --git a/src/lib/resilience/settings.ts b/src/lib/resilience/settings.ts index 3923c0fb441..1d4de4e7c0a 100644 --- a/src/lib/resilience/settings.ts +++ b/src/lib/resilience/settings.ts @@ -45,6 +45,16 @@ export const DEFAULT_REQUEST_QUEUE_MAX_WAIT_MS = (() => { return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : 15000; })(); +// Limiter-managed execution backstop (Bottleneck `expiration`). Deliberately +// separate from the queue-wait budget: non-incremental gateways (Console Go / +// Command Code) buffer whole generations before first bytes, so legitimate +// executions run minutes. Default 10 min; the backstop only catches executors +// without their own upstream timeout. +export const DEFAULT_REQUEST_QUEUE_EXECUTION_MAX_WAIT_MS = (() => { + const parsed = Number(process.env.RATE_LIMIT_EXECUTION_MAX_WAIT_MS || "600000"); + return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : 600000; +})(); + // Issue #6593: opt-in admission cap on the local rate-limit queue depth. // Default 0 = disabled (unbounded queue, today's behavior unchanged). export const DEFAULT_REQUEST_QUEUE_MAX_DEPTH = (() => { @@ -59,6 +69,7 @@ export const DEFAULT_RESILIENCE_SETTINGS: ResilienceSettings = { minTimeBetweenRequestsMs: DEFAULT_API_LIMITS.minTimeBetweenRequests, concurrentRequests: DEFAULT_API_LIMITS.concurrentRequests, maxWaitMs: DEFAULT_REQUEST_QUEUE_MAX_WAIT_MS, + executionMaxWaitMs: DEFAULT_REQUEST_QUEUE_EXECUTION_MAX_WAIT_MS, maxQueueDepth: DEFAULT_REQUEST_QUEUE_MAX_DEPTH, }, connectionCooldown: { @@ -209,6 +220,7 @@ function buildLegacyFallback(settings: JsonRecord): ResilienceSettings { { min: 1, max: 10_000 } ), maxWaitMs: DEFAULT_RESILIENCE_SETTINGS.requestQueue.maxWaitMs, + executionMaxWaitMs: DEFAULT_RESILIENCE_SETTINGS.requestQueue.executionMaxWaitMs, maxQueueDepth: DEFAULT_RESILIENCE_SETTINGS.requestQueue.maxQueueDepth, }, connectionCooldown: { diff --git a/src/lib/resilience/settings/normalize.ts b/src/lib/resilience/settings/normalize.ts index 20cb87e380d..ac7be152b3a 100644 --- a/src/lib/resilience/settings/normalize.ts +++ b/src/lib/resilience/settings/normalize.ts @@ -128,6 +128,11 @@ export function normalizeRequestQueueSettings( min: 1, max: 24 * 60 * 60 * 1000, }); + const executionMaxWaitMs = toInteger( + record.executionMaxWaitMs, + fallback.executionMaxWaitMs, + { min: 1, max: 24 * 60 * 60 * 1000 } + ); const maxQueueDepth = toInteger(record.maxQueueDepth, fallback.maxQueueDepth, { min: 0, max: 100_000, @@ -142,6 +147,7 @@ export function normalizeRequestQueueSettings( minTimeBetweenRequestsMs, concurrentRequests, maxWaitMs, + executionMaxWaitMs, maxQueueDepth, }; } diff --git a/src/lib/resilience/settings/types.ts b/src/lib/resilience/settings/types.ts index 0bc16d48b2f..e6b4b0ceb78 100644 --- a/src/lib/resilience/settings/types.ts +++ b/src/lib/resilience/settings/types.ts @@ -17,10 +17,17 @@ export interface RequestQueueSettings { minTimeBetweenRequestsMs: number; concurrentRequests: number; /** - * Legacy persisted key used as Bottleneck's post-dispatch execution - * expiration. It does not bound time spent in Bottleneck's QUEUED state. + * Queue-wait budget: how long a request may wait for a rate-limit slot + * (gates + limiter queue) before being dropped. Does NOT bound execution. */ maxWaitMs: number; + /** + * Limiter-managed execution backstop (Bottleneck `expiration`, which starts + * only after a job leaves QUEUED). Kept separate from `maxWaitMs` because + * non-incremental gateways legitimately take minutes before first bytes; + * the backstop must never undercut the upstream fetch-start timeout. + */ + executionMaxWaitMs: number; /** * Issue #6593: opt-in admission cap on the local rate-limit queue. When the * queue already holds `maxQueueDepth` requests, a new request is diff --git a/src/lib/usage/callLogArtifacts.ts b/src/lib/usage/callLogArtifacts.ts index fecc482856e..57d339a3808 100644 --- a/src/lib/usage/callLogArtifacts.ts +++ b/src/lib/usage/callLogArtifacts.ts @@ -16,6 +16,10 @@ const SIZE_LIMIT_EXCEEDED_REASON = "call_log_artifact_size_limit_exceeded"; const OMITTED_FOR_SIZE_LIMIT = "[omitted: call log artifact size limit exceeded]"; const STREAM_CHUNKS_OMITTED_FOR_SIZE_LIMIT = "[stream chunks omitted: call log artifact size limit exceeded]"; +// Error strings are kept even in the size-limit fallback (truncated to this +// cap) so every log row stays diagnosable; see buildMinimalArtifactForSizeLimit. +const MAX_CALL_LOG_ARTIFACT_ERROR_BYTES = 4 * 1024; +const SIZE_LIMIT_EXCEEDED_SUFFIX = "…[truncated: call log artifact size limit exceeded]"; export type CallLogDetailState = "none" | "ready" | "missing" | "corrupt" | "legacy-inline"; @@ -133,7 +137,11 @@ function buildMinimalArtifactForSizeLimit(artifact: CallLogArtifact) { summary: artifact.summary, requestBody: OMITTED_FOR_SIZE_LIMIT, responseBody: OMITTED_FOR_SIZE_LIMIT, - error: artifact.error ? OMITTED_FOR_SIZE_LIMIT : null, + // Never drop the error: it is the only field that says WHY the request + // failed (e.g. "Fetch timeout after 110000ms on https://..."). Diagnosing + // provider outages from a log row that shows only an omission marker is + // impossible; the error string is tiny next to the request/response bodies. + error: artifact.error ? truncateErrorForSizeLimit(artifact.error) : null, pipeline: { error: { _omniroute_truncated: true, @@ -143,12 +151,30 @@ function buildMinimalArtifactForSizeLimit(artifact: CallLogArtifact) { }; } +function truncateErrorForSizeLimit(error: unknown): string { + const text = typeof error === "string" ? error : JSON.stringify(error) ?? String(error); + if (Buffer.byteLength(text) <= MAX_CALL_LOG_ARTIFACT_ERROR_BYTES) return text; + return `${text.slice(0, MAX_CALL_LOG_ARTIFACT_ERROR_BYTES)}${SIZE_LIMIT_EXCEEDED_SUFFIX}`; +} + function serializeFinalSizeLimitFallback(artifact: CallLogArtifact, maxBytes: number): string { const withSummary = JSON.stringify(buildMinimalArtifactForSizeLimit(artifact)); if (Buffer.byteLength(withSummary) <= maxBytes) { return withSummary; } + // The summary alone exceeded the cap (pathological). Keep the error so the + // row stays diagnosable, drop everything else including the summary body. + const errorOnly = JSON.stringify({ + schemaVersion: artifact.schemaVersion, + _omniroute_truncated: true, + reason: SIZE_LIMIT_EXCEEDED_REASON, + error: artifact.error ? truncateErrorForSizeLimit(artifact.error) : null, + }); + if (Buffer.byteLength(errorOnly) <= maxBytes) { + return errorOnly; + } + return JSON.stringify({ schemaVersion: artifact.schemaVersion, _omniroute_truncated: true, @@ -186,7 +212,7 @@ function serializeArtifactForStorage(artifact: CallLogArtifact): string { ...omitOversizedPipeline(artifact), requestBody: OMITTED_FOR_SIZE_LIMIT, responseBody: OMITTED_FOR_SIZE_LIMIT, - error: artifact.error ? OMITTED_FOR_SIZE_LIMIT : null, + error: artifact.error ? truncateErrorForSizeLimit(artifact.error) : null, }); if (Buffer.byteLength(minimal) <= maxBytes) { return minimal; diff --git a/src/shared/validation/schemas/settings.ts b/src/shared/validation/schemas/settings.ts index 06ae7ebef6b..13a4b2cd078 100644 --- a/src/shared/validation/schemas/settings.ts +++ b/src/shared/validation/schemas/settings.ts @@ -42,6 +42,7 @@ export const requestQueueSettingsSchema = z minTimeBetweenRequestsMs: z.number().int().min(0).optional(), concurrentRequests: z.number().int().min(1).optional(), maxWaitMs: z.number().int().min(1).optional(), + executionMaxWaitMs: z.number().int().min(1).optional(), maxQueueDepth: z.number().int().min(0).max(100_000).optional(), }) .strict(); diff --git a/tests/unit/call-log-cap.test.ts b/tests/unit/call-log-cap.test.ts index e1cbd7e9589..fe8e32fce08 100644 --- a/tests/unit/call-log-cap.test.ts +++ b/tests/unit/call-log-cap.test.ts @@ -677,9 +677,62 @@ test("saveCallLog falls back to a compact sentinel when the configured cap is ve schemaVersion: 5, _omniroute_truncated: true, reason: "call_log_artifact_size_limit_exceeded", + error: null, }); }); +test("saveCallLog preserves a truncated error in size-limit-fallback artifacts (opencode-go 504 diagnosability)", async () => { + // Regression: the minimal size-limit fallback used to replace the error + // with "[omitted: call log artifact size limit exceeded]", wiping the only + // field that explains WHY the request failed. The error must survive. + process.env.CALL_LOG_PIPELINE_MAX_SIZE_KB = "1"; + const hugePayload = "x".repeat(64 * 1024); + const upstreamError = + "[504]: Fetch timeout after 110000ms on https://opencode.ai/zen/go/v1/chat/completions"; + + await callLogs.saveCallLog({ + id: "tiny-cap-preserves-error", + timestamp: "2026-03-31T10:08:30.000Z", + method: "POST", + path: "/v1/chat/completions", + status: 504, + model: `openai/${"gpt".repeat(512)}`, + provider: "opencode-go", + requestBody: { payload: "request" }, + responseBody: { output: "response" }, + error: upstreamError, + pipelinePayloads: { + providerRequest: { body: hugePayload }, + providerResponse: { body: hugePayload }, + }, + }); + + const db = core.getDbInstance(); + const row = db + .prepare( + ` + SELECT artifact_relpath, artifact_size_bytes, detail_state + FROM call_logs WHERE id = ? + ` + ) + .get("tiny-cap-preserves-error"); + assert.equal((row as any).detail_state, "ready"); + + const artifactPath = path.join(TEST_DATA_DIR, "call_logs", (row as any).artifact_relpath); + const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8")); + assert.equal( + artifact.error, + upstreamError, + "the upstream error must be preserved verbatim in the fallback artifact" + ); + assert.equal(artifact._omniroute_truncated, true); + assert.equal( + artifact.requestBody, + undefined, + "oversized bodies must be dropped, but the error must survive" + ); +}); + test("CALL_LOG_PIPELINE_MAX_SIZE_KB does not cap artifacts without pipeline details", async () => { process.env.CALL_LOG_PIPELINE_MAX_SIZE_KB = "8"; const requestBody = { payload: "x".repeat(16 * 1024) }; diff --git a/tests/unit/rate-limit-execution-timeout-message-4165.test.ts b/tests/unit/rate-limit-execution-timeout-message-4165.test.ts index 3cae26ebbd0..1b0eed9b5a0 100644 --- a/tests/unit/rate-limit-execution-timeout-message-4165.test.ts +++ b/tests/unit/rate-limit-execution-timeout-message-4165.test.ts @@ -50,12 +50,14 @@ async function triggerExecutionExpiration() { concurrentRequests: 1, requestsPerMinute: 100000, minTimeBetweenRequestsMs: 0, - maxWaitMs: 40, + // The execution backstop (not the queue-wait budget) feeds Bottleneck's + // `expiration`, so shrink the backstop to force a real expiration here. + executionMaxWaitMs: 40, }); rateLimitManager.enableRateLimitProtection("conn-execution-timeout"); return rateLimitManager.withRateLimit("openai", "conn-execution-timeout", "gpt-4o", async () => { - await wait(400); // > maxWaitMs (40ms) → Bottleneck fails the job + await wait(400); // > executionMaxWaitMs (40ms) → Bottleneck fails the job return "should-not-reach"; }); } @@ -108,6 +110,36 @@ test("#4165 execution expiration is local and accurately named", async () => { ); }); +test("execution outliving the queue-wait budget completes (opencode-go 504 regression)", async () => { + // Regression: the queue-wait budget (maxWaitMs) used to be passed to + // Bottleneck as the execution `expiration`, so a legitimate execution that + // outlived it (e.g. glm-5.3-flash thinking for >45s before first bytes) was + // killed mid-flight with a false 504. The backstop must come from + // executionMaxWaitMs instead. + await rateLimitManager.applyRequestQueueSettings({ + ...resilienceSettings.DEFAULT_RESILIENCE_SETTINGS.requestQueue, + autoEnableApiKeyProviders: false, + concurrentRequests: 1, + requestsPerMinute: 100000, + minTimeBetweenRequestsMs: 0, + maxWaitMs: 40, // queue-wait budget: 40ms + executionMaxWaitMs: 5000, // execution backstop: 5s + }); + rateLimitManager.enableRateLimitProtection("conn-slow-exec"); + + const result = await rateLimitManager.withRateLimit( + "openai", + "conn-slow-exec", + "gpt-4o", + async () => { + await wait(300); // outlives maxWaitMs, well within the execution backstop + return "ok"; + } + ); + assert.equal(result, "ok", "execution must not be killed by the queue-wait budget"); +}); + + test("#4165 a job that completes within the execution expiration is unaffected", async () => { await rateLimitManager.applyRequestQueueSettings({ ...resilienceSettings.DEFAULT_RESILIENCE_SETTINGS.requestQueue, diff --git a/tests/unit/ratelimit-admission-control-6593.test.ts b/tests/unit/ratelimit-admission-control-6593.test.ts index 04f3dd9fe1b..55b9267a91d 100644 --- a/tests/unit/ratelimit-admission-control-6593.test.ts +++ b/tests/unit/ratelimit-admission-control-6593.test.ts @@ -183,6 +183,14 @@ test("#6593 DEFAULT_REQUEST_QUEUE_MAX_WAIT_MS is 15s absent RATE_LIMIT_MAX_WAIT_ assert.equal(resilienceSettings.DEFAULT_RESILIENCE_SETTINGS.requestQueue.maxWaitMs, 15000); }); +test("requestQueue.executionMaxWaitMs defaults to a 10-minute backstop, separate from maxWaitMs", () => { + assert.equal(resilienceSettings.DEFAULT_REQUEST_QUEUE_EXECUTION_MAX_WAIT_MS, 600000); + assert.equal( + resilienceSettings.DEFAULT_RESILIENCE_SETTINGS.requestQueue.executionMaxWaitMs, + 600000 + ); +}); + test("#6593 zai-web receives a provider-scoped 60s scheduling budget", () => { assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("openai", 15_000), 15_000); assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("zai-web", 15_000), 60_000);