From 24a47ce482f04a631573aaebaa03ea6f0b1ce360 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 23 Aug 2026 15:53:27 +0200 Subject: [PATCH 1/3] feat(providers): allow per-connection maxWaitMs rate-limit override --- .../11251-connection-max-wait-ms-override.md | 1 + open-sse/services/rateLimitManager.ts | 28 ++- .../components/modals/EditConnectionModal.tsx | 15 ++ src/i18n/messages/ar.json | 2 + src/i18n/messages/az.json | 2 + src/i18n/messages/bg.json | 2 + src/i18n/messages/bn.json | 2 + src/i18n/messages/cs.json | 2 + src/i18n/messages/da.json | 2 + src/i18n/messages/de.json | 2 + src/i18n/messages/en.json | 2 + src/i18n/messages/es.json | 2 + src/i18n/messages/fa.json | 2 + src/i18n/messages/fi.json | 2 + src/i18n/messages/fr.json | 2 + src/i18n/messages/gu.json | 2 + src/i18n/messages/he.json | 2 + src/i18n/messages/hi.json | 2 + src/i18n/messages/hu.json | 2 + src/i18n/messages/id.json | 2 + src/i18n/messages/in.json | 2 + src/i18n/messages/it.json | 2 + src/i18n/messages/ja.json | 2 + src/i18n/messages/ko.json | 2 + src/i18n/messages/mr.json | 2 + src/i18n/messages/ms.json | 2 + src/i18n/messages/nl.json | 2 + src/i18n/messages/no.json | 2 + src/i18n/messages/phi.json | 2 + src/i18n/messages/pl.json | 2 + src/i18n/messages/pt-BR.json | 2 + src/i18n/messages/pt.json | 2 + src/i18n/messages/ro.json | 2 + src/i18n/messages/ru.json | 2 + src/i18n/messages/sk.json | 2 + src/i18n/messages/sv.json | 2 + src/i18n/messages/sw.json | 2 + src/i18n/messages/ta.json | 2 + src/i18n/messages/te.json | 2 + src/i18n/messages/th.json | 2 + src/i18n/messages/tr.json | 2 + src/i18n/messages/uk-UA.json | 2 + src/i18n/messages/ur.json | 2 + src/i18n/messages/vi.json | 2 + src/i18n/messages/zh-CN.json | 2 + src/i18n/messages/zh-TW.json | 2 + src/shared/validation/schemas/provider.ts | 20 +-- ...ection-modal-max-wait-ms-override.test.tsx | 164 ++++++++++++++++++ ...ovider-rate-limit-overrides-schema.test.ts | 47 ++++- .../ratelimit-admission-control-6593.test.ts | 42 +++++ 50 files changed, 382 insertions(+), 21 deletions(-) create mode 100644 changelog.d/features/11251-connection-max-wait-ms-override.md create mode 100644 tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx diff --git a/changelog.d/features/11251-connection-max-wait-ms-override.md b/changelog.d/features/11251-connection-max-wait-ms-override.md new file mode 100644 index 000000000000..25ac42dd98cf --- /dev/null +++ b/changelog.d/features/11251-connection-max-wait-ms-override.md @@ -0,0 +1 @@ +- **feat(providers):** allow overriding the rate-limit queue wait timeout (`maxWaitMs`) per connection, alongside the existing `rpm`/`tpm`/`tpd`/`minTime`/`maxConcurrent` overrides — a single slow provider no longer has to lower the global wait budget for every other provider (#11251) diff --git a/open-sse/services/rateLimitManager.ts b/open-sse/services/rateLimitManager.ts index 18815b32a5e3..bd793ae1caed 100644 --- a/open-sse/services/rateLimitManager.ts +++ b/open-sse/services/rateLimitManager.ts @@ -140,28 +140,40 @@ function isAutoEnableActive(settings: RequestQueueSettings): boolean { const EFFECTIVELY_INFINITE = Number.MAX_SAFE_INTEGER; const EFFECTIVELY_INFINITE_CONCURRENCY = 1000; +// Shared override-resolution rule for every per-connection rate-limit field: +// a positive override wins, 0 or missing falls through to `fallback`. +function resolveOverride(override: number | undefined | null, fallback: number): number { + return typeof override === "number" && override > 0 ? override : fallback; +} + // Resolve an RPM override. 0 or missing means "infinite" (no rate cap). function resolveRpm(override: number | undefined | null): number { - return typeof override === "number" && override > 0 ? override : EFFECTIVELY_INFINITE; + return resolveOverride(override, EFFECTIVELY_INFINITE); } // Resolve a minTime override. 0 or missing means "no minimum gap". function resolveMinTime(override: number | undefined | null): number { - return typeof override === "number" && override > 0 ? override : 0; + return resolveOverride(override, 0); } // Resolve a maxConcurrent override. 0 or missing means "effectively infinite". function resolveMaxConcurrent(override: number | undefined | null): number { - return typeof override === "number" && override > 0 ? override : EFFECTIVELY_INFINITE_CONCURRENCY; + return resolveOverride(override, EFFECTIVELY_INFINITE_CONCURRENCY); } export function resolveRequestQueueMaxWaitMs( provider: string, - configuredMaxWaitMs: number = currentRequestQueueSettings.maxWaitMs + configuredMaxWaitMs: number = currentRequestQueueSettings.maxWaitMs, + connectionId?: string ): number { - return provider.trim().toLowerCase() === "zai-web" - ? Math.max(configuredMaxWaitMs, ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS) - : configuredMaxWaitMs; + const legacyDefault = + provider.trim().toLowerCase() === "zai-web" + ? Math.max(configuredMaxWaitMs, ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS) + : configuredMaxWaitMs; + const override = connectionId + ? connectionRateLimitOverrides.get(connectionId)?.maxWaitMs + : undefined; + return resolveOverride(override, legacyDefault); } function buildLimiterDefaults() { @@ -546,7 +558,7 @@ export async function withRateLimit(provider, connectionId, model, fn, signal = // Proactive sliding-window fallback for header-less providers with a declared cap // (Fase 8.2). No-op unless PROVIDER_DEFAULT_RATE_LIMITS has an entry for `provider`. - const maxWaitMs = resolveRequestQueueMaxWaitMs(provider); + const maxWaitMs = resolveRequestQueueMaxWaitMs(provider, undefined, connectionId); await awaitProviderDefaultSlot(provider, connectionId, signal, maxWaitMs); const limiter = getLimiter(provider, connectionId, model); diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx index 838b71788a79..e2eb3288e217 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx @@ -108,6 +108,7 @@ export default function EditConnectionModal({ tpm: "", tpd: "", minTime: "", + maxWaitMs: "", rateLimitMaxConcurrent: "", apiKey: "", healthCheckInterval: 60, @@ -312,6 +313,10 @@ export default function EditConnectionModal({ connection.rateLimitOverrides?.minTime != null ? String(connection.rateLimitOverrides.minTime) : "", + maxWaitMs: + connection.rateLimitOverrides?.maxWaitMs != null + ? String(connection.rateLimitOverrides.maxWaitMs) + : "", rateLimitMaxConcurrent: connection.rateLimitOverrides?.maxConcurrent != null ? String(connection.rateLimitOverrides.maxConcurrent) @@ -528,6 +533,7 @@ export default function EditConnectionModal({ if (formData.tpm.trim()) overrides.tpm = Number(formData.tpm); if (formData.tpd.trim()) overrides.tpd = Number(formData.tpd); if (formData.minTime.trim()) overrides.minTime = Number(formData.minTime); + if (formData.maxWaitMs.trim()) overrides.maxWaitMs = Number(formData.maxWaitMs); if (formData.rateLimitMaxConcurrent.trim()) overrides.maxConcurrent = Number(formData.rateLimitMaxConcurrent); updates.rateLimitOverrides = Object.keys(overrides).length > 0 ? overrides : null; @@ -1224,6 +1230,15 @@ export default function EditConnectionModal({ placeholder={t("inherit")} hint={t("rateLimitOverridesMinTimeHint")} /> + setFormData({ ...formData, maxWaitMs: e.target.value })} + placeholder={t("inherit")} + hint={t("rateLimitOverridesMaxWaitMsHint")} + /> { - if (typeof raw === "string") { - if (raw.trim() === "") return NaN; - const parsed = Number(raw); - return Number.isNaN(parsed) ? raw : parsed; - } - return raw; - }, - z.coerce.number().int().min(0).max(max) - ); + return z.preprocess((raw) => { + if (typeof raw === "string") { + if (raw.trim() === "") return NaN; + const parsed = Number(raw); + return Number.isNaN(parsed) ? raw : parsed; + } + return raw; + }, z.coerce.number().int().min(0).max(max)); } export const updateProviderConnectionSchema = z @@ -501,6 +498,7 @@ export const updateProviderConnectionSchema = z tpd: rateLimitOverrideNumber(10_000_000_000).optional(), minTime: rateLimitOverrideNumber(60_000).optional(), maxConcurrent: rateLimitOverrideNumber(10_000).optional(), + maxWaitMs: rateLimitOverrideNumber(120_000).optional(), }) .partial() .strict() diff --git a/tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx b/tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx new file mode 100644 index 000000000000..4f8638bceaac --- /dev/null +++ b/tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx @@ -0,0 +1,164 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/store/notificationStore", () => ({ + useNotificationStore: () => ({ notify: vi.fn() }), +})); + +vi.mock("@/store/emailPrivacyStore", () => ({ + default: () => ({ hidden: false, toggle: vi.fn() }), +})); + +// Expanding "Advanced settings" also mounts ProviderTierField (#7818), which +// fetches its current tier override on mount independent of this modal's own +// save flow. Mock it out — its network call is unrelated to maxWaitMs. +vi.mock( + "@/app/(dashboard)/dashboard/providers/[id]/components/modals/providerTierFieldApi", + () => ({ + fetchProviderTierOverride: vi.fn().mockResolvedValue(""), + saveProviderTierOverride: vi.fn().mockResolvedValue(undefined), + }) +); + +const { default: EditConnectionModal } = + await import("../../../src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx"); + +let container: HTMLDivElement; +let root: Root; + +beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => root.unmount()); + container.remove(); + vi.clearAllMocks(); +}); + +function renderModal(connection: Record) { + act(() => { + root.render( + + ); + }); +} + +function expandAdvancedSettings() { + // The "Rate Limit Overrides" section (like the rest of the advanced fields) + // is collapsed by default behind the "Advanced settings" disclosure toggle. + const toggle = container.querySelector( + 'button[aria-controls="edit-connection-advanced-settings"]' + ) as HTMLButtonElement | null; + expect(toggle).not.toBeNull(); + act(() => { + toggle!.click(); + }); +} + +function findMaxWaitMsInput(): HTMLInputElement | null { + const label = Array.from(container.querySelectorAll("label")).find( + (el) => el.textContent === "rateLimitOverridesMaxWaitMsLabel" + ); + const forId = label?.getAttribute("for"); + return forId ? (container.querySelector(`#${forId}`) as HTMLInputElement | null) : null; +} + +function clickSave() { + const button = Array.from(container.querySelectorAll("button")).find( + (b) => b.textContent === "save" + ); + expect(button).toBeTruthy(); + button!.click(); +} + +describe("EditConnectionModal — maxWaitMs rate-limit override", () => { + it("renders an empty maxWaitMs field for a connection with no override", () => { + renderModal({ + id: "conn-1", + provider: "nvidia", + authType: "apikey", + name: "key", + rateLimitOverrides: { rpm: 30 }, + }); + expandAdvancedSettings(); + const input = findMaxWaitMsInput(); + expect(input).not.toBeNull(); + expect(input?.value).toBe(""); + }); + + it("preserves a persisted maxWaitMs override in form state", () => { + renderModal({ + id: "conn-2", + provider: "nvidia", + authType: "apikey", + name: "key", + rateLimitOverrides: { maxWaitMs: 45000 }, + }); + expandAdvancedSettings(); + const input = findMaxWaitMsInput(); + expect(input?.value).toBe("45000"); + }); + + it("submits the entered maxWaitMs as rateLimitOverrides.maxWaitMs", async () => { + // The "Rate Limit Overrides" section only renders for non-OAuth + // connections (`{!isOAuth && (...)}` wraps it, same gate as + // rpm/minTime/maxConcurrent). formData.apiKey stays "" (untouched by this + // test), so handleSubmit's `!isOAuth && formData.apiKey` validation-fetch + // branch is skipped and the save completes synchronously without mocking + // `fetch`. + const onSave = vi.fn().mockResolvedValue(undefined); + act(() => { + root.render( + + ); + }); + + expandAdvancedSettings(); + const input = findMaxWaitMsInput(); + expect(input).not.toBeNull(); + const setter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + "value" + )!.set!; + await act(async () => { + setter.call(input, "45000"); + input!.dispatchEvent(new Event("input", { bubbles: true })); + }); + + await act(async () => { + clickSave(); + }); + + expect(onSave).toHaveBeenCalledTimes(1); + const updates = onSave.mock.calls[0][0] as { + rateLimitOverrides: Record | null; + }; + expect(updates.rateLimitOverrides?.maxWaitMs).toBe(45000); + }); +}); diff --git a/tests/unit/provider-rate-limit-overrides-schema.test.ts b/tests/unit/provider-rate-limit-overrides-schema.test.ts index 84bca5b7f982..279e3a76eb7d 100644 --- a/tests/unit/provider-rate-limit-overrides-schema.test.ts +++ b/tests/unit/provider-rate-limit-overrides-schema.test.ts @@ -11,9 +11,23 @@ function parse(overrides: unknown) { } test("rateLimitOverrides: valid object with all fields", () => { - const r = parse({ rpm: 100, tpm: 50000, tpd: 1000000, minTime: 100, maxConcurrent: 5 }); + const r = parse({ + rpm: 100, + tpm: 50000, + tpd: 1000000, + minTime: 100, + maxConcurrent: 5, + maxWaitMs: 45000, + }); assert.ok(r.success, String(r.error)); - assert.deepEqual(r.data.rateLimitOverrides, { rpm: 100, tpm: 50000, tpd: 1000000, minTime: 100, maxConcurrent: 5 }); + assert.deepEqual(r.data.rateLimitOverrides, { + rpm: 100, + tpm: 50000, + tpd: 1000000, + minTime: 100, + maxConcurrent: 5, + maxWaitMs: 45000, + }); }); test("rateLimitOverrides: partial fields", () => { @@ -71,3 +85,32 @@ test("rateLimitOverrides: all zeros is valid", () => { const r = parse({ rpm: 0, tpm: 0, tpd: 0, minTime: 0, maxConcurrent: 0 }); assert.ok(r.success, String(r.error)); }); + +test("rateLimitOverrides: valid maxWaitMs", () => { + const r = parse({ maxWaitMs: 45000 }); + assert.ok(r.success, String(r.error)); + assert.deepEqual(r.data.rateLimitOverrides, { maxWaitMs: 45000 }); +}); + +test("rateLimitOverrides: maxWaitMs coerced from string", () => { + const r = parse({ maxWaitMs: "30000" }); + assert.ok(r.success, String(r.error)); + assert.equal(r.data.rateLimitOverrides.maxWaitMs, 30000); +}); + +test("rateLimitOverrides: rejects negative maxWaitMs", () => { + assert.equal(parse({ maxWaitMs: -1 }).success, false); +}); + +test("rateLimitOverrides: rejects float maxWaitMs", () => { + assert.equal(parse({ maxWaitMs: 1.5 }).success, false); +}); + +test("rateLimitOverrides: rejects maxWaitMs above 120000 ceiling", () => { + assert.equal(parse({ maxWaitMs: 120001 }).success, false); +}); + +test("rateLimitOverrides: maxWaitMs of 0 is valid (no override)", () => { + const r = parse({ maxWaitMs: 0 }); + assert.ok(r.success, String(r.error)); +}); diff --git a/tests/unit/ratelimit-admission-control-6593.test.ts b/tests/unit/ratelimit-admission-control-6593.test.ts index 80d3af4007b7..04f3dd9fe1b3 100644 --- a/tests/unit/ratelimit-admission-control-6593.test.ts +++ b/tests/unit/ratelimit-admission-control-6593.test.ts @@ -189,6 +189,48 @@ test("#6593 zai-web receives a provider-scoped 60s scheduling budget", () => { assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("ZAI-WEB", 90_000), 90_000); }); +test("#6593 connection maxWaitMs override takes priority over the zai-web scheduling budget", () => { + rateLimitManager.refreshConnectionRateLimits("conn-maxwait-override", { maxWaitMs: 45_000 }); + try { + // Non-special provider: override wins over the passed-in configured default. + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("openai", 15_000, "conn-maxwait-override"), + 45_000 + ); + // zai-web: override wins over its hardcoded 60s floor too. + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("zai-web", 15_000, "conn-maxwait-override"), + 45_000 + ); + } finally { + rateLimitManager.refreshConnectionRateLimits("conn-maxwait-override", null); + } +}); + +test("#6593 a connection without a maxWaitMs override keeps the zai-web 60s floor", () => { + rateLimitManager.refreshConnectionRateLimits("conn-no-maxwait-override", { rpm: 10 }); + try { + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("zai-web", 15_000, "conn-no-maxwait-override"), + rateLimitManager.ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS + ); + } finally { + rateLimitManager.refreshConnectionRateLimits("conn-no-maxwait-override", null); + } +}); + +test("#6593 a maxWaitMs override of 0 is treated as no override", () => { + rateLimitManager.refreshConnectionRateLimits("conn-zero-maxwait-override", { maxWaitMs: 0 }); + try { + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("openai", 15_000, "conn-zero-maxwait-override"), + 15_000 + ); + } finally { + rateLimitManager.refreshConnectionRateLimits("conn-zero-maxwait-override", null); + } +}); + test("#6593 DEFAULT_REQUEST_QUEUE_MAX_DEPTH defaults to 0 (disabled) absent an env override", () => { assert.equal(process.env.RATE_LIMIT_MAX_QUEUE_DEPTH, undefined); assert.equal(resilienceSettings.DEFAULT_REQUEST_QUEUE_MAX_DEPTH, 0); From 484ec30e401d20ef970147c9d2150cfb9d2e82cc Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 23 Aug 2026 17:56:23 +0200 Subject: [PATCH 2/3] fix(providers): allow maxWaitMs in the rateLimitOverrides persistence allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #11251 added maxWaitMs to the Zod validation schema and the EditConnectionModal UI, but not to the separate allowlist in sanitizeRateLimitOverrides() (src/lib/db/providers/columns.ts) that gates what actually gets persisted. Saving the field from the dashboard threw "Refusing to persist rateLimitOverrides with rejected keys: maxWaitMs" (500) on every attempt — the modal's onSave() error path has no visible surfacing for this case, so the save silently appeared to do nothing. Adds maxWaitMs to the allowlist. TDD: tests/unit/columns-validation.test.ts red before the fix (asserted maxWaitMs rejected), green after. --- src/lib/db/providers/columns.ts | 2 +- tests/unit/columns-validation.test.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/db/providers/columns.ts b/src/lib/db/providers/columns.ts index f06f948cd212..4fa5806bccca 100644 --- a/src/lib/db/providers/columns.ts +++ b/src/lib/db/providers/columns.ts @@ -80,7 +80,7 @@ export type SanitizeResult = { export function sanitizeRateLimitOverrides(value: unknown): SanitizeResult { if (value === null || value === undefined) return { sanitized: null, rejected: [] }; if (typeof value !== "object" || Array.isArray(value)) return { sanitized: null, rejected: [] }; - const allowedKeys = new Set(["rpm", "tpm", "tpd", "minTime", "maxConcurrent"]); + const allowedKeys = new Set(["rpm", "tpm", "tpd", "minTime", "maxConcurrent", "maxWaitMs"]); const rejected: string[] = []; const map: Record = {}; for (const [key, v] of Object.entries(value as Record)) { diff --git a/tests/unit/columns-validation.test.ts b/tests/unit/columns-validation.test.ts index 41f70cddeb25..64f84b22fa34 100644 --- a/tests/unit/columns-validation.test.ts +++ b/tests/unit/columns-validation.test.ts @@ -22,3 +22,13 @@ test("valid input yields no rejected keys", () => { assert.deepEqual(r.rejected, []); assert.deepEqual(r.sanitized, { rpm: 10, tpm: 20 }); }); + +// #11251 added `maxWaitMs` to the Zod validation schema and the +// EditConnectionModal UI, but not to this separate allowlist — saving the +// field from the dashboard threw "Refusing to persist rateLimitOverrides +// with rejected keys: maxWaitMs" (500) on every attempt. +test("sanitizeRateLimitOverrides accepts maxWaitMs (#11251 follow-up)", () => { + const r = sanitizeRateLimitOverrides({ minTime: 500, maxWaitMs: 30000 }); + assert.deepEqual(r.rejected, []); + assert.deepEqual(r.sanitized, { minTime: 500, maxWaitMs: 30000 }); +}); From d82199404978826b172255f8ccc472730b08c3f3 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Sun, 23 Aug 2026 12:53:09 -0300 Subject: [PATCH 3/3] i18n(vi): real translations for rateLimitOverridesMaxWaitMs* (#11251) --- src/i18n/messages/vi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 5484aac78ab9..f934934088f4 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -5855,8 +5855,8 @@ "rateLimitOverridesSection": "Ghi đè giới hạn tốc độ", "rateLimitOverridesMaxConcurrentHint": "Ghi đè số yêu cầu đồng thời tối đa cho kết nối này. Ghi đè giới hạn ở cấp tài khoản.", "rateLimitOverridesMaxConcurrentLabel": "Đồng thời tối đa (Giới hạn tốc độ)", - "rateLimitOverridesMaxWaitMsHint": "__MISSING__:Maximum time (ms) a request may wait for a rate-limit slot before failing. Overrides the global Resilience setting for this connection only.", - "rateLimitOverridesMaxWaitMsLabel": "__MISSING__:Max Queue Wait (ms)", + "rateLimitOverridesMaxWaitMsHint": "Thời gian tối đa (ms) một yêu cầu có thể chờ để có suất giới hạn tốc độ trước khi thất bại. Ghi đè cài đặt Resilience toàn cục chỉ cho kết nối này.", + "rateLimitOverridesMaxWaitMsLabel": "Thời gian chờ hàng đợi tối đa (ms)", "rateLimitOverridesMinTimeHint": "Thời gian tối thiểu (ms) giữa các yêu cầu. Ghi đè độ trễ bộ giới hạn tốc độ mặc định.", "rateLimitOverridesMinTimeLabel": "Khoảng thời gian tối thiểu (ms)", "rateLimitOverridesRpmHint": "Số yêu cầu tối đa mỗi phút cho kết nối này. Ghi đè giá trị mặc định của nhà cung cấp.",