From a575eb1da0b7bb89a348aa1846a08f022a899234 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 15:32:08 +0900 Subject: [PATCH 01/24] feat(anthropic): add account pool quotaWindow config type and normalization --- src/oauth/anthropic-routing.ts | 22 +++++++++++++++- src/types.ts | 1 + src/types/config.ts | 4 +++ tests/anthropic-account-pool.test.ts | 39 ++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index 69dcd021a1..aed5b863c3 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -27,7 +27,7 @@ import { POOL_KEY_ANTHROPIC, seedPoolRotationAccount, } from "../codex/pool-rotation"; -import type { OcxAccountPoolRotationStrategy, OcxConfig } from "../types"; +import type { OcxAccountPoolQuotaWindow, OcxAccountPoolRotationStrategy, OcxConfig } from "../types"; import { sweepExpiredOnWrite } from "../lib/state-store-sweeper"; import { retainedUtf8Bytes } from "../lib/admission"; @@ -39,6 +39,8 @@ const MAX_AFFINITY_ENTRIES = 2_000; const MAX_AFFINITY_COMPONENT_BYTES = 512; const UNKNOWN_USAGE_SCORE = 100; const DEFAULT_AUTO_SWITCH_THRESHOLD = 80; +const DEFAULT_QUOTA_WINDOW: OcxAccountPoolQuotaWindow = "five-hour"; +const VALID_QUOTA_WINDOWS = new Set(["five-hour", "weekly", "max-utilization"]); /** Cap same-request 429 rotations so short Retry-After cannot infinite-loop. */ export const ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST = 3; @@ -50,6 +52,8 @@ export interface AnthropicAccountPoolConfig { strategy?: OcxAccountPoolRotationStrategy; /** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */ stickyLimit?: number; + /** Usage window for quota-based scoring. Default "five-hour" (today's behaviour). */ + quotaWindow?: OcxAccountPoolQuotaWindow; } interface AccountHealth { @@ -86,6 +90,22 @@ export function anthropicAutoSwitchThreshold(config: OcxConfig): number { return DEFAULT_AUTO_SWITCH_THRESHOLD; } +/** Strict parse for management APIs — returns null instead of defaulting. */ +export function parseAccountPoolQuotaWindow(raw: unknown): OcxAccountPoolQuotaWindow | null { + if (typeof raw === "string" && VALID_QUOTA_WINDOWS.has(raw as OcxAccountPoolQuotaWindow)) { + return raw as OcxAccountPoolQuotaWindow; + } + return null; +} + +export function normalizeAccountPoolQuotaWindow(raw: unknown): OcxAccountPoolQuotaWindow { + return parseAccountPoolQuotaWindow(raw) ?? DEFAULT_QUOTA_WINDOW; +} + +export function anthropicQuotaWindow(config: AnthropicAccountPoolConfig): OcxAccountPoolQuotaWindow { + return normalizeAccountPoolQuotaWindow(config.quotaWindow); +} + function parseRetryAfterMs(value: string | null | undefined, now: number): number | undefined { const text = value?.trim(); if (!text) return undefined; diff --git a/src/types.ts b/src/types.ts index 08880878df..89ca2a619e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,6 +65,7 @@ export type { OcxConfigRebaseProvenance, OcxConfig, OcxAccountPoolRotationStrategy, + OcxAccountPoolQuotaWindow, OcxComboStrategy, OcxComboDefaultEffort, OcxComboTarget, diff --git a/src/types/config.ts b/src/types/config.ts index 0fa86715bd..53fca2809b 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -628,6 +628,8 @@ export interface OcxConfig { strategy?: OcxAccountPoolRotationStrategy; /** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */ stickyLimit?: number; + /** Usage window for quota-based scoring. Default "five-hour" (today's behaviour). */ + quotaWindow?: OcxAccountPoolQuotaWindow; }; /** * Generic OAuth multi-account 429 failover (#2568). Presence-driven by default. @@ -662,6 +664,8 @@ export interface OcxConfig { export type OcxAccountPoolRotationStrategy = "quota" | "round-robin" | "fill-first"; +export type OcxAccountPoolQuotaWindow = "five-hour" | "weekly" | "max-utilization"; + export type OcxComboStrategy = "failover" | "round-robin" | "random" | "least-used" | "reset-window"; export type OcxComboDefaultEffort = "low" | "medium" | "high" | "xhigh" | "max" | "ultra"; diff --git a/tests/anthropic-account-pool.test.ts b/tests/anthropic-account-pool.test.ts index c13f59bd10..7650a8f2dd 100644 --- a/tests/anthropic-account-pool.test.ts +++ b/tests/anthropic-account-pool.test.ts @@ -4,12 +4,15 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { clearPoolRotationState, notePoolRotationFailure, POOL_KEY_ANTHROPIC } from "../src/codex/pool-rotation"; import { + anthropicQuotaWindow, anthropicSessionKeyFromParts, bindAnthropicSessionAffinity, clearAnthropicAccountPoolState, formatAnthropicProviderForLog, getEligibleAnthropicAccounts, isAnthropicAccountPoolEnabled, + normalizeAccountPoolQuotaWindow, + parseAccountPoolQuotaWindow, resolveAnthropicAccountForSession, resetAnthropicRoutingForManualSelection, rotateAnthropicAccountOn429, @@ -380,3 +383,39 @@ describe("anthropic account pool", () => { expect(resolveAnthropicAccountForSession("seed-3", config).accountId).toBe(cId); }); }); + +describe("anthropic account pool quota window", () => { + test("every valid window normalizes and strict-parses to itself", () => { + for (const window of ["five-hour", "weekly", "max-utilization"]) { + expect(normalizeAccountPoolQuotaWindow(window)).toBe(window); + expect(parseAccountPoolQuotaWindow(window)).toBe(window); + } + }); + + test("an unknown window string defaults to five-hour and fails strict parse", () => { + expect(normalizeAccountPoolQuotaWindow("daily")).toBe("five-hour"); + expect(parseAccountPoolQuotaWindow("daily")).toBeNull(); + }); + + test("undefined defaults to five-hour and fails strict parse", () => { + expect(normalizeAccountPoolQuotaWindow(undefined)).toBe("five-hour"); + expect(parseAccountPoolQuotaWindow(undefined)).toBeNull(); + }); + + test("non-string input never satisfies strict parse", () => { + expect(parseAccountPoolQuotaWindow(5)).toBeNull(); + expect(parseAccountPoolQuotaWindow(null)).toBeNull(); + expect(parseAccountPoolQuotaWindow({})).toBeNull(); + expect(parseAccountPoolQuotaWindow(["weekly"])).toBeNull(); + expect(normalizeAccountPoolQuotaWindow(5)).toBe("five-hour"); + }); + + test("accessor reads the pool config field and defaults when absent or invalid", () => { + expect(anthropicQuotaWindow({ quotaWindow: "five-hour" })).toBe("five-hour"); + expect(anthropicQuotaWindow({ quotaWindow: "weekly" })).toBe("weekly"); + expect(anthropicQuotaWindow({ quotaWindow: "max-utilization" })).toBe("max-utilization"); + expect(anthropicQuotaWindow({})).toBe("five-hour"); + expect(anthropicQuotaWindow({ enabled: true })).toBe("five-hour"); + expect(anthropicQuotaWindow({ quotaWindow: "daily" })).toBe("five-hour"); + }); +}); From f9323b0183b3a6b72b812f37cea48d85410f393b Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 15:42:00 +0900 Subject: [PATCH 02/24] refactor(anthropic): thread config through account pool usage scoring --- src/oauth/anthropic-routing.ts | 51 +++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index aed5b863c3..c5eeb71649 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -162,12 +162,16 @@ function isCooled(accountId: string, now: number): boolean { return getAnthropicAccountHealthSnapshot(accountId, now) !== null; } -function hasKnownUsage(accountId: string): boolean { +/** + * Usage scoring takes `config` so which quota window is read stays a + * configuration decision. `five-hour` is the only window scored today. + */ +function hasKnownUsage(config: OcxConfig, accountId: string): boolean { const quota = getCachedProviderAccountQuota(PROVIDER, accountId); return typeof quota?.fiveHourPercent === "number" && Number.isFinite(quota.fiveHourPercent); } -function usageScore(accountId: string): number { +function usageScore(config: OcxConfig, accountId: string): number { const quota = getCachedProviderAccountQuota(PROVIDER, accountId); if (!quota || typeof quota.fiveHourPercent !== "number" || !Number.isFinite(quota.fiveHourPercent)) { return UNKNOWN_USAGE_SCORE; @@ -211,20 +215,29 @@ export function getAnthropicPoolRetryAfterSeconds(now = Date.now()): number | nu return Math.max(1, Math.ceil((earliest - now) / 1000)); } -function pickLowestUsage(excludeId: string | undefined, now: number): string | null { +interface ScoredAccount { + accountId: string; + score: number; +} + +function compareScoredAccounts(a: ScoredAccount, b: ScoredAccount): number { + return a.score - b.score; +} + +function pickLowestUsage(config: OcxConfig, excludeId: string | undefined, now: number): string | null { const eligible = getEligibleAnthropicAccounts(now).filter(id => id !== excludeId); if (eligible.length === 0) return null; - let best = eligible[0]!; - let bestScore = usageScore(best); - for (let i = 1; i < eligible.length; i++) { - const id = eligible[i]!; - const score = usageScore(id); - if (score < bestScore) { - best = id; - bestScore = score; - } + const scored: ScoredAccount[] = eligible.map(accountId => ({ + accountId, + score: usageScore(config, accountId), + })); + let best = scored[0]!; + for (let i = 1; i < scored.length; i++) { + const candidate = scored[i]!; + // Strict `< 0` keeps the earliest eligible account on an exact tie. + if (compareScoredAccounts(candidate, best) < 0) best = candidate; } - return best; + return best.accountId; } /** Next eligible Anthropic account in stable order after `afterId` (wrapping). */ @@ -270,7 +283,7 @@ function pickAlternateAnthropicAccount( if (strategy === "fill-first") { return pickNextFillFirstAnthropicAccount(config, excludeId, eligible); } - return pickLowestUsage(excludeId, now); + return pickLowestUsage(config, excludeId, now); } function pruneExpiredAffinity(now: number): void { @@ -311,8 +324,8 @@ function isActiveUnderFillFirstThreshold(config: OcxConfig, accountId: string): const threshold = anthropicAutoSwitchThreshold(config); if (threshold <= 0) return true; // Unknown usage must not force fill-first to abandon the active account. - if (!hasKnownUsage(accountId)) return true; - return usageScore(accountId) < threshold; + if (!hasKnownUsage(config, accountId)) return true; + return usageScore(config, accountId) < threshold; } /** @@ -433,11 +446,11 @@ export function resolveAnthropicAccountForSession( if (threshold > 0) { // Unknown usage must NOT force a switch away from the healthy active account. - if (activeOk && (!hasKnownUsage(set.activeAccountId) || usageScore(set.activeAccountId) < threshold)) { + if (activeOk && (!hasKnownUsage(config, set.activeAccountId) || usageScore(config, set.activeAccountId) < threshold)) { accountId = set.activeAccountId; reason = "active"; } else { - const picked = pickLowestUsage(undefined, now); + const picked = pickLowestUsage(config, undefined, now); if (picked) { accountId = picked; reason = activeOk && picked === set.activeAccountId ? "active" : "lowest-usage"; @@ -450,7 +463,7 @@ export function resolveAnthropicAccountForSession( accountId = set.activeAccountId; reason = "active"; } else { - const picked = pickLowestUsage(set.activeAccountId, now); + const picked = pickLowestUsage(config, set.activeAccountId, now); if (picked) { accountId = picked; reason = "only-eligible"; From 35e16d761dbf0f4966531cd413f067aeb1d495a9 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 15:42:57 +0900 Subject: [PATCH 03/24] feat(management): expose quotaWindow on the anthropic account-pool API --- src/server/management/oauth-account-routes.ts | 13 +++ tests/account-pool-management-api.test.ts | 79 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/server/management/oauth-account-routes.ts b/src/server/management/oauth-account-routes.ts index ee57515b41..5e441ea013 100644 --- a/src/server/management/oauth-account-routes.ts +++ b/src/server/management/oauth-account-routes.ts @@ -39,6 +39,7 @@ import { parseAccountPoolStickyLimit, parseAccountPoolStrategy, } from "../../codex/pool-rotation"; +import { normalizeAccountPoolQuotaWindow, parseAccountPoolQuotaWindow } from "../../oauth/anthropic-routing"; import { primeCodexPoolQuotas } from "../../codex/auth-api"; import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap"; import { resolveCodexHomeDir } from "../../codex/home"; @@ -326,6 +327,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise< autoSwitchThreshold: typeof pool.autoSwitchThreshold === "number" ? pool.autoSwitchThreshold : 80, strategy: normalizeAccountPoolStrategy(pool.strategy), stickyLimit: normalizeAccountPoolStickyLimit(pool.stickyLimit), + quotaWindow: normalizeAccountPoolQuotaWindow(pool.quotaWindow), experimental: true, }); } @@ -340,6 +342,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise< autoSwitchThreshold?: unknown; strategy?: unknown; stickyLimit?: unknown; + quotaWindow?: unknown; }; const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : ""; if (provider !== "anthropic") return jsonResponse({ error: "pool config is only supported for anthropic" }, 400); @@ -376,11 +379,20 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise< } stickyLimit = parsed; } + let quotaWindow = config.anthropicAccountPool?.quotaWindow; + if (body.quotaWindow !== undefined) { + const parsed = parseAccountPoolQuotaWindow(body.quotaWindow); + if (parsed === null) { + return jsonResponse({ error: "quotaWindow must be one of: five-hour, weekly, max-utilization" }, 400); + } + quotaWindow = parsed; + } config.anthropicAccountPool = { enabled, autoSwitchThreshold: threshold, ...(strategy !== undefined ? { strategy } : {}), ...(stickyLimit !== undefined ? { stickyLimit } : {}), + ...(quotaWindow !== undefined ? { quotaWindow } : {}), }; saveConfigPreservingClaudeCode(config); reconcileLiveStateStores(); @@ -391,6 +403,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise< autoSwitchThreshold: threshold, strategy: normalizeAccountPoolStrategy(strategy), stickyLimit: normalizeAccountPoolStickyLimit(stickyLimit), + quotaWindow: normalizeAccountPoolQuotaWindow(quotaWindow), experimental: true, }); } diff --git a/tests/account-pool-management-api.test.ts b/tests/account-pool-management-api.test.ts index 4dbe399269..0a8c993215 100644 --- a/tests/account-pool-management-api.test.ts +++ b/tests/account-pool-management-api.test.ts @@ -352,4 +352,83 @@ describe("Anthropic account pool strategy management API", () => { await server.stop(true); } }); + + test("GET returns quotaWindow five-hour by default", async () => { + const server = startServer(0); + try { + const res = await fetch(new URL("/api/oauth/accounts/pool?provider=anthropic", server.url)); + expect(res.status).toBe(200); + expect(await res.json()).toMatchObject({ quotaWindow: "five-hour" }); + } finally { + await server.stop(true); + } + }); + + test("PUT persists each valid quotaWindow value", async () => { + const server = startServer(0); + try { + for (const quotaWindow of ["weekly", "max-utilization", "five-hour"]) { + const put = await fetch(new URL("/api/oauth/accounts/pool", server.url), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ provider: "anthropic", enabled: true, quotaWindow }), + }); + expect(put.status).toBe(200); + expect(await put.json()).toMatchObject({ ok: true, quotaWindow }); + + const get = await fetch(new URL("/api/oauth/accounts/pool?provider=anthropic", server.url)); + expect(await get.json()).toMatchObject({ quotaWindow }); + } + } finally { + await server.stop(true); + } + }); + + test("PUT rejects invalid quotaWindow with 400", async () => { + const server = startServer(0); + try { + for (const bad of ["monthly", "", "Weekly", 1, null]) { + const res = await fetch(new URL("/api/oauth/accounts/pool", server.url), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ provider: "anthropic", enabled: true, quotaWindow: bad }), + }); + expect(res.status).toBe(400); + expect(await res.json()).toMatchObject({ + error: "quotaWindow must be one of: five-hour, weekly, max-utilization", + }); + } + } finally { + await server.stop(true); + } + }); + + test("PUT without quotaWindow preserves the existing value", async () => { + const server = startServer(0); + try { + const first = await fetch(new URL("/api/oauth/accounts/pool", server.url), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ provider: "anthropic", enabled: true, quotaWindow: "weekly" }), + }); + expect(first.status).toBe(200); + + const second = await fetch(new URL("/api/oauth/accounts/pool", server.url), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ provider: "anthropic", enabled: false, autoSwitchThreshold: 55 }), + }); + expect(second.status).toBe(200); + expect(await second.json()).toMatchObject({ quotaWindow: "weekly" }); + + const get = await fetch(new URL("/api/oauth/accounts/pool?provider=anthropic", server.url)); + expect(await get.json()).toMatchObject({ + enabled: false, + autoSwitchThreshold: 55, + quotaWindow: "weekly", + }); + } finally { + await server.stop(true); + } + }); }); From 4f870c8b7b3a23434b4a1b7ff9e1231d6ea645d1 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 15:43:38 +0900 Subject: [PATCH 04/24] docs(anthropic): document account pool quotaWindow across locales --- .../src/content/docs/fr/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/guides/claude-code.md | 3 ++- .../src/content/docs/ja/reference/configuration/providers.md | 2 +- .../src/content/docs/ko/reference/configuration/providers.md | 2 +- .../src/content/docs/reference/configuration/providers.md | 5 +++-- .../src/content/docs/ru/reference/configuration/providers.md | 2 +- .../src/content/docs/tr/reference/configuration/providers.md | 2 +- .../content/docs/zh-cn/reference/configuration/providers.md | 2 +- .../content/docs/zh-tw/reference/configuration/providers.md | 2 +- 9 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 47029ee45b..5c87224cb4 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -194,7 +194,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, choisir la plus faible utilisation connue et mise en cache sur 5 heures qui atteint ou dépasse ce seuil. `0` désactive la sélection selon le quota. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; quota utilise uniquement les barres sur 5 heures. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; quota lit la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index 07f989ef5f..e5d9c6a42a 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -15,7 +15,8 @@ add-account). By default every request uses the **active** account only. An **experimental, opt-in** Claude account pool (`anthropicAccountPool.enabled`) adds sticky session affinity and 429 cooldown failover across those OAuth accounts. For **new** sessions only, `anthropicAccountPool.strategy` selects among eligible accounts: `quota` (default) picks -lowest known 5-hour usage when above `autoSwitchThreshold`; `round-robin` spreads evenly +lowest known usage in the window set by `quotaWindow` (default the 5-hour bar) when above +`autoSwitchThreshold`; `round-robin` spreads evenly (`stickyLimit`, default `1`); `fill-first` drains the active account until cooldown, reauthentication, or threshold, then advances. It is **off by default**, shows a GUI warning, and is not battle-tested — Anthropic may restrict accounts that look like automated rotation; diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index b51e8a2e32..a504c84836 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -160,7 +160,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションの場合は、このしきい値以上の、既知の最も低いキャッシュされた 5 時間の使用量を選択します。 `0` はクォータの選択を無効にします。 | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。クォータでは 5 時間足のみを使用します。 | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。quota は `quotaWindow` で指定した期間を参照し、既定は 5 時間足です。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index b9eb746726..444247f13d 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -164,7 +164,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 이 임계값 이상에서 알려진 캐시 5시간 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. quota는 5시간 막대만 사용합니다. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. quota는 `quotaWindow`로 지정한 창을 읽으며, 기본값은 5시간 막대입니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 8840acf900..bdeac425a4 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -283,8 +283,9 @@ rotation may trigger provider restrictions. | Key | Type | Default | Description | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, choose the lowest known cached 5-hour usage at or above this threshold. `0` disables quota picking. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; quota uses 5-hour bars only. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, choose the lowest known cached usage in the configured window at or above this threshold. `0` disables quota picking. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; quota reads the window set by `quotaWindow`. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar the `quota` strategy scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted, and breaks weekly ties by lower 5-hour usage. `max-utilization` scores whichever of the two bars is higher. This affects new-session picking only, not affinity re-evaluation, and per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index 94334dae06..de431a7b2f 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -195,7 +195,7 @@ reauth или порога исчерпания; здоровые привяза | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached 5-hour usage, если активный аккаунт достиг порога. `0` отключает выбор по quota. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; quota смотрит только на 5-hour bar'ы. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; quota смотрит на окно, заданное в `quotaWindow`; по умолчанию это 5-hour bar'ы. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index e4f5497f8c..aed5d44754 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -220,7 +220,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlar için bu eşikte veya üzerinde bilinen en düşük önbelleğe alınmış 5 saatlik kullanımı seçin. `0` kota seçimini devre dışı bırakır. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; kota yalnızca 5 saatlik çubukları kullanır. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; kota, `quotaWindow` ile belirlenen pencereyi okur; varsayılan 5 saatlik çubuklardır. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index ef827e9d32..a25ee2eeca 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -158,7 +158,7 @@ affinity。这些策略不能规避 provider enforcement。 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,选择已知缓存的、5 小时使用率最低且达到或超过此阈值的账户。`0` 会禁用配额选择。 | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;quota 只使用 5 小时条形数据。 | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;quota 读取 `quotaWindow` 指定的窗口,默认是 5 小时条形数据。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index 398a396dc7..47747e4cb2 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -127,7 +127,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,選擇在此閾值或以上的最低已知快取 5 小時用量。`0` 停用量量挑選。 | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;quota 僅使用 5 小時列。 | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;quota 讀取 `quotaWindow` 指定的視窗,預設為 5 小時列。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 From dd15b5490d524f6ea2fe8318a040b3f855b9d8b6 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 16:05:46 +0900 Subject: [PATCH 05/24] feat(anthropic): add window-aware account pool usage scoring --- src/oauth/anthropic-routing.ts | 71 ++++++++-- tests/anthropic-account-pool.test.ts | 185 ++++++++++++++++++++++++++- 2 files changed, 241 insertions(+), 15 deletions(-) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index c5eeb71649..44ec7d22c7 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -162,21 +162,56 @@ function isCooled(accountId: string, now: number): boolean { return getAnthropicAccountHealthSnapshot(accountId, now) !== null; } -/** - * Usage scoring takes `config` so which quota window is read stays a - * configuration decision. `five-hour` is the only window scored today. - */ +function fiveHourKnown(accountId: string): boolean { + const percent = getCachedProviderAccountQuota(PROVIDER, accountId)?.fiveHourPercent; + return typeof percent === "number" && Number.isFinite(percent); +} + +function weeklyKnown(accountId: string): boolean { + const percent = getCachedProviderAccountQuota(PROVIDER, accountId)?.weeklyPercent; + return typeof percent === "number" && Number.isFinite(percent); +} + +function fiveHourScore(accountId: string): number { + const percent = getCachedProviderAccountQuota(PROVIDER, accountId)?.fiveHourPercent; + return typeof percent === "number" && Number.isFinite(percent) + ? Math.max(0, Math.min(100, percent)) + : UNKNOWN_USAGE_SCORE; +} + +function weeklyScore(accountId: string): number { + const percent = getCachedProviderAccountQuota(PROVIDER, accountId)?.weeklyPercent; + return typeof percent === "number" && Number.isFinite(percent) + ? Math.max(0, Math.min(100, percent)) + : UNKNOWN_USAGE_SCORE; +} + +function exhausted5h(accountId: string): boolean { + return fiveHourKnown(accountId) && fiveHourScore(accountId) >= 100; +} + function hasKnownUsage(config: OcxConfig, accountId: string): boolean { - const quota = getCachedProviderAccountQuota(PROVIDER, accountId); - return typeof quota?.fiveHourPercent === "number" && Number.isFinite(quota.fiveHourPercent); + const window = anthropicQuotaWindow(anthropicAccountPoolConfig(config)); + switch (window) { + case "five-hour": return fiveHourKnown(accountId); + case "weekly": return weeklyKnown(accountId); + case "max-utilization": return fiveHourKnown(accountId) || weeklyKnown(accountId); + } } function usageScore(config: OcxConfig, accountId: string): number { - const quota = getCachedProviderAccountQuota(PROVIDER, accountId); - if (!quota || typeof quota.fiveHourPercent !== "number" || !Number.isFinite(quota.fiveHourPercent)) { - return UNKNOWN_USAGE_SCORE; + const window = anthropicQuotaWindow(anthropicAccountPoolConfig(config)); + switch (window) { + case "five-hour": return fiveHourScore(accountId); + case "weekly": return weeklyScore(accountId); + case "max-utilization": { + const scores = [ + ...(fiveHourKnown(accountId) ? [fiveHourScore(accountId)] : []), + ...(weeklyKnown(accountId) ? [weeklyScore(accountId)] : []), + ]; + return scores.length > 0 ? Math.max(...scores) : UNKNOWN_USAGE_SCORE; + } } - return Math.max(0, Math.min(100, quota.fiveHourPercent)); } const TOKEN_SKEW_MS = 60_000; @@ -218,18 +253,23 @@ export function getAnthropicPoolRetryAfterSeconds(now = Date.now()): number | nu interface ScoredAccount { accountId: string; score: number; + fiveHourTieBreak: number; } function compareScoredAccounts(a: ScoredAccount, b: ScoredAccount): number { - return a.score - b.score; + return a.score - b.score || a.fiveHourTieBreak - b.fiveHourTieBreak; } function pickLowestUsage(config: OcxConfig, excludeId: string | undefined, now: number): string | null { - const eligible = getEligibleAnthropicAccounts(now).filter(id => id !== excludeId); + const window = anthropicQuotaWindow(anthropicAccountPoolConfig(config)); + const unfiltered = getEligibleAnthropicAccounts(now).filter(id => id !== excludeId); + const available = window === "weekly" ? unfiltered.filter(id => !exhausted5h(id)) : unfiltered; + const eligible = available.length > 0 ? available : unfiltered; if (eligible.length === 0) return null; const scored: ScoredAccount[] = eligible.map(accountId => ({ accountId, score: usageScore(config, accountId), + fiveHourTieBreak: window === "weekly" ? fiveHourScore(accountId) : 0, })); let best = scored[0]!; for (let i = 1; i < scored.length; i++) { @@ -323,6 +363,8 @@ function anthropicPoolStrategy(config: OcxConfig): OcxAccountPoolRotationStrateg function isActiveUnderFillFirstThreshold(config: OcxConfig, accountId: string): boolean { const threshold = anthropicAutoSwitchThreshold(config); if (threshold <= 0) return true; + const window = anthropicQuotaWindow(anthropicAccountPoolConfig(config)); + if (window === "weekly" && exhausted5h(accountId)) return false; // Unknown usage must not force fill-first to abandon the active account. if (!hasKnownUsage(config, accountId)) return true; return usageScore(config, accountId) < threshold; @@ -445,8 +487,11 @@ export function resolveAnthropicAccountForSession( let reason: AnthropicAccountSelectionReason = "none"; if (threshold > 0) { + const window = anthropicQuotaWindow(anthropicAccountPoolConfig(config)); // Unknown usage must NOT force a switch away from the healthy active account. - if (activeOk && (!hasKnownUsage(config, set.activeAccountId) || usageScore(config, set.activeAccountId) < threshold)) { + if (activeOk + && !(window === "weekly" && exhausted5h(set.activeAccountId)) + && (!hasKnownUsage(config, set.activeAccountId) || usageScore(config, set.activeAccountId) < threshold)) { accountId = set.activeAccountId; reason = "active"; } else { diff --git a/tests/anthropic-account-pool.test.ts b/tests/anthropic-account-pool.test.ts index 7650a8f2dd..c2ca5055b6 100644 --- a/tests/anthropic-account-pool.test.ts +++ b/tests/anthropic-account-pool.test.ts @@ -19,7 +19,7 @@ import { } from "../src/oauth/anthropic-routing"; import { getAccountSet, saveCredential, setActiveAccount } from "../src/oauth/store"; import { clearAccountQuotaCache, setCachedProviderAccountQuotaForTests } from "../src/providers/quota"; -import type { OcxAccountPoolRotationStrategy, OcxConfig } from "../src/types"; +import type { OcxAccountPoolQuotaWindow, OcxAccountPoolRotationStrategy, OcxConfig } from "../src/types"; const originalHome = process.env.OPENCODEX_HOME; let home: string; @@ -68,7 +68,11 @@ async function seedTwoAccounts() { function cfg( enabled: boolean, threshold = 80, - pool: { strategy?: OcxAccountPoolRotationStrategy; stickyLimit?: number } = {}, + pool: { + strategy?: OcxAccountPoolRotationStrategy; + stickyLimit?: number; + quotaWindow?: OcxAccountPoolQuotaWindow; + } = {}, ): OcxConfig { return { port: 0, @@ -419,3 +423,180 @@ describe("anthropic account pool quota window", () => { expect(anthropicQuotaWindow({ quotaWindow: "daily" })).toBe("five-hour"); }); }); + +describe("anthropic account pool quota window scoring", () => { + test("weekly mode picks lowest weekly usage", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 10, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 90, weeklyPercent: 10, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-lowest", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(bId); + }); + + test("weekly mode excludes 5h-exhausted candidate", async () => { + const { aId, bId, cId } = await seedThreeAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 30, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 100, weeklyPercent: 5, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", cId, { fiveHourPercent: 40, weeklyPercent: 50, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-exhausted-candidate", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(cId); + }); + + test("weekly mode keeps 5h-exhausted candidate when it is the only alternative", async () => { + const { aId, bId } = await seedTwoAccounts(); + setCachedProviderAccountQuotaForTests("anthropic", bId, { + fiveHourPercent: 100, + weeklyPercent: 80, + updatedAt: Date.now(), + }); + + expect(rotateAnthropicAccountOn429(cfg(true, 80, { quotaWindow: "weekly" }), aId, "30", "weekly-only-exhausted")).toBe(bId); + }); + + test("weekly mode does not exclude a candidate whose 5h is merely unknown", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 20, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { weeklyPercent: 60, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-unknown-five-hour", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(bId); + }); + + test("weekly mode ranks unknown weekly last", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 90, weeklyPercent: 85, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 10, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-unknown-last", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(aId); + }); + + test("weekly tie breaks by lower five-hour usage", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 50, weeklyPercent: 85, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 20, weeklyPercent: 85, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-five-hour-tie", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(bId); + }); + + test("weekly mode treats 5h-exhausted active as over threshold", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 100, weeklyPercent: 10, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 30, weeklyPercent: 60, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-exhausted-active", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(bId); + }); + + test("max-utilization scores by the hotter window", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 20, weeklyPercent: 95, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 60, weeklyPercent: 30, updatedAt }); + + expect(resolveAnthropicAccountForSession("max-hotter", cfg(true, 80, { quotaWindow: "max-utilization" })).accountId).toBe(bId); + }); + + test("omitted quotaWindow equals explicit five-hour", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 10, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 90, weeklyPercent: 10, updatedAt }); + + expect(resolveAnthropicAccountForSession("window-omitted", cfg(true)).accountId).toBe(aId); + expect(resolveAnthropicAccountForSession("window-five-hour", cfg(true, 80, { quotaWindow: "five-hour" })).accountId).toBe(aId); + expect(resolveAnthropicAccountForSession("window-weekly-control", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(bId); + }); + + test("threshold 0 keeps the active account regardless of quotaWindow", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 100, weeklyPercent: 100, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 0, weeklyPercent: 0, updatedAt }); + + for (const quotaWindow of ["five-hour", "weekly", "max-utilization"] as const) { + const sessionKey = `threshold-zero-${quotaWindow}`; + expect(resolveAnthropicAccountForSession(sessionKey, cfg(true, 0, { quotaWindow })).accountId).toBe(aId); + } + }); + + test("threshold 0 still applies quotaWindow on 429 failover", async () => { + const { aId, bId, cId } = await seedThreeAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 10, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", cId, { fiveHourPercent: 90, weeklyPercent: 10, updatedAt }); + + expect(rotateAnthropicAccountOn429(cfg(true, 0, { quotaWindow: "weekly" }), aId, "30", "threshold-zero-weekly")).toBe(cId); + + clearAnthropicAccountPoolState(); + expect(rotateAnthropicAccountOn429(cfg(true, 0, { quotaWindow: "five-hour" }), aId, "30", "threshold-zero-five-hour")).toBe(bId); + }); + + test("429 failover never returns null while any eligible account remains", async () => { + const { aId, bId } = await seedTwoAccounts(); + setCachedProviderAccountQuotaForTests("anthropic", bId, { + fiveHourPercent: 100, + weeklyPercent: 100, + updatedAt: Date.now(), + }); + + expect(rotateAnthropicAccountOn429(cfg(true, 80, { quotaWindow: "weekly" }), aId, "30", "weekly-never-null")).toBe(bId); + }); + + test("fill-first threshold uses configured window", async () => { + const { aId } = await seedTwoAccounts(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { + fiveHourPercent: 90, + weeklyPercent: 10, + updatedAt: Date.now(), + }); + + expect(resolveAnthropicAccountForSession( + "fill-first-weekly-threshold", + cfg(true, 80, { strategy: "fill-first", quotaWindow: "weekly" }), + ).accountId).toBe(aId); + expect(resolveAnthropicAccountForSession( + "fill-first-five-hour-threshold", + cfg(true, 80, { strategy: "fill-first", quotaWindow: "five-hour" }), + ).accountId).not.toBe(aId); + }); + + test("fill-first at threshold 0 keeps a 5h-exhausted active under weekly", async () => { + const { aId } = await seedTwoAccounts(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { + fiveHourPercent: 100, + weeklyPercent: 100, + updatedAt: Date.now(), + }); + + expect(resolveAnthropicAccountForSession( + "fill-first-zero-weekly", + cfg(true, 0, { strategy: "fill-first", quotaWindow: "weekly" }), + ).accountId).toBe(aId); + }); + + test("weekly mode skips a 5h-exhausted successor under fill-first", async () => { + const { aId, bId, cId } = await seedThreeAccounts(); + expect([aId, bId, cId].sort((a, b) => a.localeCompare(b))).toEqual([aId, bId, cId]); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 10, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 100, weeklyPercent: 10, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", cId, { fiveHourPercent: 20, weeklyPercent: 30, updatedAt }); + + expect(resolveAnthropicAccountForSession( + "fill-first-skip-exhausted", + cfg(true, 80, { strategy: "fill-first", quotaWindow: "weekly" }), + ).accountId).toBe(cId); + }); + + test("weekly mode with empty quota cache falls back to stable order", async () => { + const { aId, bId } = await seedTwoAccounts(); + const config = cfg(true, 80, { quotaWindow: "weekly" }); + + expect(resolveAnthropicAccountForSession("weekly-empty-keep", config).accountId).toBe(aId); + expect(rotateAnthropicAccountOn429(config, aId, "30", "weekly-empty-failover")).toBe(bId); + }); +}); From 57cbe6b03d3719e48b249626c3295b9afe2621cb Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 16:07:08 +0900 Subject: [PATCH 06/24] feat(gui): add a quota window selector to the Claude account pool --- gui/src/account-pool-strategy.ts | 17 ++ .../AnthropicAccountPoolSettings.tsx | 62 ++++- gui/src/i18n/de.ts | 10 +- gui/src/i18n/en.ts | 13 +- gui/src/i18n/fr.ts | 9 +- gui/src/i18n/ja.ts | 10 +- gui/src/i18n/ko.ts | 10 +- gui/src/i18n/ru.ts | 10 +- gui/src/i18n/tr.ts | 10 +- gui/src/i18n/zh-TW.ts | 9 +- gui/src/i18n/zh.ts | 10 +- .../anthropic-pool-quota-window.test.tsx | 212 ++++++++++++++++++ 12 files changed, 372 insertions(+), 10 deletions(-) create mode 100644 gui/tests/anthropic-pool-quota-window.test.tsx diff --git a/gui/src/account-pool-strategy.ts b/gui/src/account-pool-strategy.ts index 4ff6b860c2..4dbc7b9e2b 100644 --- a/gui/src/account-pool-strategy.ts +++ b/gui/src/account-pool-strategy.ts @@ -6,12 +6,23 @@ export const ACCOUNT_POOL_STRATEGIES: readonly AccountPoolStrategy[] = [ "fill-first", ] as const; +/** Which cached usage bar the `quota` strategy scores. Mirrors `OcxAccountPoolQuotaWindow`. */ +export type AccountPoolQuotaWindow = "five-hour" | "weekly" | "max-utilization"; + +export const ACCOUNT_POOL_QUOTA_WINDOWS: readonly AccountPoolQuotaWindow[] = [ + "five-hour", + "weekly", + "max-utilization", +] as const; + export const DEFAULT_ACCOUNT_POOL_STRATEGY: AccountPoolStrategy = "quota"; +export const DEFAULT_ACCOUNT_POOL_QUOTA_WINDOW: AccountPoolQuotaWindow = "five-hour"; export const DEFAULT_ACCOUNT_POOL_STICKY_LIMIT = 1; export const MIN_ACCOUNT_POOL_STICKY_LIMIT = 1; export const MAX_ACCOUNT_POOL_STICKY_LIMIT = 100; const STRATEGY_SET = new Set(ACCOUNT_POOL_STRATEGIES); +const QUOTA_WINDOW_SET = new Set(ACCOUNT_POOL_QUOTA_WINDOWS); export function normalizeAccountPoolStrategy(value: unknown): AccountPoolStrategy { return typeof value === "string" && STRATEGY_SET.has(value) @@ -19,6 +30,12 @@ export function normalizeAccountPoolStrategy(value: unknown): AccountPoolStrateg : DEFAULT_ACCOUNT_POOL_STRATEGY; } +export function normalizeAccountPoolQuotaWindow(value: unknown): AccountPoolQuotaWindow { + return typeof value === "string" && QUOTA_WINDOW_SET.has(value) + ? value as AccountPoolQuotaWindow + : DEFAULT_ACCOUNT_POOL_QUOTA_WINDOW; +} + export function normalizeAccountPoolStickyLimit(value: unknown): number { return typeof value === "number" && Number.isInteger(value) diff --git a/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx b/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx index d0ef91fab5..d9208cf920 100644 --- a/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx +++ b/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx @@ -5,20 +5,32 @@ import { useCallback, useEffect, useState } from "react"; import { useT } from "../../i18n/shared"; import { + ACCOUNT_POOL_QUOTA_WINDOWS, + DEFAULT_ACCOUNT_POOL_QUOTA_WINDOW, DEFAULT_ACCOUNT_POOL_STICKY_LIMIT, DEFAULT_ACCOUNT_POOL_STRATEGY, + normalizeAccountPoolQuotaWindow, normalizeAccountPoolStickyLimit, normalizeAccountPoolStrategy, parseAccountPoolStickyLimitDraft, + type AccountPoolQuotaWindow, type AccountPoolStrategy, } from "../../account-pool-strategy"; import AccountPoolStrategyControls from "../AccountPoolStrategyControls"; +import { Select } from "../../ui"; + +const QUOTA_WINDOW_LABEL_KEYS = { + "five-hour": "accountPool.quotaWindowFiveHour", + weekly: "accountPool.quotaWindowWeekly", + "max-utilization": "accountPool.quotaWindowMaxUtilization", +} as const; type PoolState = { enabled: boolean; threshold: number; strategy: AccountPoolStrategy; stickyLimit: number; + quotaWindow: AccountPoolQuotaWindow; }; export default function AnthropicAccountPoolSettings({ @@ -56,6 +68,7 @@ export default function AnthropicAccountPoolSettings({ autoSwitchThreshold?: number; strategy?: unknown; stickyLimit?: unknown; + quotaWindow?: unknown; }>; }) .then(json => { @@ -67,6 +80,7 @@ export default function AnthropicAccountPoolSettings({ threshold: nextThreshold, strategy: normalizeAccountPoolStrategy(json.strategy), stickyLimit: nextSticky, + quotaWindow: normalizeAccountPoolQuotaWindow(json.quotaWindow), }); setDraft(String(nextThreshold)); setStickyDraft(String(nextSticky)); @@ -87,6 +101,7 @@ export default function AnthropicAccountPoolSettings({ threshold: number; strategy: AccountPoolStrategy; stickyLimit: number; + quotaWindow: AccountPoolQuotaWindow; }) => { const previousState = state; setState({ @@ -94,6 +109,7 @@ export default function AnthropicAccountPoolSettings({ threshold: next.threshold, strategy: next.strategy, stickyLimit: next.stickyLimit, + quotaWindow: next.quotaWindow, }); setSaving(true); setError(null); @@ -107,20 +123,24 @@ export default function AnthropicAccountPoolSettings({ autoSwitchThreshold: next.threshold, strategy: next.strategy, stickyLimit: next.stickyLimit, + quotaWindow: next.quotaWindow, }), }); if (!res.ok) throw new Error("save"); const json = await res.json().catch(() => null) as { strategy?: unknown; stickyLimit?: unknown; + quotaWindow?: unknown; } | null; const savedStrategy = normalizeAccountPoolStrategy(json?.strategy ?? next.strategy); const savedSticky = normalizeAccountPoolStickyLimit(json?.stickyLimit ?? next.stickyLimit); + const savedWindow = normalizeAccountPoolQuotaWindow(json?.quotaWindow ?? next.quotaWindow); setState({ enabled: next.enabled, threshold: next.threshold, strategy: savedStrategy, stickyLimit: savedSticky, + quotaWindow: savedWindow, }); setDraft(String(next.threshold)); setStickyDraft(String(savedSticky)); @@ -140,6 +160,10 @@ export default function AnthropicAccountPoolSettings({ const threshold = state?.threshold ?? 80; const strategy = state?.strategy ?? DEFAULT_ACCOUNT_POOL_STRATEGY; const stickyLimit = state?.stickyLimit ?? DEFAULT_ACCOUNT_POOL_STICKY_LIMIT; + const quotaWindow = state?.quotaWindow ?? DEFAULT_ACCOUNT_POOL_QUOTA_WINDOW; + // Only quota scores a usage bar; fill-first scores one too, but a 0 threshold turns its + // drain point off. Neither reads a bar under round-robin, so the window is inert there. + const quotaWindowInert = strategy !== "quota" && !(strategy === "fill-first" && threshold > 0); const loading = state === null && !loadError; // Always allow turning the pool off; only block enabling when fewer than 2 accounts. const toggleDisabled = loading || saving || loadError || (!enabled && accountCount < 2); @@ -155,7 +179,10 @@ export default function AnthropicAccountPoolSettings({ : loading ? t("common.loading") : enabled - ? t("anthropicPool.enabledDesc", { threshold }) + ? t("anthropicPool.enabledDesc", { + threshold, + window: t(QUOTA_WINDOW_LABEL_KEYS[quotaWindow]), + }) : t("anthropicPool.disabledDesc")} @@ -172,6 +199,7 @@ export default function AnthropicAccountPoolSettings({ threshold, strategy, stickyLimit, + quotaWindow, }); }} > @@ -214,6 +242,7 @@ export default function AnthropicAccountPoolSettings({ threshold: parsed, strategy, stickyLimit, + quotaWindow, }); } }} @@ -234,6 +263,7 @@ export default function AnthropicAccountPoolSettings({ threshold, strategy: next, stickyLimit, + quotaWindow, }); }} onStickyDraftChange={setStickyDraft} @@ -253,9 +283,39 @@ export default function AnthropicAccountPoolSettings({ threshold, strategy, stickyLimit: parsed, + quotaWindow, }); }} /> + + + )} diff --git a/gui/src/styles.css b/gui/src/styles.css index 0ef2053fac..a22eb013ef 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -1810,6 +1810,7 @@ dialog.modal-overlay::backdrop { margin-top: 12px; padding: 0 16px; } +.anthropic-pool-card__field--quota-window { padding-bottom: 16px; } .api-active-keys-skeleton { min-height: 96px; border: 1px solid var(--border-soft); diff --git a/gui/tests/anthropic-pool-quota-window.test.tsx b/gui/tests/anthropic-pool-quota-window.test.tsx index 6892b13eee..289463dc42 100644 --- a/gui/tests/anthropic-pool-quota-window.test.tsx +++ b/gui/tests/anthropic-pool-quota-window.test.tsx @@ -176,6 +176,27 @@ describe("Anthropic account pool quota window", () => { expect(drainedHost.textContent).toContain("scores a usage bar"); }); + test("quota window help text does not open the selector", async () => { + stubPool({ + enabled: true, + autoSwitchThreshold: 80, + strategy: "quota", + stickyLimit: 1, + quotaWindow: "five-hour", + }); + const host = await mountPool(); + const field = windowTrigger(host).closest(".anthropic-pool-card__field"); + const help = field?.querySelector(".card-sub"); + if (!help) throw new Error("quota window help missing"); + + await act(async () => { + help.click(); + await flush(); + }); + + expect(testWindow.document.querySelector('[role="listbox"]')).toBeNull(); + }); + test("save sends quotaWindow in the PUT body", async () => { const puts = stubPool({ enabled: true, From a5844385409578450bf2916fc7fef0aebe731070 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 21:24:12 +0900 Subject: [PATCH 08/24] fix(gui): use spacing token for quota window padding --- gui/src/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/src/styles.css b/gui/src/styles.css index a22eb013ef..4bd3568f70 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -1810,7 +1810,7 @@ dialog.modal-overlay::backdrop { margin-top: 12px; padding: 0 16px; } -.anthropic-pool-card__field--quota-window { padding-bottom: 16px; } +.anthropic-pool-card__field--quota-window { padding-bottom: var(--space-4); } .api-active-keys-skeleton { min-height: 96px; border: 1px solid var(--border-soft); From 77cb03b8694ecc79fa72178eb79ee1596c5fa4e8 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 21:24:12 +0900 Subject: [PATCH 09/24] docs(anthropic): complete quota window behavior across locales --- .../docs/fr/reference/configuration/providers.md | 3 ++- docs-site/src/content/docs/guides/claude-code.md | 10 ++++++---- .../docs/ja/reference/configuration/providers.md | 3 ++- .../docs/ko/reference/configuration/providers.md | 3 ++- .../content/docs/reference/configuration/providers.md | 2 +- .../docs/ru/reference/configuration/providers.md | 3 ++- .../docs/tr/reference/configuration/providers.md | 4 ++-- .../docs/zh-cn/reference/configuration/providers.md | 3 ++- .../docs/zh-tw/reference/configuration/providers.md | 3 ++- 9 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 5c87224cb4..2705d561ec 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -193,8 +193,9 @@ rotation automatique peut déclencher des restrictions du fournisseur. | Clé | Type | Par défaut | Description | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, choisir la plus faible utilisation connue et mise en cache sur 5 heures qui atteint ou dépasse ce seuil. `0` désactive la sélection selon le quota. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; quota lit la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire, ignore les comptes dont la barre sur 5 heures est épuisée et départage les égalités par la plus faible utilisation sur 5 heures. `max-utilization` utilise la plus élevée des deux barres. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index e5d9c6a42a..3b95d80553 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -13,10 +13,10 @@ You can log in multiple Claude accounts via the Providers dashboard (`ocx login add-account). By default every request uses the **active** account only. An **experimental, opt-in** Claude account pool (`anthropicAccountPool.enabled`) adds sticky -session affinity and 429 cooldown failover across those OAuth accounts. For **new** sessions -only, `anthropicAccountPool.strategy` selects among eligible accounts: `quota` (default) picks -lowest known usage in the window set by `quotaWindow` (default the 5-hour bar) when above -`autoSwitchThreshold`; `round-robin` spreads evenly +session affinity and 429 cooldown failover across those OAuth accounts. For **new** sessions, +`anthropicAccountPool.strategy` selects among eligible accounts: `quota` (default) picks the +lowest known usage in the window set by `quotaWindow` (`five-hour` by default, or `weekly` / +`max-utilization`) when above `autoSwitchThreshold`; `round-robin` spreads evenly (`stickyLimit`, default `1`); `fill-first` drains the active account until cooldown, reauthentication, or threshold, then advances. It is **off by default**, shows a GUI warning, and is not battle-tested — Anthropic may restrict accounts that look like automated rotation; @@ -32,6 +32,8 @@ Operational contract when enabled: selection until re-authenticated. - If every eligible account is cooling, the proxy returns **429** (not 401) with `Retry-After` when known. +- Recovery, including 429 failover, uses `quotaWindow` to rank eligible replacements without + changing the existing cooldown or failover limits; `round-robin` ignores `quotaWindow`. See [Configuration](/reference/configuration/#anthropicaccountpool-experimental). diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index a504c84836..0111e262ef 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -159,8 +159,9 @@ affinity を維持します。これらの戦略は provider enforcement を回 |キー |タイプ |デフォルト |説明 | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションの場合は、このしきい値以上の、既知の最も低いキャッシュされた 5 時間の使用量を選択します。 `0` はクォータの選択を無効にします。 | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。quota は `quotaWindow` で指定した期間を参照し、既定は 5 時間足です。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使いますが、5 時間使用量が上限に達したアカウントを除外し、週次使用量が同じ場合は 5 時間使用量が低い方を優先します。`max-utilization` は 2 つの値のうち高い方を使います。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 444247f13d..6a73a306e5 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -163,8 +163,9 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | 키 | 타입 | 기본값 | 설명 | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 이 임계값 이상에서 알려진 캐시 5시간 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. quota는 `quotaWindow`로 지정한 창을 읽으며, 기본값은 5시간 막대입니다. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하되 5시간 막대가 소진된 계정을 건너뛰고, 주간 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index bdeac425a4..0c0e40994c 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -285,7 +285,7 @@ rotation may trigger provider restrictions. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, choose the lowest known cached usage in the configured window at or above this threshold. `0` disables quota picking. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; quota reads the window set by `quotaWindow`. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar the `quota` strategy scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted, and breaks weekly ties by lower 5-hour usage. `max-utilization` scores whichever of the two bars is higher. This affects new-session picking only, not affinity re-evaluation, and per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted, and breaks weekly ties by lower 5-hour usage. `max-utilization` scores whichever of the two bars is higher. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index de431a7b2f..9167fefc54 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -194,8 +194,9 @@ reauth или порога исчерпания; здоровые привяза | Ключ | Тип | По умолчанию | Описание | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached 5-hour usage, если активный аккаунт достиг порога. `0` отключает выбор по quota. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; quota смотрит на окно, заданное в `quotaWindow`; по умолчанию это 5-hour bar'ы. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar, но пропускает аккаунты с исчерпанным 5-hour bar и при равенстве недельного usage выбирает меньший 5-hour usage. `max-utilization` использует большее из двух значений. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index aed5d44754..90d880a56d 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -219,8 +219,9 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | Anahtar | Tip | Varsayılan | Açıklama | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlar için bu eşikte veya üzerinde bilinen en düşük önbelleğe alınmış 5 saatlik kullanımı seçin. `0` kota seçimini devre dışı bırakır. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; kota, `quotaWindow` ile belirlenen pencereyi okur; varsayılan 5 saatlik çubuklardır. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır, ancak 5 saatlik çubuğu tükenmiş hesapları atlar ve haftalık eşitliklerde daha düşük 5 saatlik kullanımı tercih eder. `max-utilization` iki çubuktan yüksek olanı kullanır. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden @@ -479,4 +480,3 @@ bildirir; senkronize edilen katalog `xhigh`'ı ayrı tutarken `max` bildirir. "visionSidecar": { "enabled": true } } ``` - diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index a25ee2eeca..f1d6ade2d4 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -157,8 +157,9 @@ affinity。这些策略不能规避 provider enforcement。 | 键 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,选择已知缓存的、5 小时使用率最低且达到或超过此阈值的账户。`0` 会禁用配额选择。 | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;quota 读取 `quotaWindow` 指定的窗口,默认是 5 小时条形数据。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,但仍会跳过 5 小时用量已耗尽的账户;每周用量相同时,优先选择 5 小时用量较低的账户。`max-utilization` 使用两条用量中较高的值。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index 47747e4cb2..ebc7fb3d31 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -126,8 +126,9 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | Key | 型別 | 預設值 | 說明 | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,選擇在此閾值或以上的最低已知快取 5 小時用量。`0` 停用量量挑選。 | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;quota 讀取 `quotaWindow` 指定的視窗,預設為 5 小時列。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,但仍會略過 5 小時用量已耗盡的帳號;每週用量相同時,優先選擇 5 小時用量較低的帳號。`max-utilization` 使用兩個用量中較高的值。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 From 16ee2958c4415ea263f59348fed9ffc33e6e0022 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 21:32:24 +0900 Subject: [PATCH 10/24] test(gui): decouple quota window checks from copy --- gui/tests/anthropic-pool-quota-window.test.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/gui/tests/anthropic-pool-quota-window.test.tsx b/gui/tests/anthropic-pool-quota-window.test.tsx index 289463dc42..36dc66e5b7 100644 --- a/gui/tests/anthropic-pool-quota-window.test.tsx +++ b/gui/tests/anthropic-pool-quota-window.test.tsx @@ -131,10 +131,6 @@ describe("Anthropic account pool quota window", () => { const quotaHost = await mountPool(); expect(windowTrigger(quotaHost).disabled).toBe(false); - expect(windowTrigger(quotaHost).textContent).toContain("Weekly bar"); - expect(quotaHost.textContent).toContain("Quota window"); - // The hint replaces the inert notice whenever the setting actually scores a bar. - expect(quotaHost.textContent).toContain("breaks weekly ties by lower 5-hour usage"); stubPool({ enabled: true, @@ -146,7 +142,6 @@ describe("Anthropic account pool quota window", () => { const fillHost = await mountPool(); expect(windowTrigger(fillHost).disabled).toBe(false); - expect(windowTrigger(fillHost).textContent).toContain("5-hour bar"); }); test("quota window selector is disabled for round-robin and for fill-first with threshold 0", async () => { @@ -160,7 +155,6 @@ describe("Anthropic account pool quota window", () => { const rrHost = await mountPool(); expect(windowTrigger(rrHost).disabled).toBe(true); - expect(rrHost.textContent).toContain("scores a usage bar"); stubPool({ enabled: true, @@ -173,7 +167,6 @@ describe("Anthropic account pool quota window", () => { // fill-first only drains against a threshold; at 0 there is no bar to score. expect(windowTrigger(drainedHost).disabled).toBe(true); - expect(drainedHost.textContent).toContain("scores a usage bar"); }); test("quota window help text does not open the selector", async () => { @@ -211,8 +204,9 @@ describe("Anthropic account pool quota window", () => { windowTrigger(host).click(); await flush(); }); - const option = Array.from(testWindow.document.querySelectorAll('[role="option"]')) - .find((el) => (el.textContent ?? "").includes("Weekly bar")); + const option = testWindow.document.querySelector( + '[role="option"]:not([aria-selected="true"])', + ) as unknown as HTMLButtonElement | null; if (!option) throw new Error("weekly option missing"); await act(async () => { option.click(); @@ -228,6 +222,5 @@ describe("Anthropic account pool quota window", () => { stickyLimit: 1, quotaWindow: "weekly", }); - expect(windowTrigger(host).textContent).toContain("Weekly bar"); }); }); From 36a970c9230751e02e7122995cbb713279248067 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 21:32:24 +0900 Subject: [PATCH 11/24] docs(anthropic): align localized quota window guides --- docs-site/src/content/docs/fr/guides/claude-code.md | 3 ++- docs-site/src/content/docs/tr/guides/claude-code.md | 4 ++-- docs-site/src/content/docs/zh-tw/guides/claude-code.md | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs-site/src/content/docs/fr/guides/claude-code.md b/docs-site/src/content/docs/fr/guides/claude-code.md index 8a5ccf79b2..baddc6b355 100644 --- a/docs-site/src/content/docs/fr/guides/claude-code.md +++ b/docs-site/src/content/docs/fr/guides/claude-code.md @@ -15,7 +15,8 @@ ajouter un compte). Par défaut, chaque requête utilise uniquement le compte ** Un groupe de comptes Claude **expérimental et facultatif** (`anthropicAccountPool.enabled`) ajoute l'affinité de session et le basculement en cas de délai de récupération 429 entre ces comptes OAuth. Pour les **nouvelles** sessions uniquement, `anthropicAccountPool.strategy` sélectionne un compte éligible : `quota` (par défaut) -choisit la plus faible utilisation connue sur 5 heures lorsqu'elle dépasse `autoSwitchThreshold` ; `round-robin` +choisit la plus faible utilisation connue dans la fenêtre configurée par `anthropicAccountPool.quotaWindow` +(`five-hour` par défaut, `weekly` ou `max-utilization`) lorsqu'elle dépasse `autoSwitchThreshold` ; `round-robin` répartit les sessions uniformément (`stickyLimit`, `1` par défaut) ; `fill-first` utilise le compte actif jusqu'à un délai de récupération, une réauthentification ou le seuil, puis passe au suivant. Cette fonction est **désactivée par défaut**, affiche un avertissement dans l'interface et n'a pas été éprouvée en production. diff --git a/docs-site/src/content/docs/tr/guides/claude-code.md b/docs-site/src/content/docs/tr/guides/claude-code.md index e335bea33e..42a65eb915 100644 --- a/docs-site/src/content/docs/tr/guides/claude-code.md +++ b/docs-site/src/content/docs/tr/guides/claude-code.md @@ -19,7 +19,8 @@ fazla Claude hesabına giriş yapabilirsiniz. Varsayılan olarak her istek yaln bağlılığı ve 429 bekleme süresi (cooldown) yük devretmesi ekler. Yalnızca **yeni** oturumlar için `anthropicAccountPool.strategy` uygun hesaplar arasından seçim yapar: `quota` (varsayılan), `autoSwitchThreshold` üzerinde olduğunda -bilinen en düşük 5 saatlik kullanımı seçer; `round-robin` eşit olarak dağıtır +`anthropicAccountPool.quotaWindow` ile yapılandırılan penceredeki bilinen en düşük kullanımı +seçer (`five-hour` varsayılandır; `weekly` ve `max-utilization` da kullanılabilir); `round-robin` eşit olarak dağıtır (`stickyLimit`, varsayılan `1`); `fill-first`, bekleme süresi, yeniden kimlik doğrulama veya eşiğe kadar aktif hesabı tüketir, ardından ilerler. **Varsayılan olarak kapalıdır**, bir GUI uyarısı gösterir ve sahada kapsamlı olarak test @@ -607,4 +608,3 @@ modellerde opencodex varsayılan olarak bunu taslakla değiştirir (`blockedSkil aracının `model` argümanını değil, `` yönergelerini kullanır. Yönergenin hedeflenen rotayla eşleştiğinden emin olun. Model yer tutucusu olarak `"haiku"` iletin. - diff --git a/docs-site/src/content/docs/zh-tw/guides/claude-code.md b/docs-site/src/content/docs/zh-tw/guides/claude-code.md index be06746fba..bf98b5fb40 100644 --- a/docs-site/src/content/docs/zh-tw/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-tw/guides/claude-code.md @@ -14,7 +14,9 @@ Code 可以使用每一個已路由的供應商——包括 OAuth 登入、帳 **實驗性、opt-in** 的 Claude 帳號池(`anthropicAccountPool.enabled`)會在這些 OAuth 帳號之間加入 sticky session affinity 與 429 冷卻故障轉移。僅對**新**工作階段,`anthropicAccountPool.strategy` -會在合格帳號之間選擇:`quota`(預設)在用量高於 `autoSwitchThreshold` 時挑選已知 5 小時用量最低者; +會在合格帳號之間選擇:`quota`(預設)在用量高於 `autoSwitchThreshold` 時,依 +`anthropicAccountPool.quotaWindow` 所設定的視窗挑選已知用量最低者(`five-hour` 為預設,亦可選 +`weekly` 或 `max-utilization`); `round-robin` 平均分散(`stickyLimit`,預設 `1`);`fill-first` 一直使用作用中帳號直到冷卻、重新認證 或達到閾值,然後前進。它**預設關閉**、會在 GUI 顯示警告,而且尚未經過實戰驗證——Anthropic 可能 限制看起來像自動輪換的帳號;輪換並不能保護你免受供應商執行機制的處置。 From d283a7d1973581510d9bd7a23ea5287615dd019d Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 21:36:27 +0900 Subject: [PATCH 12/24] fix(anthropic): break max quota ties by five-hour usage --- src/oauth/anthropic-routing.ts | 2 +- tests/anthropic-account-pool.test.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index 44ec7d22c7..c840d60ab9 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -269,7 +269,7 @@ function pickLowestUsage(config: OcxConfig, excludeId: string | undefined, now: const scored: ScoredAccount[] = eligible.map(accountId => ({ accountId, score: usageScore(config, accountId), - fiveHourTieBreak: window === "weekly" ? fiveHourScore(accountId) : 0, + fiveHourTieBreak: window === "five-hour" ? 0 : fiveHourScore(accountId), })); let best = scored[0]!; for (let i = 1; i < scored.length; i++) { diff --git a/tests/anthropic-account-pool.test.ts b/tests/anthropic-account-pool.test.ts index c2ca5055b6..58998f682c 100644 --- a/tests/anthropic-account-pool.test.ts +++ b/tests/anthropic-account-pool.test.ts @@ -500,6 +500,15 @@ describe("anthropic account pool quota window scoring", () => { expect(resolveAnthropicAccountForSession("max-hotter", cfg(true, 80, { quotaWindow: "max-utilization" })).accountId).toBe(bId); }); + test("max-utilization tie breaks by lower five-hour usage", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 90, weeklyPercent: 20, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 40, weeklyPercent: 90, updatedAt }); + + expect(resolveAnthropicAccountForSession("max-five-hour-tie", cfg(true, 80, { quotaWindow: "max-utilization" })).accountId).toBe(bId); + }); + test("omitted quotaWindow equals explicit five-hour", async () => { const { aId, bId } = await seedTwoAccounts(); const updatedAt = Date.now(); From 43430e1cbbe8bdfbd16966415c2c582cc2647467 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 21:36:27 +0900 Subject: [PATCH 13/24] docs(anthropic): document quota tie breaking --- .../src/content/docs/fr/reference/configuration/providers.md | 2 +- .../src/content/docs/ja/reference/configuration/providers.md | 2 +- .../src/content/docs/ko/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/reference/configuration/providers.md | 2 +- .../src/content/docs/ru/reference/configuration/providers.md | 2 +- .../src/content/docs/tr/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-cn/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-tw/reference/configuration/providers.md | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 2705d561ec..6d314a5ffd 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -195,7 +195,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; quota lit la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire, ignore les comptes dont la barre sur 5 heures est épuisée et départage les égalités par la plus faible utilisation sur 5 heures. `max-utilization` utilise la plus élevée des deux barres. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 0111e262ef..1de4a4260a 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -161,7 +161,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。quota は `quotaWindow` で指定した期間を参照し、既定は 5 時間足です。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使いますが、5 時間使用量が上限に達したアカウントを除外し、週次使用量が同じ場合は 5 時間使用量が低い方を優先します。`max-utilization` は 2 つの値のうち高い方を使います。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使いますが、5 時間使用量が上限に達したアカウントを除外します。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 6a73a306e5..aea5ef7eb0 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -165,7 +165,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. quota는 `quotaWindow`로 지정한 창을 읽으며, 기본값은 5시간 막대입니다. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하되 5시간 막대가 소진된 계정을 건너뛰고, 주간 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하되 5시간 막대가 소진된 계정을 건너뜁니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 0c0e40994c..088032b20a 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -285,7 +285,7 @@ rotation may trigger provider restrictions. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, choose the lowest known cached usage in the configured window at or above this threshold. `0` disables quota picking. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; quota reads the window set by `quotaWindow`. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted, and breaks weekly ties by lower 5-hour usage. `max-utilization` scores whichever of the two bars is higher. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index 9167fefc54..dbb5be5f79 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -196,7 +196,7 @@ reauth или порога исчерпания; здоровые привяза | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; quota смотрит на окно, заданное в `quotaWindow`; по умолчанию это 5-hour bar'ы. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar, но пропускает аккаунты с исчерпанным 5-hour bar и при равенстве недельного usage выбирает меньший 5-hour usage. `max-utilization` использует большее из двух значений. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar, но пропускает аккаунты с исчерпанным 5-hour bar. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index 90d880a56d..3c389fddc8 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -221,7 +221,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; kota, `quotaWindow` ile belirlenen pencereyi okur; varsayılan 5 saatlik çubuklardır. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır, ancak 5 saatlik çubuğu tükenmiş hesapları atlar ve haftalık eşitliklerde daha düşük 5 saatlik kullanımı tercih eder. `max-utilization` iki çubuktan yüksek olanı kullanır. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır, ancak 5 saatlik çubuğu tükenmiş hesapları atlar. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index f1d6ade2d4..ab3674b425 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -159,7 +159,7 @@ affinity。这些策略不能规避 provider enforcement。 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;quota 读取 `quotaWindow` 指定的窗口,默认是 5 小时条形数据。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,但仍会跳过 5 小时用量已耗尽的账户;每周用量相同时,优先选择 5 小时用量较低的账户。`max-utilization` 使用两条用量中较高的值。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,但仍会跳过 5 小时用量已耗尽的账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index ebc7fb3d31..aad1ea733c 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -128,7 +128,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;quota 讀取 `quotaWindow` 指定的視窗,預設為 5 小時列。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,但仍會略過 5 小時用量已耗盡的帳號;每週用量相同時,優先選擇 5 小時用量較低的帳號。`max-utilization` 使用兩個用量中較高的值。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,但仍會略過 5 小時用量已耗盡的帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 From 853ac541f406ffdec202f2a102dbf4b512068060 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Tue, 25 Aug 2026 22:46:52 +0900 Subject: [PATCH 14/24] docs(anthropic): document the quota window behind fill-first and recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The provider tables said `quota` reads `quotaWindow` but left out that fill-first evaluates its drain threshold in that same window, and the English `autoSwitchThreshold` row read as if the account chosen had to be at or above the threshold — `pickLowestUsage` applies no such filter once the active account crosses it. The fr/tr/zh-tw Claude Code guides were also missing the recovery ranking bullet the English guide already carries. --- docs-site/src/content/docs/fr/guides/claude-code.md | 3 +++ .../src/content/docs/fr/reference/configuration/providers.md | 2 +- .../src/content/docs/ja/reference/configuration/providers.md | 2 +- .../src/content/docs/ko/reference/configuration/providers.md | 2 +- .../src/content/docs/reference/configuration/providers.md | 4 ++-- .../src/content/docs/ru/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/tr/guides/claude-code.md | 3 +++ .../src/content/docs/tr/reference/configuration/providers.md | 2 +- .../content/docs/zh-cn/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/zh-tw/guides/claude-code.md | 2 ++ .../content/docs/zh-tw/reference/configuration/providers.md | 2 +- 11 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs-site/src/content/docs/fr/guides/claude-code.md b/docs-site/src/content/docs/fr/guides/claude-code.md index baddc6b355..671c22c233 100644 --- a/docs-site/src/content/docs/fr/guides/claude-code.md +++ b/docs-site/src/content/docs/fr/guides/claude-code.md @@ -32,6 +32,9 @@ Comportement lorsque cette option est activée : sélection jusqu'à sa réauthentification. - Si chaque compte éligible est en temporisation, le proxy renvoie **429** (et non 401) avec `Retry-After` lorsqu'il est connu. +- La récupération, y compris le basculement 429, utilise `quotaWindow` pour classer les comptes de + remplacement admissibles, sans modifier les limites existantes de temporisation ou de basculement ; + `round-robin` ignore `quotaWindow`. Voir [Configuration](/fr/reference/configuration/providers/#anthropicaccountpool-expérimental). diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 6d314a5ffd..067511b9b2 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -194,7 +194,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; quota lit la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; `quota` classe les comptes selon la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures, et `fill-first` évalue son seuil d'évacuation dans cette même fenêtre. | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 1de4a4260a..7907d0f0cf 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -160,7 +160,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。quota は `quotaWindow` で指定した期間を参照し、既定は 5 時間足です。 | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。`quota` は `quotaWindow` で指定した期間(既定は 5 時間足)でアカウントを順位付けし、`fill-first` も同じ期間で使い切りのしきい値を判定します。 | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使いますが、5 時間使用量が上限に達したアカウントを除外します。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index aea5ef7eb0..17dafd5606 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -164,7 +164,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. quota는 `quotaWindow`로 지정한 창을 읽으며, 기본값은 5시간 막대입니다. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. `quota`는 `quotaWindow`로 지정한 창(기본값은 5시간 막대)으로 계정 순위를 매기고, `fill-first`도 같은 창에서 소진 임계값을 판정합니다. | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하되 5시간 막대가 소진된 계정을 건너뜁니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 088032b20a..0f295b56e6 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -283,8 +283,8 @@ rotation may trigger provider restrictions. | Key | Type | Default | Description | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, choose the lowest known cached usage in the configured window at or above this threshold. `0` disables quota picking. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; quota reads the window set by `quotaWindow`. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables quota picking. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; `quota` ranks accounts by the window set by `quotaWindow`, and `fill-first` evaluates its drain threshold in that same window. | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index dbb5be5f79..224df22002 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -195,7 +195,7 @@ reauth или порога исчерпания; здоровые привяза | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; quota смотрит на окно, заданное в `quotaWindow`; по умолчанию это 5-hour bar'ы. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; `quota` ранжирует аккаунты по окну, заданному в `quotaWindow` (по умолчанию это 5-hour bar'ы), а `fill-first` в этом же окне оценивает свой порог исчерпания. | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar, но пропускает аккаунты с исчерпанным 5-hour bar. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | diff --git a/docs-site/src/content/docs/tr/guides/claude-code.md b/docs-site/src/content/docs/tr/guides/claude-code.md index 42a65eb915..1fe4fd0393 100644 --- a/docs-site/src/content/docs/tr/guides/claude-code.md +++ b/docs-site/src/content/docs/tr/guides/claude-code.md @@ -38,6 +38,9 @@ Etkinleştirildiğinde operasyonel sözleşme: böylece yeniden kimlik doğrulanana kadar seçimden hariç tutulur. - Uygun tüm hesaplar soğutuluyorsa, proxy bilindiğinde `Retry-After` ile birlikte **429** (401 değil) döndürür. +- 429 yük devretmesi dahil kurtarma, mevcut soğuma ve yük devretme sınırlarını + değiştirmeden uygun yedek hesapları sıralamak için `quotaWindow` kullanır; + `round-robin` ise `quotaWindow` ayarını yok sayar. Bkz. [Yapılandırma](/tr/reference/configuration/#anthropicaccountpool-experimental). diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index 3c389fddc8..034bcba387 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -220,7 +220,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; kota, `quotaWindow` ile belirlenen pencereyi okur; varsayılan 5 saatlik çubuklardır. | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; `quota`, `quotaWindow` ile belirlenen pencereye (varsayılan 5 saatlik çubuklar) göre hesapları sıralar ve `fill-first` de tükenme eşiğini aynı pencerede değerlendirir. | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır, ancak 5 saatlik çubuğu tükenmiş hesapları atlar. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index ab3674b425..6a69e16788 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -158,7 +158,7 @@ affinity。这些策略不能规避 provider enforcement。 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;quota 读取 `quotaWindow` 指定的窗口,默认是 5 小时条形数据。 | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;`quota` 按 `quotaWindow` 指定的窗口(默认是 5 小时条形数据)对账户排序,`fill-first` 也在同一窗口中判定其耗尽阈值。 | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,但仍会跳过 5 小时用量已耗尽的账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | diff --git a/docs-site/src/content/docs/zh-tw/guides/claude-code.md b/docs-site/src/content/docs/zh-tw/guides/claude-code.md index bf98b5fb40..2663cb3647 100644 --- a/docs-site/src/content/docs/zh-tw/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-tw/guides/claude-code.md @@ -28,6 +28,8 @@ sticky session affinity 與 429 冷卻故障轉移。僅對**新**工作階段 - Affinity 是**程序本機**的(proxy 重啟後就會遺失)。 - **401/403** 憑證失敗會隔離該帳號(`needsReauth`),直到重新認證前都不會參與選擇。 - 如果每個合格帳號都在冷卻,proxy 會回傳 **429**(不是 401),並在已知時附上 `Retry-After`。 +- 復原(包括 429 容錯移轉)會使用 `quotaWindow` 為合格的替代帳號排序,且不改變現有的冷卻或 + 容錯移轉上限;`round-robin` 會忽略 `quotaWindow`。 請見 [Configuration](/zh-tw/reference/configuration/#anthropicaccountpool-experimental)。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index aad1ea733c..7f68ca3874 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -127,7 +127,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | -| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;quota 讀取 `quotaWindow` 指定的視窗,預設為 5 小時列。 | +| `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;`quota` 依 `quotaWindow` 指定的視窗(預設為 5 小時列)為帳號排序,`fill-first` 也在同一視窗中判定其排空閾值。 | | `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,但仍會略過 5 小時用量已耗盡的帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | From fa4a8cc9222915a6905c9f6306b7791a584aa7a9 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Sat, 29 Aug 2026 21:20:23 +0900 Subject: [PATCH 15/24] fix(anthropic): align weekly fallback and quota copy --- .../docs/fr/reference/configuration/providers.md | 2 +- .../docs/ja/reference/configuration/providers.md | 2 +- .../docs/ko/reference/configuration/providers.md | 2 +- .../docs/reference/configuration/providers.md | 2 +- .../docs/ru/reference/configuration/providers.md | 2 +- .../docs/tr/reference/configuration/providers.md | 2 +- .../zh-cn/reference/configuration/providers.md | 2 +- .../zh-tw/reference/configuration/providers.md | 2 +- .../AnthropicAccountPoolSettings.tsx | 10 ++++++---- gui/src/i18n/de.ts | 5 +++-- gui/src/i18n/en.ts | 5 +++-- gui/src/i18n/fr.ts | 5 +++-- gui/src/i18n/ja.ts | 5 +++-- gui/src/i18n/ko.ts | 5 +++-- gui/src/i18n/ru.ts | 5 +++-- gui/src/i18n/tr.ts | 5 +++-- gui/src/i18n/zh-TW.ts | 5 +++-- gui/src/i18n/zh.ts | 5 +++-- gui/tests/anthropic-pool-quota-window.test.tsx | 14 ++++++++++++++ src/oauth/anthropic-routing.ts | 9 ++++++--- tests/anthropic-account-pool.test.ts | 14 ++++++++++++++ 21 files changed, 75 insertions(+), 33 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 067511b9b2..9b8a656e0c 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -195,7 +195,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; `quota` classe les comptes selon la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures, et `fill-first` évalue son seuil d'évacuation dans cette même fenêtre. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 7907d0f0cf..6a7f6e26c6 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -161,7 +161,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。`quota` は `quotaWindow` で指定した期間(既定は 5 時間足)でアカウントを順位付けし、`fill-first` も同じ期間で使い切りのしきい値を判定します。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使いますが、5 時間使用量が上限に達したアカウントを除外します。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 17dafd5606..35c07ae005 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -165,7 +165,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. `quota`는 `quotaWindow`로 지정한 창(기본값은 5시간 막대)으로 계정 순위를 매기고, `fill-first`도 같은 창에서 소진 임계값을 판정합니다. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하되 5시간 막대가 소진된 계정을 건너뜁니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 0f295b56e6..b9de867853 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -285,7 +285,7 @@ rotation may trigger provider restrictions. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables quota picking. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; `quota` ranks accounts by the window set by `quotaWindow`, and `fill-first` evaluates its drain threshold in that same window. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar but still skips accounts whose 5-hour bar is exhausted. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index 224df22002..efa93d62dc 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -196,7 +196,7 @@ reauth или порога исчерпания; здоровые привяза | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; `quota` ранжирует аккаунты по окну, заданному в `quotaWindow` (по умолчанию это 5-hour bar'ы), а `fill-first` в этом же окне оценивает свой порог исчерпания. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar, но пропускает аккаунты с исчерпанным 5-hour bar. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index 034bcba387..2a470e0245 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -221,7 +221,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; `quota`, `quotaWindow` ile belirlenen pencereye (varsayılan 5 saatlik çubuklar) göre hesapları sıralar ve `fill-first` de tükenme eşiğini aynı pencerede değerlendirir. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır, ancak 5 saatlik çubuğu tükenmiş hesapları atlar. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index 6a69e16788..168c2a6acb 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -159,7 +159,7 @@ affinity。这些策略不能规避 provider enforcement。 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;`quota` 按 `quotaWindow` 指定的窗口(默认是 5 小时条形数据)对账户排序,`fill-first` 也在同一窗口中判定其耗尽阈值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,但仍会跳过 5 小时用量已耗尽的账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index 7f68ca3874..bcf7a99082 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -128,7 +128,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;`quota` 依 `quotaWindow` 指定的視窗(預設為 5 小時列)為帳號排序,`fill-first` 也在同一視窗中判定其排空閾值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,但仍會略過 5 小時用量已耗盡的帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已耗盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 diff --git a/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx b/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx index f7f3e8df00..699331c805 100644 --- a/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx +++ b/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx @@ -179,10 +179,12 @@ export default function AnthropicAccountPoolSettings({ : loading ? t("common.loading") : enabled - ? t("anthropicPool.enabledDesc", { - threshold, - window: t(QUOTA_WINDOW_LABEL_KEYS[quotaWindow]), - }) + ? threshold === 0 + ? t("anthropicPool.enabledNoQuotaDesc") + : t("anthropicPool.enabledDesc", { + threshold, + window: t(QUOTA_WINDOW_LABEL_KEYS[quotaWindow]), + }) : t("anthropicPool.disabledDesc")} diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 334787d7f1..180c1a33a1 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1230,6 +1230,7 @@ export const de: Record = { "codexAuth.catalogRefreshPending": "Die Änderung wurde gespeichert, aber die Aktualisierung des Codex-Modellkatalogs steht noch aus. Führe ocx sync aus, um es erneut zu versuchen.", "anthropicPool.title": "Claude-Kontenpool (experimentell)", "anthropicPool.enabledDesc": "Bei 429 wird das Konto gekühlt und umgeschaltet. Neue Sitzungen bevorzugen Nutzung unter {threshold}% ({window}).", + "anthropicPool.enabledNoQuotaDesc": "Bei 429 wird das Konto gekühlt und umgeschaltet. Die kontingentbasierte Auswahl neuer Sitzungen ist deaktiviert; gesunde Affinitäten und das aktive Konto bleiben bestehen.", "anthropicPool.disabledDesc": "Nutzt nur das aktive Claude-Konto. Nur aktivieren, wenn experimentelles Routing akzeptabel ist.", "anthropicPool.experimentalWarning": "Experimentell und nicht kampferprobt. Anthropic kann Konten einschränken, die wie automatische Multi-Konto-Rotation wirken. Dieselbe Organisation kann Kontingent teilen — Pooling hilft dann nicht. Ausgeschaltet lassen, sofern das Risiko unklar ist.", "anthropicPool.needTwoAccounts": "Füge mindestens zwei Claude-OAuth-Konten hinzu, bevor du den Pool aktivierst.", @@ -1261,11 +1262,11 @@ export const de: Record = { "accountPool.strategyUpdateFailed": "Rotationsstrategie konnte nicht gespeichert werden.", "accountPool.quotaWindow": "Kontingentfenster", - "accountPool.quotaWindowDesc": "Welchen zwischengespeicherten Nutzungsbalken die Kontingentstrategie bewertet, wenn sie ein Konto für eine neue Sitzung auswählt.", + "accountPool.quotaWindowDesc": "Welcher zwischengespeicherte Nutzungsbalken die kontingentbasierte Auswahl neuer Sitzungen, Fill-first-Schwellenprüfungen und geeignete 429-Ersatzkonten steuert.", "accountPool.quotaWindowFiveHour": "5-Stunden-Balken", "accountPool.quotaWindowWeekly": "Wochenbalken", "accountPool.quotaWindowMaxUtilization": "Höherer Balken", - "accountPool.quotaWindowHint": "Der Wochenbalken überspringt weiterhin Konten, deren 5-Stunden-Balken erschöpft ist, und löst Gleichstände über die geringere 5-Stunden-Nutzung auf; die Wochenbalken einzelner Konten sind erst bekannt, sobald die Anbieterseite sie abgefragt hat.", + "accountPool.quotaWindowHint": "Der Wochenbalken überspringt Konten mit erschöpftem 5-Stunden-Balken, solange ein anderes geeignetes Konto verbleibt, greift aber auf sie zurück, wenn keines verbleibt. Gleichstände bevorzugen die geringere 5-Stunden-Nutzung; einzelne Wochenbalken sind erst nach der Abfrage auf der Anbieterseite bekannt.", "accountPool.quotaWindowInert": "Nur Kontingent — oder Fill-first mit einem Schwellenwert über 0 — bewertet einen Nutzungsbalken; für die aktuelle Rotationsstrategie ändert diese Einstellung daher nichts.", "accountPool.priority": "Auswahlreihenfolge", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 451f007693..1315a52a75 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1725,6 +1725,7 @@ export const en = { "anthropicPool.title": "Claude account pool (experimental)", "anthropicPool.enabledDesc": "On 429, cools the account and fails over. New sessions prefer usage under {threshold}% ({window}).", + "anthropicPool.enabledNoQuotaDesc": "On 429, cools the account and fails over. Quota-based new-session selection is off, so healthy affinity and active-account routing stay in place.", "anthropicPool.disabledDesc": "Uses only the active Claude account. Enable only if you accept experimental routing.", "anthropicPool.experimentalWarning": "Experimental and not battle-tested. Anthropic may restrict accounts that look like automated multi-account rotation. Same organization can share quota — pooling those accounts will not help. Keep this off unless you understand the risk.", "anthropicPool.needTwoAccounts": "Add at least two Claude OAuth accounts before enabling the pool.", @@ -1759,11 +1760,11 @@ export const en = { // anthropicPool.enabledDesc, so each locale owns its own inline casing instead of the // call site lowercasing a translated string (which breaks for Turkish and CJK). "accountPool.quotaWindow": "Quota window", - "accountPool.quotaWindowDesc": "Which cached usage bar the quota strategy scores when it picks an account for a new session.", + "accountPool.quotaWindowDesc": "Which cached usage bar controls quota-based new-session selection, fill-first threshold checks, and eligible 429 replacements.", "accountPool.quotaWindowFiveHour": "5-hour bar", "accountPool.quotaWindowWeekly": "Weekly bar", "accountPool.quotaWindowMaxUtilization": "Higher bar", - "accountPool.quotaWindowHint": "Weekly still skips accounts whose 5-hour bar is exhausted, and breaks weekly ties by lower 5-hour usage; per-account weekly bars are only known once the Providers page has polled them.", + "accountPool.quotaWindowHint": "Weekly skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to them when none do. Weekly ties prefer lower 5-hour usage; per-account weekly bars are only known once the Providers page has polled them.", "accountPool.quotaWindowInert": "Only quota — or fill-first above a 0 threshold — scores a usage bar, so this setting changes nothing for the current rotation strategy.", // Selection order. User-visible copy stays in sequence words (first/earlier/later/last); diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index a67a4c1e2a..355bdf9676 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1697,6 +1697,7 @@ export const fr: Record = { "codexAuth.catalogRefreshPending": "La modification a été enregistrée, mais l’actualisation du catalogue de modèles Codex est en attente. Exécutez ocx sync pour réessayer.", "anthropicPool.title": "Groupe de comptes Claude (expérimental)", "anthropicPool.enabledDesc": "En cas de 429, met le compte en délai de récupération et bascule vers un autre. Les nouvelles sessions privilégient une utilisation inférieure à {threshold}% ({window}).", + "anthropicPool.enabledNoQuotaDesc": "En cas de 429, met le compte en délai de récupération et bascule vers un autre. La sélection des nouvelles sessions par quota est désactivée ; les affinités saines et le compte actif sont conservés.", "anthropicPool.disabledDesc": "Utilise uniquement le compte Claude actif. Activez cette option seulement si vous acceptez le routage expérimental.", "anthropicPool.experimentalWarning": "Fonctionnalité expérimentale et peu éprouvée. Anthropic peut restreindre les comptes présentant une rotation multicomptes automatisée. Les comptes d’une même organisation peuvent partager un quota — leur mise en groupe n’apportera rien. Laissez cette option désactivée si vous n’en comprenez pas les risques.", "anthropicPool.needTwoAccounts": "Ajoutez au moins deux comptes OAuth Claude avant d’activer le groupe.", @@ -1726,11 +1727,11 @@ export const fr: Record = { "accountPool.strategyLoadFailed": "Impossible de charger la stratégie de rotation.", "accountPool.strategyUpdateFailed": "Impossible d’enregistrer la stratégie de rotation.", "accountPool.quotaWindow": "Fenêtre de quota", - "accountPool.quotaWindowDesc": "Barre d’utilisation en cache évaluée par la stratégie Quota lorsqu’elle choisit un compte pour une nouvelle session.", + "accountPool.quotaWindowDesc": "Barre d’utilisation en cache qui régit la sélection des nouvelles sessions par quota, les seuils de remplissage prioritaire et les remplacements 429 admissibles.", "accountPool.quotaWindowFiveHour": "Barre de 5 heures", "accountPool.quotaWindowWeekly": "Barre hebdomadaire", "accountPool.quotaWindowMaxUtilization": "Barre la plus haute", - "accountPool.quotaWindowHint": "La barre hebdomadaire ignore toujours les comptes dont la barre de 5 heures est épuisée et départage les égalités hebdomadaires par la plus faible utilisation sur 5 heures ; les barres hebdomadaires de chaque compte ne sont connues qu’une fois la page Fournisseurs interrogée.", + "accountPool.quotaWindowHint": "La barre hebdomadaire ignore les comptes dont la barre de 5 heures est épuisée tant qu’un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. Les égalités privilégient la plus faible utilisation sur 5 heures ; les barres hebdomadaires ne sont connues qu’après interrogation de la page Fournisseurs.", "accountPool.quotaWindowInert": "Seule la stratégie Quota — ou le remplissage prioritaire avec un seuil supérieur à 0 — évalue une barre d’utilisation ; ce réglage ne change donc rien pour la stratégie de rotation actuelle.", "accountPool.priority": "Ordre de sélection", "accountPool.priorityAria": "Ordre de sélection de ce compte", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index ec07b55add..eff0ab1674 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1658,6 +1658,7 @@ export const ja: Record = { "codexAuth.catalogRefreshPending": "変更は保存されましたが、Codex モデルカタログの更新が保留中です。ocx sync を実行して再試行してください。", "anthropicPool.title": "Claude アカウントプール(実験的)", "anthropicPool.enabledDesc": "429 時にアカウントをクールダウンしてフェイルオーバーします。新規セッションは{window}の使用率が {threshold}% 未満のアカウントを優先します。", + "anthropicPool.enabledNoQuotaDesc": "429 時にアカウントをクールダウンしてフェイルオーバーします。クォータに基づく新規セッション選択は無効で、正常な affinity とアクティブアカウントのルーティングを維持します。", "anthropicPool.disabledDesc": "アクティブな Claude アカウントのみを使用します。実験的ルーティングを受け入れる場合のみ有効にしてください。", "anthropicPool.experimentalWarning": "実験的で十分に検証されていません。自動的な複数アカウント回転に見える行為は Anthropic により制限される可能性があります。同一組織はクォータを共有することがあり、その場合プールしても効果がありません。リスクを理解していない場合はオフのままにしてください。", "anthropicPool.needTwoAccounts": "プールを有効にする前に、Claude OAuth アカウントを 2 つ以上追加してください。", @@ -1689,11 +1690,11 @@ export const ja: Record = { "accountPool.strategyUpdateFailed": "ローテーション戦略を保存できませんでした。", "accountPool.quotaWindow": "クォータ集計ウィンドウ", - "accountPool.quotaWindowDesc": "クォータ戦略が新規セッションのアカウントを選ぶとき、どのキャッシュ済み使用量バーを評価するかを指定します。", + "accountPool.quotaWindowDesc": "クォータに基づく新規セッション選択、フィルファーストのしきい値判定、対象となる 429 代替先で使うキャッシュ済み使用量バーを指定します。", "accountPool.quotaWindowFiveHour": "5 時間バー", "accountPool.quotaWindowWeekly": "週間バー", "accountPool.quotaWindowMaxUtilization": "高い方のバー", - "accountPool.quotaWindowHint": "週間バーを選んでも 5 時間バーを使い切ったアカウントはスキップされ、週間の使用量が並んだ場合は 5 時間の使用量が少ない方を選びます。アカウントごとの週間バーは、プロバイダーページが取得した後にのみ判明します。", + "accountPool.quotaWindowHint": "週間バーでは、他に対象アカウントが残る間だけ 5 時間バーを使い切ったアカウントをスキップし、残らない場合はそれらへフォールバックします。同点では 5 時間使用量が少ない方を優先します。アカウントごとの週間バーはプロバイダーページで取得した後にのみ判明します。", "accountPool.quotaWindowInert": "使用量バーを評価するのはクォータ、またはしきい値が 0 を超えるフィルファーストだけです。現在のローテーション戦略では、この設定は何も変えません。", "accountPool.priority": "選択順序", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 9ad3a70594..1ee0d5eba6 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1254,6 +1254,7 @@ export const ko: Record = { "codexAuth.catalogRefreshPending": "변경 사항은 저장되었지만 Codex 모델 카탈로그 새로 고침이 보류 중입니다. ocx sync를 실행해 다시 시도하세요.", "anthropicPool.title": "Claude 계정 풀(실험적)", "anthropicPool.enabledDesc": "429 시 계정을 쿨다운하고 장애 조치합니다. 새 세션은 {window}이 {threshold}% 미만인 계정을 우선합니다.", + "anthropicPool.enabledNoQuotaDesc": "429 시 계정을 쿨다운하고 장애 조치합니다. 할당량 기반 새 세션 선택은 꺼지고, 정상 어피니티와 활성 계정 라우팅은 유지됩니다.", "anthropicPool.disabledDesc": "활성 Claude 계정만 사용합니다. 실험적 라우팅을 감수할 때만 켜세요.", "anthropicPool.experimentalWarning": "실험적이며 충분히 검증되지 않았습니다. 자동 다중 계정 로테이션처럼 보이는 동작은 Anthropic이 계정을 제한할 수 있습니다. 같은 조직은 할당량을 공유할 수 있어 풀링이 도움이 되지 않을 수 있습니다. 위험을 이해하지 못하면 꺼 두세요.", "anthropicPool.needTwoAccounts": "풀을 켜기 전에 Claude OAuth 계정을 두 개 이상 추가하세요.", @@ -1285,11 +1286,11 @@ export const ko: Record = { "accountPool.strategyUpdateFailed": "로테이션 전략을 저장하지 못했습니다.", "accountPool.quotaWindow": "할당량 기준 구간", - "accountPool.quotaWindowDesc": "할당량 전략이 새 세션에 계정을 배정할 때 어떤 캐시된 사용량을 기준으로 점수를 매길지 정합니다.", + "accountPool.quotaWindowDesc": "할당량 기반 새 세션 선택, 필 퍼스트 임계값 판정, 가능한 429 대체 계정에 사용할 캐시 사용량 기준을 정합니다.", "accountPool.quotaWindowFiveHour": "5시간 사용량", "accountPool.quotaWindowWeekly": "주간 사용량", "accountPool.quotaWindowMaxUtilization": "더 높은 사용량", - "accountPool.quotaWindowHint": "주간을 선택해도 5시간 사용량이 소진된 계정은 계속 건너뛰며, 주간 사용량이 같으면 5시간 사용량이 더 낮은 계정을 고릅니다. 계정별 주간 사용량은 공급자 페이지에서 한 번 조회한 뒤에야 알 수 있습니다.", + "accountPool.quotaWindowHint": "주간은 다른 사용 가능한 계정이 남아 있을 때만 5시간 사용량이 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. 주간 사용량이 같으면 5시간 사용량이 더 낮은 계정을 고르며, 계정별 주간 사용량은 공급자 페이지에서 조회한 뒤에만 알 수 있습니다.", "accountPool.quotaWindowInert": "할당량 전략, 또는 임계값이 0보다 큰 필 퍼스트만 사용량을 기준으로 점수를 매깁니다. 현재 로테이션 전략에서는 이 설정이 아무 영향을 주지 않습니다.", "accountPool.priority": "선택 순서", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 0ac5bcb025..c5aef47e27 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1709,6 +1709,7 @@ export const ru: Record = { "codexAuth.catalogRefreshPending": "Изменение сохранено, но обновление каталога моделей Codex ещё не завершено. Выполните ocx sync, чтобы повторить попытку.", "anthropicPool.title": "Пул аккаунтов Claude (экспериментально)", "anthropicPool.enabledDesc": "При 429 аккаунт охлаждается и выполняется переключение. Новые сессии предпочитают использование ниже {threshold}% ({window}).", + "anthropicPool.enabledNoQuotaDesc": "При 429 аккаунт охлаждается и выполняется переключение. Выбор новых сессий по квоте отключён; сохраняются здоровая привязка и маршрутизация через активный аккаунт.", "anthropicPool.disabledDesc": "Используется только активный аккаунт Claude. Включайте только если принимаете экспериментальную маршрутизацию.", "anthropicPool.experimentalWarning": "Экспериментально и недостаточно проверено. Anthropic может ограничить аккаунты, похожие на автоматическую ротацию. Одна организация может делить квоту — пул таких аккаунтов не поможет. Оставляйте выключенным, если не понимаете риск.", "anthropicPool.needTwoAccounts": "Перед включением пула добавьте минимум два OAuth-аккаунта Claude.", @@ -1740,11 +1741,11 @@ export const ru: Record = { "accountPool.strategyUpdateFailed": "Не удалось сохранить стратегию ротации.", "accountPool.quotaWindow": "Окно квоты", - "accountPool.quotaWindowDesc": "Какую кешированную полосу использования оценивает стратегия «Квота», когда выбирает аккаунт для новой сессии.", + "accountPool.quotaWindowDesc": "Какая кешированная полоса используется для выбора новых сессий по квоте, проверки порога Fill-first и допустимых замен после 429.", "accountPool.quotaWindowFiveHour": "Полоса 5 часов", "accountPool.quotaWindowWeekly": "Недельная полоса", "accountPool.quotaWindowMaxUtilization": "Наибольшая полоса", - "accountPool.quotaWindowHint": "Недельная полоса всё равно пропускает аккаунты с исчерпанной полосой 5 часов, а при равных недельных значениях выбирает аккаунт с меньшим использованием за 5 часов; недельные полосы отдельных аккаунтов известны только после опроса на странице провайдеров.", + "accountPool.quotaWindowHint": "Недельная полоса пропускает аккаунты с исчерпанной полосой 5 часов, пока остаётся другой допустимый аккаунт, но возвращается к ним, если других нет. При равенстве выбирается меньшее использование за 5 часов; недельные полосы известны только после опроса на странице провайдеров.", "accountPool.quotaWindowInert": "Полосу использования оценивает только «Квота» или Fill-first с порогом выше 0, поэтому для текущей стратегии ротации эта настройка ничего не меняет.", "accountPool.priority": "Порядок выбора", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index b1665b556f..9de3c1f4f2 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1716,6 +1716,7 @@ export const tr: Record = { "anthropicPool.title": "Claude hesap havuzu (deneysel)", "anthropicPool.enabledDesc": "429 alındığında hesabı bekletir ve başka bir hesaba geçer. Yeni oturumlar {window} değerine göre %{threshold} altında kullanıma sahip hesapları tercih eder.", + "anthropicPool.enabledNoQuotaDesc": "429 alındığında hesabı bekletir ve başka bir hesaba geçer. Kotaya dayalı yeni oturum seçimi kapalıdır; sağlıklı oturum bağlılığı ve aktif hesap yönlendirmesi korunur.", "anthropicPool.disabledDesc": "Yalnızca aktif Claude hesabını kullanır.", "anthropicPool.experimentalWarning": "Deneysel: Claude OAuth hesaplarını döndürmek desteklenmeyen bir kullanım yoludur ve Anthropic hesap kısıtlamalarına veya hesabın askıya alınmasına yol açabilir. Aynı kuruluşu paylaşan hesaplar oran limitlerini paylaşır ve döndürmeden ek kapasite kazanmaz. Riskleri anlamıyorsanız kapalı tutun.", "anthropicPool.needTwoAccounts": "Havuzu etkinleştirmeden önce en az iki Claude OAuth hesabı ekleyin.", @@ -1747,11 +1748,11 @@ export const tr: Record = { "accountPool.strategyUpdateFailed": "Strateji kaydedilemedi.", "accountPool.quotaWindow": "Kota penceresi", - "accountPool.quotaWindowDesc": "Kota stratejisi yeni bir oturum için hesap seçerken hangi önbelleğe alınmış kullanım çubuğunu puanlar.", + "accountPool.quotaWindowDesc": "Kotaya dayalı yeni oturum seçimi, İlk doldurma eşik kontrolleri ve uygun 429 yedekleri için hangi önbelleğe alınmış kullanım çubuğunun kullanılacağını belirler.", "accountPool.quotaWindowFiveHour": "5 saatlik çubuk", "accountPool.quotaWindowWeekly": "Haftalık çubuk", "accountPool.quotaWindowMaxUtilization": "Daha yüksek çubuk", - "accountPool.quotaWindowHint": "Haftalık çubuk seçildiğinde bile 5 saatlik çubuğu tükenmiş hesaplar atlanır ve haftalık değerler eşit olduğunda 5 saatlik kullanımı daha düşük olan hesap seçilir; hesap başına haftalık çubuklar ancak Sağlayıcılar sayfası bunları sorguladıktan sonra bilinir.", + "accountPool.quotaWindowHint": "Haftalık çubuk, başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. Eşitlikte 5 saatlik kullanımı daha düşük olan seçilir; hesap başına haftalık çubuklar ancak Sağlayıcılar sayfası sorguladıktan sonra bilinir.", "accountPool.quotaWindowInert": "Kullanım çubuğunu yalnızca Kota ya da eşiği 0'ın üzerinde olan İlk doldurma puanlar; bu yüzden geçerli rotasyon stratejisi için bu ayar hiçbir şeyi değiştirmez.", "accountPool.priority": "Seçim sırası", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 03dda0b2e5..58d3453b07 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -1329,6 +1329,7 @@ export const zhTW: Record = { "codexAuth.pausedHint": "恢復前不會參與自動切換、重試、冷卻恢復或手動選擇。", "anthropicPool.title": "Claude 帳號池(實驗性)", "anthropicPool.enabledDesc": "遇到 429 時冷卻該帳號並故障轉移。新會話優先使用{window}低於 {threshold}% 的帳號。", + "anthropicPool.enabledNoQuotaDesc": "遇到 429 時冷卻該帳號並故障轉移。依配額選擇新會話的功能已關閉,健康的 affinity 與目前帳號路由會維持不變。", "anthropicPool.disabledDesc": "僅使用當前活躍的 Claude 帳號。僅在接受實驗性路由時啟用。", "anthropicPool.experimentalWarning": "實驗性功能,尚未充分驗證。看起來像自動多帳號輪換的行為可能導致 Anthropic 限制帳號。同一組織可能共享配額——對這些帳號做池化沒有幫助。除非瞭解風險,否則請保持關閉。", "anthropicPool.needTwoAccounts": "啟用帳號池前請至少新增兩個 Claude OAuth 帳號。", @@ -1352,11 +1353,11 @@ export const zhTW: Record = { "accountPool.strategyLoadFailed": "無法載入輪換策略。", "accountPool.strategyUpdateFailed": "無法儲存輪換策略。", "accountPool.quotaWindow": "配額統計區間", - "accountPool.quotaWindowDesc": "配額策略為新會話挑選帳號時,要依哪一條快取用量計分。", + "accountPool.quotaWindowDesc": "指定依配額選擇新會話、填滿優先門檻判定,以及可用 429 替代帳號所採用的快取用量。", "accountPool.quotaWindowFiveHour": "5 小時用量", "accountPool.quotaWindowWeekly": "每週用量", "accountPool.quotaWindowMaxUtilization": "較高的用量", - "accountPool.quotaWindowHint": "選擇每週用量時,仍會略過 5 小時用量已用盡的帳號;每週用量相同時,優先挑選 5 小時用量較低的帳號。各帳號的每週用量要等供應商頁面輪詢過後才會得知。", + "accountPool.quotaWindowHint": "每週用量會在仍有其他可用帳號時略過 5 小時用量已用盡的帳號;若沒有其他帳號,則會退回使用這些帳號。每週用量相同時優先挑選 5 小時用量較低者;各帳號的每週用量要等供應商頁面輪詢後才會得知。", "accountPool.quotaWindowInert": "只有配額策略,或門檻大於 0 的填滿優先策略,才會依用量計分;在目前的輪換策略下,這項設定不會有任何作用。", "codexAuth.switched": "下一次請求將使用 {email}", "codexAuth.loadFailed": "無法載入 Codex 帳號設定。", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 13a0650d07..21147a4ff4 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1247,6 +1247,7 @@ export const zh: Record = { "codexAuth.catalogRefreshPending": "更改已保存,但 Codex 模型目录仍待刷新。请运行 ocx sync 重试。", "anthropicPool.title": "Claude 账户池(实验性)", "anthropicPool.enabledDesc": "遇到 429 时冷却该账户并故障转移。新会话优先使用{window}低于 {threshold}% 的账户。", + "anthropicPool.enabledNoQuotaDesc": "遇到 429 时冷却该账户并故障转移。按配额选择新会话的功能已关闭,健康的会话亲和性和当前活跃账户路由保持不变。", "anthropicPool.disabledDesc": "仅使用当前活跃的 Claude 账户。仅在接受实验性路由时启用。", "anthropicPool.experimentalWarning": "实验性功能,尚未充分验证。看起来像自动多账户轮换的行为可能导致 Anthropic 限制账户。同一组织可能共享配额——对这些账户做池化没有帮助。除非了解风险,否则请保持关闭。", "anthropicPool.needTwoAccounts": "启用账户池前请至少添加两个 Claude OAuth 账户。", @@ -1278,11 +1279,11 @@ export const zh: Record = { "accountPool.strategyUpdateFailed": "无法保存轮换策略。", "accountPool.quotaWindow": "配额统计窗口", - "accountPool.quotaWindowDesc": "配额策略为新会话挑选账号时,按哪条缓存的用量打分。", + "accountPool.quotaWindowDesc": "指定按配额选择新会话、填满优先阈值判断以及可用 429 替代账户所使用的缓存用量。", "accountPool.quotaWindowFiveHour": "5 小时用量", "accountPool.quotaWindowWeekly": "每周用量", "accountPool.quotaWindowMaxUtilization": "较高的用量", - "accountPool.quotaWindowHint": "选择每周用量时,仍会跳过 5 小时用量已耗尽的账号;每周用量相同时,优先挑选 5 小时用量更低的账号。各账号的每周用量要等提供商页面轮询过之后才能获知。", + "accountPool.quotaWindowHint": "每周用量会在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。每周用量相同时优先选择 5 小时用量更低者;各账户的每周用量要等提供商页面轮询后才能获知。", "accountPool.quotaWindowInert": "只有配额策略,或阈值大于 0 的填满优先策略,才会按用量打分;在当前轮换策略下这项设置不起作用。", "accountPool.priority": "选择顺序", diff --git a/gui/tests/anthropic-pool-quota-window.test.tsx b/gui/tests/anthropic-pool-quota-window.test.tsx index 36dc66e5b7..ea37be9a59 100644 --- a/gui/tests/anthropic-pool-quota-window.test.tsx +++ b/gui/tests/anthropic-pool-quota-window.test.tsx @@ -169,6 +169,20 @@ describe("Anthropic account pool quota window", () => { expect(windowTrigger(drainedHost).disabled).toBe(true); }); + test("threshold zero description says quota-based new-session selection is off", async () => { + stubPool({ + enabled: true, + autoSwitchThreshold: 0, + strategy: "quota", + stickyLimit: 1, + quotaWindow: "weekly", + }); + const host = await mountPool(); + + expect(host.textContent).toContain("Quota-based new-session selection is off"); + expect(host.textContent).not.toContain("prefer usage under 0%"); + }); + test("quota window help text does not open the selector", async () => { stubPool({ enabled: true, diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index c840d60ab9..b982535b83 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -286,8 +286,11 @@ function pickNextFillFirstAnthropicAccount( afterId: string, eligible: string[], ): string | null { - if (eligible.length === 0) return null; - const ordered = [...eligible].sort((a, b) => a.localeCompare(b)); + const window = anthropicQuotaWindow(anthropicAccountPoolConfig(config)); + const available = window === "weekly" ? eligible.filter(id => !exhausted5h(id)) : eligible; + const candidates = available.length > 0 ? available : eligible; + if (candidates.length === 0) return null; + const ordered = [...candidates].sort((a, b) => a.localeCompare(b)); const set = getAccountSet(PROVIDER); const stableAll = set ? [...set.accounts.map(a => a.id)].sort((a, b) => a.localeCompare(b)) @@ -303,7 +306,7 @@ function pickNextFillFirstAnthropicAccount( let fallback: string | null = null; for (let step = 1; step <= stableAll.length; step++) { const candidate = stableAll[(startIdx + step) % stableAll.length]!; - if (!eligible.includes(candidate)) continue; + if (!candidates.includes(candidate)) continue; if (!fallback) fallback = candidate; if (isActiveUnderFillFirstThreshold(config, candidate)) return candidate; } diff --git a/tests/anthropic-account-pool.test.ts b/tests/anthropic-account-pool.test.ts index 58998f682c..d6af8fad33 100644 --- a/tests/anthropic-account-pool.test.ts +++ b/tests/anthropic-account-pool.test.ts @@ -601,6 +601,20 @@ describe("anthropic account pool quota window scoring", () => { ).accountId).toBe(cId); }); + test("weekly fill-first fallback prefers a non-exhausted successor above threshold", async () => { + const { aId, bId, cId } = await seedThreeAccounts(); + expect([aId, bId, cId].sort((a, b) => a.localeCompare(b))).toEqual([aId, bId, cId]); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 10, weeklyPercent: 90, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 100, weeklyPercent: 10, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", cId, { fiveHourPercent: 20, weeklyPercent: 90, updatedAt }); + + expect(resolveAnthropicAccountForSession( + "fill-first-non-exhausted-fallback", + cfg(true, 80, { strategy: "fill-first", quotaWindow: "weekly" }), + ).accountId).toBe(cId); + }); + test("weekly mode with empty quota cache falls back to stable order", async () => { const { aId, bId } = await seedTwoAccounts(); const config = cfg(true, 80, { quotaWindow: "weekly" }); From ba4433dd3c06142e392434e99c074ff24e50bdc0 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Sat, 29 Aug 2026 21:34:17 +0900 Subject: [PATCH 16/24] docs(anthropic): distinguish quota evidence and strategy behavior --- .../src/content/docs/fr/reference/configuration/providers.md | 2 +- .../src/content/docs/ja/reference/configuration/providers.md | 2 +- .../src/content/docs/ko/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/reference/configuration/providers.md | 2 +- .../src/content/docs/ru/reference/configuration/providers.md | 2 +- .../src/content/docs/tr/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-cn/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-tw/reference/configuration/providers.md | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 9b8a656e0c..8dd0656aee 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -195,7 +195,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; `quota` classe les comptes selon la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures, et `fill-first` évalue son seuil d'évacuation dans cette même fenêtre. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation mise en cache utilisée pour classer les comptes. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. `round-robin` ignore ce réglage. Une session saine avec affinité n'est pas rééquilibrée de manière proactive ; la récupération après une erreur terminale, y compris le basculement 429, classe néanmoins les remplaçants avec cette fenêtre sans modifier les limites de temporisation ou de basculement. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation signalée par le fournisseur, mise en cache et utilisée pour la sélection selon l'utilisation. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. Une session saine avec affinité n'est pas rééquilibrée de manière proactive. Pour l'affectation des nouvelles sessions et la récupération après erreur terminale, `quota` classe directement les candidats admissibles avec cette fenêtre ; `fill-first` avance dans un ordre stable selon le seuil et les règles d'épuisement de cette fenêtre ; `round-robin` l'ignore. Le délai de récupération, les limites de basculement et l'éligibilité de réauthentification restent des états locaux distincts. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 6a7f6e26c6..0518501b49 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -161,7 +161,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。`quota` は `quotaWindow` で指定した期間(既定は 5 時間足)でアカウントを順位付けし、`fill-first` も同じ期間で使い切りのしきい値を判定します。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で評価するキャッシュ済み使用量です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。`round-robin` はこの設定を無視します。正常な affinity セッションを先回りして再配置することはありませんが、429 フェイルオーバーを含む終端エラーからの復旧では、既存のクールダウンとフェイルオーバー上限を変えずに、この期間で代替アカウントを順位付けします。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で使う、プロバイダー報告のキャッシュ済み使用率です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。正常な affinity セッションを先回りして再配置することはありません。新規セッションの割り当てと終端エラーからの復旧では、`quota` はこの期間で対象候補を直接順位付けし、`fill-first` はこの期間のしきい値と上限到達ルールを使って安定順に進み、`round-robin` はこの設定を無視します。クールダウン、フェイルオーバー上限、再認証の適格性は別のローカル状態です。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 35c07ae005..173343eabf 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -165,7 +165,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. `quota`는 `quotaWindow`로 지정한 창(기본값은 5시간 막대)으로 계정 순위를 매기고, `fill-first`도 같은 창에서 소진 임계값을 판정합니다. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택이 점수화할 캐시 사용량 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. `round-robin`은 이 설정을 무시합니다. 정상 affinity 세션을 선제적으로 재배치하지 않지만, 429 failover를 포함한 종료 오류 복구에서는 기존 쿨다운과 failover 한도를 바꾸지 않은 채 이 창으로 대체 계정의 순위를 매깁니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택에 사용하는, 공급자가 보고한 캐시 사용률 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. 정상 affinity 세션을 선제적으로 재배치하지 않습니다. 새 세션 배정과 종료 오류 복구에서 `quota`는 이 창으로 사용 가능한 후보의 순위를 직접 매기고, `fill-first`는 이 창의 임계값과 소진 규칙에 따라 안정 순서로 이동하며, `round-robin`은 이 설정을 무시합니다. 쿨다운, failover 한도, 재인증 가능 여부는 별도의 로컬 상태로 유지됩니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index b9de867853..f00f671165 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -285,7 +285,7 @@ rotation may trigger provider restrictions. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables quota picking. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; `quota` ranks accounts by the window set by `quotaWindow`, and `fill-first` evaluates its drain threshold in that same window. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Which cached usage bar usage-aware account selection scores. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. `round-robin` ignores this setting. A healthy affinity-bound session is not proactively rebalanced; terminal recovery, including 429 failover, still ranks eligible replacement accounts with this window without changing cooldown or failover limits. Per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and terminal recovery, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index efa93d62dc..c5f9cbd0ec 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -196,7 +196,7 @@ reauth или порога исчерпания; здоровые привяза | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; `quota` ранжирует аккаунты по окну, заданному в `quotaWindow` (по умолчанию это 5-hour bar'ы), а `fill-first` в этом же окне оценивает свой порог исчерпания. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Cached usage bar для оценки при выборе аккаунта по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. `round-robin` игнорирует настройку. Здоровая сессия с affinity не перебалансируется заранее; восстановление после терминальной ошибки, включая 429 failover, всё равно ранжирует доступные замены по этому окну, не меняя лимиты cooldown и failover. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Кешированная полоса использования, сообщённая провайдером и применяемая при выборе по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. Здоровая сессия с affinity не перебалансируется заранее. При назначении новой сессии и восстановлении после терминальной ошибки `quota` напрямую ранжирует доступных кандидатов по этому окну, `fill-first` идёт в стабильном порядке с учётом порога и правил исчерпания этого окна, а `round-robin` игнорирует настройку. Cooldown, лимиты failover и допустимость повторной аутентификации остаются отдельным локальным состоянием. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index 2a470e0245..58d621bc2d 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -221,7 +221,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; `quota`, `quotaWindow` ile belirlenen pencereye (varsayılan 5 saatlik çubuklar) göre hesapları sıralar ve `fill-first` de tükenme eşiğini aynı pencerede değerlendirir. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminin puanladığı önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. `round-robin` bu ayarı yok sayar. Sağlıklı affinity oturumları önceden yeniden dengelenmez; 429 yük devretmesi dahil terminal hata kurtarması, mevcut soğuma ve yük devretme sınırlarını değiştirmeden uygun yedek hesapları bu pencereye göre sıralar. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminde kullanılan, sağlayıcının bildirdiği önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. Sağlıklı affinity oturumları önceden yeniden dengelenmez. Yeni oturum ataması ve terminal hata kurtarmasında `quota`, uygun adayları doğrudan bu pencereye göre sıralar; `fill-first`, bu pencerenin eşik ve tükenme kurallarıyla kararlı sırada ilerler; `round-robin` ayarı yok sayar. Cooldown, yük devretme sınırları ve yeniden kimlik doğrulama uygunluğu ayrı yerel durum olarak kalır. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index 168c2a6acb..dd2931df7e 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -159,7 +159,7 @@ affinity。这些策略不能规避 provider enforcement。 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;`quota` 按 `quotaWindow` 指定的窗口(默认是 5 小时条形数据)对账户排序,`fill-first` 也在同一窗口中判定其耗尽阈值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时用于评分的缓存用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。`round-robin` 会忽略此设置。不会主动重新平衡健康且已建立亲和性的会话;遇到终止性错误进行恢复时(包括 429 故障转移),仍会按此窗口对可用替代账户排序,但不会改变现有冷却或故障转移上限。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时使用的、由提供商报告并缓存的用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。不会主动重新平衡健康且已建立亲和性的会话。在新会话分配和终止性错误恢复中,`quota` 直接按此窗口对可用候选账户排序;`fill-first` 按此窗口的阈值和耗尽规则以稳定顺序前进;`round-robin` 忽略此设置。冷却状态、故障转移上限和重新认证资格仍是独立的本地状态。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index bcf7a99082..8a222472fe 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -128,7 +128,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;`quota` 依 `quotaWindow` 指定的視窗(預設為 5 小時列)為帳號排序,`fill-first` 也在同一視窗中判定其排空閾值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所評分的快取用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已耗盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。`round-robin` 會忽略此設定。不會主動重新平衡健康且已有 affinity 的 session;遇到終止錯誤進行復原時(包括 429 容錯移轉),仍會按此視窗排序合格的替代帳號,但不改變現有冷卻或容錯移轉上限。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所採用、由供應商回報並快取的用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已用盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。不會主動重新平衡健康且已有 affinity 的 session。在分配新 session 與終止錯誤復原時,`quota` 直接依此視窗排序可用候選帳號;`fill-first` 依此視窗的門檻與用盡規則按穩定順序前進;`round-robin` 忽略此設定。冷卻狀態、容錯移轉上限與重新驗證資格仍是獨立的本機狀態。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 From 5c1e2201a1777df4e7576cfff8d38ac1a5767455 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Sat, 29 Aug 2026 21:43:32 +0900 Subject: [PATCH 17/24] docs(anthropic): specify unknown quota ordering --- .../src/content/docs/fr/reference/configuration/providers.md | 2 +- .../src/content/docs/ja/reference/configuration/providers.md | 2 +- .../src/content/docs/ko/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/reference/configuration/providers.md | 2 +- .../src/content/docs/ru/reference/configuration/providers.md | 2 +- .../src/content/docs/tr/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-cn/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-tw/reference/configuration/providers.md | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index 8dd0656aee..aeac6ce50b 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -195,7 +195,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; `quota` classe les comptes selon la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures, et `fill-first` évalue son seuil d'évacuation dans cette même fenêtre. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation signalée par le fournisseur, mise en cache et utilisée pour la sélection selon l'utilisation. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la plus élevée des deux barres. Dans les deux modes, les égalités sont départagées par la plus faible utilisation sur 5 heures. Une session saine avec affinité n'est pas rééquilibrée de manière proactive. Pour l'affectation des nouvelles sessions et la récupération après erreur terminale, `quota` classe directement les candidats admissibles avec cette fenêtre ; `fill-first` avance dans un ordre stable selon le seuil et les règles d'épuisement de cette fenêtre ; `round-robin` l'ignore. Le délai de récupération, les limites de basculement et l'éligibilité de réauthentification restent des états locaux distincts. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation signalée par le fournisseur, mise en cache et utilisée pour la sélection selon l'utilisation. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la valeur connue la plus élevée et peut donc employer la barre sur 5 heures avant que la barre hebdomadaire soit disponible ; si aucune n'est connue, le compte suit l'ordre des utilisations inconnues. Les utilisations connues précèdent les inconnues, mais si tous les comptes admissibles sont inconnus, la sélection en renvoie tout de même un dans leur ordre admissible. Après le départage documenté par la plus faible utilisation sur 5 heures, une égalité exacte conserve cet ordre. Une session saine avec affinité n'est pas rééquilibrée de manière proactive. Pour l'affectation des nouvelles sessions et la récupération après erreur terminale, `quota` classe directement les candidats admissibles avec cette fenêtre ; `fill-first` avance dans un ordre stable selon le seuil et les règles d'épuisement de cette fenêtre ; `round-robin` l'ignore. Le délai de récupération, les limites de basculement et l'éligibilité de réauthentification restent des états locaux distincts. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index 0518501b49..ed5d29c665 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -161,7 +161,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。`quota` は `quotaWindow` で指定した期間(既定は 5 時間足)でアカウントを順位付けし、`fill-first` も同じ期間で使い切りのしきい値を判定します。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で使う、プロバイダー報告のキャッシュ済み使用率です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は 2 つの値のうち高い方を使います。どちらのモードでも同点の場合は 5 時間使用量が低い方を優先します。正常な affinity セッションを先回りして再配置することはありません。新規セッションの割り当てと終端エラーからの復旧では、`quota` はこの期間で対象候補を直接順位付けし、`fill-first` はこの期間のしきい値と上限到達ルールを使って安定順に進み、`round-robin` はこの設定を無視します。クールダウン、フェイルオーバー上限、再認証の適格性は別のローカル状態です。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で使う、プロバイダー報告のキャッシュ済み使用率です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は判明している値のうち最も高いものを使うため、週次使用量が未取得でも 5 時間使用量を利用できます。どちらも不明なら unknown の順位付けに従います。既知の使用量は unknown より先ですが、対象がすべて unknown でも対象順の先頭を選択します。記載した 5 時間使用量による同点判定後も完全に同点なら、対象順を維持します。正常な affinity セッションを先回りして再配置することはありません。新規セッションの割り当てと終端エラーからの復旧では、`quota` はこの期間で対象候補を直接順位付けし、`fill-first` はこの期間のしきい値と上限到達ルールを使って安定順に進み、`round-robin` はこの設定を無視します。クールダウン、フェイルオーバー上限、再認証の適格性は別のローカル状態です。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index 173343eabf..d52972be22 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -165,7 +165,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. `quota`는 `quotaWindow`로 지정한 창(기본값은 5시간 막대)으로 계정 순위를 매기고, `fill-first`도 같은 창에서 소진 임계값을 판정합니다. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택에 사용하는, 공급자가 보고한 캐시 사용률 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 두 막대 중 더 높은 값을 사용합니다. 두 모드 모두 동점이면 5시간 사용량이 낮은 계정을 우선합니다. 정상 affinity 세션을 선제적으로 재배치하지 않습니다. 새 세션 배정과 종료 오류 복구에서 `quota`는 이 창으로 사용 가능한 후보의 순위를 직접 매기고, `fill-first`는 이 창의 임계값과 소진 규칙에 따라 안정 순서로 이동하며, `round-robin`은 이 설정을 무시합니다. 쿨다운, failover 한도, 재인증 가능 여부는 별도의 로컬 상태로 유지됩니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택에 사용하는, 공급자가 보고한 캐시 사용률 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 알려진 값 중 가장 높은 값을 사용하므로 주간 사용량을 알기 전에도 5시간 사용량을 쓸 수 있고, 둘 다 모르면 unknown 순서를 따릅니다. 알려진 사용량은 unknown보다 앞서지만, 사용 가능한 계정이 모두 unknown이어도 사용 가능한 순서의 계정을 선택합니다. 앞서 설명한 5시간 사용량 동점 판정 뒤에도 완전히 같으면 사용 가능한 순서를 유지합니다. 정상 affinity 세션을 선제적으로 재배치하지 않습니다. 새 세션 배정과 종료 오류 복구에서 `quota`는 이 창으로 사용 가능한 후보의 순위를 직접 매기고, `fill-first`는 이 창의 임계값과 소진 규칙에 따라 안정 순서로 이동하며, `round-robin`은 이 설정을 무시합니다. 쿨다운, failover 한도, 재인증 가능 여부는 별도의 로컬 상태로 유지됩니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index f00f671165..1c0e942b05 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -285,7 +285,7 @@ rotation may trigger provider restrictions. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables quota picking. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; `quota` ranks accounts by the window set by `quotaWindow`, and `fill-first` evaluates its drain threshold in that same window. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores whichever of the two bars is higher. Ties in either mode prefer lower 5-hour usage. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and terminal recovery, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores the highest known bar, so it can use 5-hour usage before weekly usage is available; if neither is known, the account follows unknown-usage ordering. Known usage ranks before unknown usage, but if every eligible account is unknown, selection still returns one in eligible order. After the documented lower-5-hour tie-break, exact ties preserve eligible order. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and terminal recovery, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index c5f9cbd0ec..7b3b4b2d16 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -196,7 +196,7 @@ reauth или порога исчерпания; здоровые привяза | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; `quota` ранжирует аккаунты по окну, заданному в `quotaWindow` (по умолчанию это 5-hour bar'ы), а `fill-first` в этом же окне оценивает свой порог исчерпания. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Кешированная полоса использования, сообщённая провайдером и применяемая при выборе по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует большее из двух значений. При равенстве в обоих режимах выбирается меньший 5-hour usage. Здоровая сессия с affinity не перебалансируется заранее. При назначении новой сессии и восстановлении после терминальной ошибки `quota` напрямую ранжирует доступных кандидатов по этому окну, `fill-first` идёт в стабильном порядке с учётом порога и правил исчерпания этого окна, а `round-robin` игнорирует настройку. Cooldown, лимиты failover и допустимость повторной аутентификации остаются отдельным локальным состоянием. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Кешированная полоса использования, сообщённая провайдером и применяемая при выборе по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует наибольшее известное значение, поэтому до появления недельных данных может использовать 5-hour usage; если неизвестны оба значения, аккаунт следует порядку unknown usage. Известное использование ранжируется раньше unknown, но если у всех доступных аккаунтов оно неизвестно, выбирается аккаунт в доступном порядке. После описанного сравнения по меньшему 5-hour usage полное равенство также сохраняет этот порядок. Здоровая сессия с affinity не перебалансируется заранее. При назначении новой сессии и восстановлении после терминальной ошибки `quota` напрямую ранжирует доступных кандидатов по этому окну, `fill-first` идёт в стабильном порядке с учётом порога и правил исчерпания этого окна, а `round-robin` игнорирует настройку. Cooldown, лимиты failover и допустимость повторной аутентификации остаются отдельным локальным состоянием. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index 58d621bc2d..afc770d2c7 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -221,7 +221,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; `quota`, `quotaWindow` ile belirlenen pencereye (varsayılan 5 saatlik çubuklar) göre hesapları sıralar ve `fill-first` de tükenme eşiğini aynı pencerede değerlendirir. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminde kullanılan, sağlayıcının bildirdiği önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` iki çubuktan yüksek olanı kullanır. Her iki moddaki eşitliklerde daha düşük 5 saatlik kullanım tercih edilir. Sağlıklı affinity oturumları önceden yeniden dengelenmez. Yeni oturum ataması ve terminal hata kurtarmasında `quota`, uygun adayları doğrudan bu pencereye göre sıralar; `fill-first`, bu pencerenin eşik ve tükenme kurallarıyla kararlı sırada ilerler; `round-robin` ayarı yok sayar. Cooldown, yük devretme sınırları ve yeniden kimlik doğrulama uygunluğu ayrı yerel durum olarak kalır. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminde kullanılan, sağlayıcının bildirdiği önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` bilinen en yüksek değeri kullanır; haftalık değer henüz yokken 5 saatlik değeri kullanabilir, ikisi de bilinmiyorsa hesap unknown kullanım sırasını izler. Bilinen kullanım unknown değerlerden önce gelir; tüm uygun hesaplar unknown olsa bile uygun sıradaki bir hesap seçilir. Belgelenen daha düşük 5 saatlik kullanım eşitlik bozmasından sonra tam eşitlikte de uygun sıra korunur. Sağlıklı affinity oturumları önceden yeniden dengelenmez. Yeni oturum ataması ve terminal hata kurtarmasında `quota`, uygun adayları doğrudan bu pencereye göre sıralar; `fill-first`, bu pencerenin eşik ve tükenme kurallarıyla kararlı sırada ilerler; `round-robin` ayarı yok sayar. Cooldown, yük devretme sınırları ve yeniden kimlik doğrulama uygunluğu ayrı yerel durum olarak kalır. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index dd2931df7e..81f793e546 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -159,7 +159,7 @@ affinity。这些策略不能规避 provider enforcement。 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;`quota` 按 `quotaWindow` 指定的窗口(默认是 5 小时条形数据)对账户排序,`fill-first` 也在同一窗口中判定其耗尽阈值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时使用的、由提供商报告并缓存的用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用两条用量中较高的值。两种模式分数相同时,都优先选择 5 小时用量较低的账户。不会主动重新平衡健康且已建立亲和性的会话。在新会话分配和终止性错误恢复中,`quota` 直接按此窗口对可用候选账户排序;`fill-first` 按此窗口的阈值和耗尽规则以稳定顺序前进;`round-robin` 忽略此设置。冷却状态、故障转移上限和重新认证资格仍是独立的本地状态。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时使用的、由提供商报告并缓存的用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用已知值中的最高值,因此每周用量尚不可用时仍可使用 5 小时用量;两者都未知时,账户遵循 unknown 用量排序。已知用量排在 unknown 之前,但如果所有可用账户都未知,仍会按可用顺序选择一个账户。在前述较低 5 小时用量的同分判定之后,完全相同时也保留可用顺序。不会主动重新平衡健康且已建立亲和性的会话。在新会话分配和终止性错误恢复中,`quota` 直接按此窗口对可用候选账户排序;`fill-first` 按此窗口的阈值和耗尽规则以稳定顺序前进;`round-robin` 忽略此设置。冷却状态、故障转移上限和重新认证资格仍是独立的本地状态。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index 8a222472fe..cb30e54afa 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -128,7 +128,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;`quota` 依 `quotaWindow` 指定的視窗(預設為 5 小時列)為帳號排序,`fill-first` 也在同一視窗中判定其排空閾值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所採用、由供應商回報並快取的用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已用盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用兩個用量中較高的值。兩種模式分數相同時,都優先選擇 5 小時用量較低的帳號。不會主動重新平衡健康且已有 affinity 的 session。在分配新 session 與終止錯誤復原時,`quota` 直接依此視窗排序可用候選帳號;`fill-first` 依此視窗的門檻與用盡規則按穩定順序前進;`round-robin` 忽略此設定。冷卻狀態、容錯移轉上限與重新驗證資格仍是獨立的本機狀態。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所採用、由供應商回報並快取的用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已用盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用已知值中的最高值,因此每週用量尚未取得時仍可使用 5 小時用量;兩者都未知時,帳號遵循 unknown 用量排序。已知用量排在 unknown 之前,但若所有可用帳號都是 unknown,仍會依可用順序選出一個。完成前述較低 5 小時用量的同分判定後,完全相同時也保留可用順序。不會主動重新平衡健康且已有 affinity 的 session。在分配新 session 與終止錯誤復原時,`quota` 直接依此視窗排序可用候選帳號;`fill-first` 依此視窗的門檻與用盡規則按穩定順序前進;`round-robin` 忽略此設定。冷卻狀態、容錯移轉上限與重新驗證資格仍是獨立的本機狀態。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 From dc461bda3d89f8ceeae1e3c1569b05016fff4bdc Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Sat, 29 Aug 2026 21:50:41 +0900 Subject: [PATCH 18/24] docs(anthropic): scope quota recovery to eligible 429 routing --- .../src/content/docs/fr/reference/configuration/providers.md | 2 +- .../src/content/docs/ja/reference/configuration/providers.md | 2 +- .../src/content/docs/ko/reference/configuration/providers.md | 2 +- docs-site/src/content/docs/reference/configuration/providers.md | 2 +- .../src/content/docs/ru/reference/configuration/providers.md | 2 +- .../src/content/docs/tr/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-cn/reference/configuration/providers.md | 2 +- .../src/content/docs/zh-tw/reference/configuration/providers.md | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index aeac6ce50b..f02c66defc 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -195,7 +195,7 @@ rotation automatique peut déclencher des restrictions du fournisseur. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Active l'affinité persistante et le basculement après une temporisation 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Pour les nouvelles sessions, lorsque le compte actif atteint ce seuil, choisir la plus faible utilisation connue et mise en cache dans la fenêtre configurée. `0` désactive la sélection selon le quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Stratégie des nouvelles sessions ; `quota` classe les comptes selon la fenêtre définie par `quotaWindow`, par défaut les barres sur 5 heures, et `fill-first` évalue son seuil d'évacuation dans cette même fenêtre. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation signalée par le fournisseur, mise en cache et utilisée pour la sélection selon l'utilisation. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la valeur connue la plus élevée et peut donc employer la barre sur 5 heures avant que la barre hebdomadaire soit disponible ; si aucune n'est connue, le compte suit l'ordre des utilisations inconnues. Les utilisations connues précèdent les inconnues, mais si tous les comptes admissibles sont inconnus, la sélection en renvoie tout de même un dans leur ordre admissible. Après le départage documenté par la plus faible utilisation sur 5 heures, une égalité exacte conserve cet ordre. Une session saine avec affinité n'est pas rééquilibrée de manière proactive. Pour l'affectation des nouvelles sessions et la récupération après erreur terminale, `quota` classe directement les candidats admissibles avec cette fenêtre ; `fill-first` avance dans un ordre stable selon le seuil et les règles d'épuisement de cette fenêtre ; `round-robin` l'ignore. Le délai de récupération, les limites de basculement et l'éligibilité de réauthentification restent des états locaux distincts. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Barre d'utilisation signalée par le fournisseur, mise en cache et utilisée pour la sélection selon l'utilisation. `five-hour` conserve le comportement actuel. `weekly` utilise la barre hebdomadaire et ignore les comptes dont la barre sur 5 heures est épuisée tant qu'un autre compte admissible reste disponible, mais y revient si aucun autre ne reste. `max-utilization` utilise la valeur connue la plus élevée et peut donc employer la barre sur 5 heures avant que la barre hebdomadaire soit disponible ; si aucune n'est connue, le compte suit l'ordre des utilisations inconnues. Les utilisations connues précèdent les inconnues, mais si tous les comptes admissibles sont inconnus, la sélection en renvoie tout de même un dans leur ordre admissible. Après le départage documenté par la plus faible utilisation sur 5 heures, une égalité exacte conserve cet ordre. Une session saine avec affinité n'est pas rééquilibrée de manière proactive. Pour l'affectation des nouvelles sessions et la reprise du routage après un remplacement admissible à la suite d'un 429, `quota` classe directement les candidats admissibles avec cette fenêtre ; `fill-first` avance dans un ordre stable selon le seuil et les règles d'épuisement de cette fenêtre ; `round-robin` l'ignore. Le délai de récupération, les limites de basculement et l'éligibilité de réauthentification restent des états locaux distincts. Les barres hebdomadaires ne sont connues qu'après leur interrogation dans la page Fournisseurs du tableau de bord. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Liaisons de nouvelle session réussies conservées sur une sélection à tour de rôle. Portée 1–100. | Lorsque cette option est activée, un 429 enregistre une temporisation bornée à partir de `Retry-After` ou d'un délai de repli, puis peut diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index ed5d29c665..7bc1a00ba8 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -161,7 +161,7 @@ affinity を維持します。これらの戦略は provider enforcement を回 | `anthropicAccountPool.enabled?` | `boolean` | `false` |スティッキー アフィニティと 429 クールダウン フェイルオーバーを有効にします。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` |新しいセッションでは、アクティブなアカウントがこのしきい値に達すると、設定した期間で既知のキャッシュ使用量が最も低いアカウントを選択します。 `0` はクォータ選択を無効にします。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` |新しいセッション戦略。`quota` は `quotaWindow` で指定した期間(既定は 5 時間足)でアカウントを順位付けし、`fill-first` も同じ期間で使い切りのしきい値を判定します。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で使う、プロバイダー報告のキャッシュ済み使用率です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は判明している値のうち最も高いものを使うため、週次使用量が未取得でも 5 時間使用量を利用できます。どちらも不明なら unknown の順位付けに従います。既知の使用量は unknown より先ですが、対象がすべて unknown でも対象順の先頭を選択します。記載した 5 時間使用量による同点判定後も完全に同点なら、対象順を維持します。正常な affinity セッションを先回りして再配置することはありません。新規セッションの割り当てと終端エラーからの復旧では、`quota` はこの期間で対象候補を直接順位付けし、`fill-first` はこの期間のしきい値と上限到達ルールを使って安定順に進み、`round-robin` はこの設定を無視します。クールダウン、フェイルオーバー上限、再認証の適格性は別のローカル状態です。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` |使用量ベースのアカウント選択で使う、プロバイダー報告のキャッシュ済み使用率です。`five-hour` は従来の動作を維持します。`weekly` は週次使用量を使い、他に対象アカウントが残る間だけ 5 時間使用量が上限に達したアカウントを除外し、残らない場合はそれらへフォールバックします。`max-utilization` は判明している値のうち最も高いものを使うため、週次使用量が未取得でも 5 時間使用量を利用できます。どちらも不明なら unknown の順位付けに従います。既知の使用量は unknown より先ですが、対象がすべて unknown でも対象順の先頭を選択します。記載した 5 時間使用量による同点判定後も完全に同点なら、対象順を維持します。正常な affinity セッションを先回りして再配置することはありません。新規セッションの割り当てと、対象となる 429 代替後のルーティング復旧では、`quota` はこの期間で対象候補を直接順位付けし、`fill-first` はこの期間のしきい値と上限到達ルールを使って安定順に進み、`round-robin` はこの設定を無視します。クールダウン、フェイルオーバー上限、再認証の適格性は別のローカル状態です。アカウント別の週次使用量は、ダッシュボードのプロバイダーページで取得した後にのみ利用できます。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` |成功した新しいセッションのバインドは 1 つのラウンドロビン選択で保持されます。範囲は 1 ~ 100。 | 有効にすると、429 レコードは `Retry-After` またはデフォルトのバックオフからの制限されたクールダウンを記録し、リクエスト内でローテーションする可能性があります。アフィニティはプロセスローカルであり、サイズ制限があります。資格情報 401/403 は、アカウントに再認証が必要であることをマークします。すべての対象となるアカウントが冷却されている場合、クライアントは、既知の場合、認証エラーではなく、`Retry-After` を含む 429 を受け取ります。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index d52972be22..d7f92d08bb 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -165,7 +165,7 @@ affinity 초기화 뒤의 기존 작업도 포함될 수 있습니다. 출력 | `anthropicAccountPool.enabled?` | `boolean` | `false` | sticky 결속과 429 쿨다운 failover를 켭니다. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 새 세션에서는 활성 계정이 이 임계값에 도달하면 설정된 창의 알려진 캐시 사용량이 가장 낮은 계정을 고릅니다. `0`이면 quota 선택을 끕니다. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 새 세션 전략입니다. `quota`는 `quotaWindow`로 지정한 창(기본값은 5시간 막대)으로 계정 순위를 매기고, `fill-first`도 같은 창에서 소진 임계값을 판정합니다. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택에 사용하는, 공급자가 보고한 캐시 사용률 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 알려진 값 중 가장 높은 값을 사용하므로 주간 사용량을 알기 전에도 5시간 사용량을 쓸 수 있고, 둘 다 모르면 unknown 순서를 따릅니다. 알려진 사용량은 unknown보다 앞서지만, 사용 가능한 계정이 모두 unknown이어도 사용 가능한 순서의 계정을 선택합니다. 앞서 설명한 5시간 사용량 동점 판정 뒤에도 완전히 같으면 사용 가능한 순서를 유지합니다. 정상 affinity 세션을 선제적으로 재배치하지 않습니다. 새 세션 배정과 종료 오류 복구에서 `quota`는 이 창으로 사용 가능한 후보의 순위를 직접 매기고, `fill-first`는 이 창의 임계값과 소진 규칙에 따라 안정 순서로 이동하며, `round-robin`은 이 설정을 무시합니다. 쿨다운, failover 한도, 재인증 가능 여부는 별도의 로컬 상태로 유지됩니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 사용량 기반 계정 선택에 사용하는, 공급자가 보고한 캐시 사용률 막대입니다. `five-hour`는 기존 동작을 유지합니다. `weekly`는 주간 막대를 사용하며 다른 사용 가능한 계정이 남아 있을 때만 5시간 막대가 소진된 계정을 건너뛰고, 아무 계정도 남지 않으면 해당 계정으로 폴백합니다. `max-utilization`은 알려진 값 중 가장 높은 값을 사용하므로 주간 사용량을 알기 전에도 5시간 사용량을 쓸 수 있고, 둘 다 모르면 unknown 순서를 따릅니다. 알려진 사용량은 unknown보다 앞서지만, 사용 가능한 계정이 모두 unknown이어도 사용 가능한 순서의 계정을 선택합니다. 앞서 설명한 5시간 사용량 동점 판정 뒤에도 완전히 같으면 사용 가능한 순서를 유지합니다. 정상 affinity 세션을 선제적으로 재배치하지 않습니다. 새 세션 배정과 가능한 429 대체 이후 라우팅 복구에서 `quota`는 이 창으로 사용 가능한 후보의 순위를 직접 매기고, `fill-first`는 이 창의 임계값과 소진 규칙에 따라 안정 순서로 이동하며, `round-robin`은 이 설정을 무시합니다. 쿨다운, failover 한도, 재인증 가능 여부는 별도의 로컬 상태로 유지됩니다. 계정별 주간 막대는 대시보드의 프로바이더 페이지에서 조회한 뒤에만 알 수 있습니다. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 성공한 새 세션 결속이 한 번의 라운드로빈 선택에 유지되는 횟수입니다. 범위는 1–100입니다. | 활성화되면 429 레코드가 `Retry-After` 또는 기본 backoff에서 제한된 쿨다운을 기록하고, 요청 안에서 회전할 수 있습니다. 결속은 프로세스 로컬이며 크기가 제한됩니다. 자격 증명 401/403은 해당 계정이 재인증이 필요함을 표시합니다. 적격한 계정이 모두 쿨다운 중이면, 클라이언트는 인증 오류가 아니라 알려진 경우 `Retry-After`가 포함된 429를 받습니다. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 1c0e942b05..842a5a7136 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -285,7 +285,7 @@ rotation may trigger provider restrictions. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables quota picking. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; `quota` ranks accounts by the window set by `quotaWindow`, and `fill-first` evaluates its drain threshold in that same window. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores the highest known bar, so it can use 5-hour usage before weekly usage is available; if neither is known, the account follows unknown-usage ordering. Known usage ranks before unknown usage, but if every eligible account is unknown, selection still returns one in eligible order. After the documented lower-5-hour tie-break, exact ties preserve eligible order. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and terminal recovery, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores the highest known bar, so it can use 5-hour usage before weekly usage is available; if neither is known, the account follows unknown-usage ordering. Known usage ranks before unknown usage, but if every eligible account is unknown, selection still returns one in eligible order. After the documented lower-5-hour tie-break, exact ties preserve eligible order. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and routing recovery after an eligible 429 replacement, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index 7b3b4b2d16..4714edee39 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -196,7 +196,7 @@ reauth или порога исчерпания; здоровые привяза | `anthropicAccountPool.enabled?` | `boolean` | `false` | Включить sticky affinity и cooldown failover на 429. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Для новых сессий выбирать аккаунт с наименьшим известным cached usage в настроенном окне, если активный аккаунт достиг порога. `0` отключает выбор по quota. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Стратегия для новых сессий; `quota` ранжирует аккаунты по окну, заданному в `quotaWindow` (по умолчанию это 5-hour bar'ы), а `fill-first` в этом же окне оценивает свой порог исчерпания. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Кешированная полоса использования, сообщённая провайдером и применяемая при выборе по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует наибольшее известное значение, поэтому до появления недельных данных может использовать 5-hour usage; если неизвестны оба значения, аккаунт следует порядку unknown usage. Известное использование ранжируется раньше unknown, но если у всех доступных аккаунтов оно неизвестно, выбирается аккаунт в доступном порядке. После описанного сравнения по меньшему 5-hour usage полное равенство также сохраняет этот порядок. Здоровая сессия с affinity не перебалансируется заранее. При назначении новой сессии и восстановлении после терминальной ошибки `quota` напрямую ранжирует доступных кандидатов по этому окну, `fill-first` идёт в стабильном порядке с учётом порога и правил исчерпания этого окна, а `round-robin` игнорирует настройку. Cooldown, лимиты failover и допустимость повторной аутентификации остаются отдельным локальным состоянием. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Кешированная полоса использования, сообщённая провайдером и применяемая при выборе по использованию. `five-hour` сохраняет прежнее поведение. `weekly` использует недельный bar и пропускает аккаунты с исчерпанным 5-hour bar, пока остаётся другой доступный аккаунт, но возвращается к ним, если других нет. `max-utilization` использует наибольшее известное значение, поэтому до появления недельных данных может использовать 5-hour usage; если неизвестны оба значения, аккаунт следует порядку unknown usage. Известное использование ранжируется раньше unknown, но если у всех доступных аккаунтов оно неизвестно, выбирается аккаунт в доступном порядке. После описанного сравнения по меньшему значению 5-hour usage полное равенство также сохраняет этот порядок. Здоровая сессия с affinity не перебалансируется заранее. При назначении новой сессии и восстановлении маршрутизации после допустимой замены при 429 `quota` напрямую ранжирует доступных кандидатов по этому окну, `fill-first` идёт в стабильном порядке с учётом порога и правил исчерпания этого окна, а `round-robin` игнорирует настройку. Cooldown, лимиты failover и допустимость повторной аутентификации остаются отдельным локальным состоянием. Недельные bar'ы аккаунтов известны только после опроса на странице Providers в dashboard. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Сколько успешных bind'ов новых сессий удерживать на одном выборе round-robin. Диапазон 1–100. | Если функция включена, 429 записывает ограниченный cooldown из `Retry-After` или из default diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index afc770d2c7..ee05f61387 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -221,7 +221,7 @@ ve otomatik rotasyon sağlayıcı kısıtlamalarını tetikleyebilir. | `anthropicAccountPool.enabled?` | `boolean` | `false` | Yapışkan bağlılığı ve 429 soğuma yük devretmesini etkinleştirin. | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | Yeni oturumlarda etkin hesap bu eşiğe ulaştığında, yapılandırılan penceredeki bilinen en düşük önbelleğe alınmış kullanımı seçin. `0` kota seçimini devre dışı bırakır. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | Yeni oturum stratejisi; `quota`, `quotaWindow` ile belirlenen pencereye (varsayılan 5 saatlik çubuklar) göre hesapları sıralar ve `fill-first` de tükenme eşiğini aynı pencerede değerlendirir. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminde kullanılan, sağlayıcının bildirdiği önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` bilinen en yüksek değeri kullanır; haftalık değer henüz yokken 5 saatlik değeri kullanabilir, ikisi de bilinmiyorsa hesap unknown kullanım sırasını izler. Bilinen kullanım unknown değerlerden önce gelir; tüm uygun hesaplar unknown olsa bile uygun sıradaki bir hesap seçilir. Belgelenen daha düşük 5 saatlik kullanım eşitlik bozmasından sonra tam eşitlikte de uygun sıra korunur. Sağlıklı affinity oturumları önceden yeniden dengelenmez. Yeni oturum ataması ve terminal hata kurtarmasında `quota`, uygun adayları doğrudan bu pencereye göre sıralar; `fill-first`, bu pencerenin eşik ve tükenme kurallarıyla kararlı sırada ilerler; `round-robin` ayarı yok sayar. Cooldown, yük devretme sınırları ve yeniden kimlik doğrulama uygunluğu ayrı yerel durum olarak kalır. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | Kullanıma dayalı hesap seçiminde kullanılan, sağlayıcının bildirdiği önbelleğe alınmış kullanım çubuğu. `five-hour` mevcut davranışı korur. `weekly` haftalık çubuğu kullanır ve başka uygun hesap kaldığı sürece 5 saatlik çubuğu tükenmiş hesapları atlar; hiçbiri kalmazsa bu hesaplara geri döner. `max-utilization` bilinen en yüksek değeri kullanır; haftalık değer henüz yokken 5 saatlik değeri kullanabilir, ikisi de bilinmiyorsa hesap unknown kullanım sırasını izler. Bilinen kullanım unknown değerlerden önce gelir; tüm uygun hesaplar unknown olsa bile uygun sıradaki bir hesap seçilir. Belgelenen daha düşük 5 saatlik kullanım eşitlik bozmasından sonra tam eşitlikte de uygun sıra korunur. Sağlıklı affinity oturumları önceden yeniden dengelenmez. Yeni oturum ataması ve uygun bir 429 yedeğine geçildikten sonraki yönlendirme kurtarmasında `quota`, uygun adayları doğrudan bu pencereye göre sıralar; `fill-first`, bu pencerenin eşik ve tükenme kurallarıyla kararlı sırada ilerler; `round-robin` ayarı yok sayar. Cooldown, yük devretme sınırları ve yeniden kimlik doğrulama uygunluğu ayrı yerel durum olarak kalır. Hesap başına haftalık çubuklar ancak dashboard Sağlayıcılar sayfasında sorgulandıktan sonra bilinir. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Bir round-robin seçiminde tutulan başarılı yeni oturum bağlamaları. Aralık 1–100. | Etkinleştirildiğinde 429, `Retry-After`'dan veya varsayılan bir geri çekilmeden diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index 81f793e546..969df072f7 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -159,7 +159,7 @@ affinity。这些策略不能规避 provider enforcement。 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 启用粘性亲和性和 429 冷却故障转移。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 对于新会话,当活动账户达到此阈值时,选择配置窗口中已知缓存使用率最低的账户。`0` 会禁用配额选择。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新会话策略;`quota` 按 `quotaWindow` 指定的窗口(默认是 5 小时条形数据)对账户排序,`fill-first` 也在同一窗口中判定其耗尽阈值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时使用的、由提供商报告并缓存的用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用已知值中的最高值,因此每周用量尚不可用时仍可使用 5 小时用量;两者都未知时,账户遵循 unknown 用量排序。已知用量排在 unknown 之前,但如果所有可用账户都未知,仍会按可用顺序选择一个账户。在前述较低 5 小时用量的同分判定之后,完全相同时也保留可用顺序。不会主动重新平衡健康且已建立亲和性的会话。在新会话分配和终止性错误恢复中,`quota` 直接按此窗口对可用候选账户排序;`fill-first` 按此窗口的阈值和耗尽规则以稳定顺序前进;`round-robin` 忽略此设置。冷却状态、故障转移上限和重新认证资格仍是独立的本地状态。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 基于用量选择账户时使用的、由提供商报告并缓存的用量条。`five-hour` 保持原有行为。`weekly` 使用每周用量条,并在仍有其他可用账户时跳过 5 小时用量已耗尽的账户;若没有其他账户,则回退使用这些账户。`max-utilization` 使用已知值中的最高值,因此每周用量尚不可用时仍可使用 5 小时用量;两者都未知时,账户遵循 unknown 用量排序。已知用量排在 unknown 之前,但如果所有可用账户都未知,仍会按可用顺序选择一个账户。在前述较低 5 小时用量的同分判定之后,完全相同时也保留可用顺序。不会主动重新平衡健康且已建立亲和性的会话。在新会话分配和符合条件的 429 替代后的路由恢复中,`quota` 直接按此窗口对可用候选账户排序;`fill-first` 按此窗口的阈值和耗尽规则以稳定顺序前进;`round-robin` 忽略此设置。冷却状态、故障转移上限和重新认证资格仍是独立的本地状态。各账户的每周用量只有在控制面板的提供商页面完成查询后才可用。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次轮询选择中保留的成功新会话绑定次数。范围 1–100。 | 启用后,429 会根据 `Retry-After` 记录有界冷却,或者使用默认退避,并且可能在同一请求内轮换。亲和性是进程本地的,并且有大小上限。凭据 401/403 会将账户标记为需要重新认证。如果所有合格账户都在冷却,客户端会在已知时收到带 `Retry-After` 的 429,而不是身份验证错误。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index cb30e54afa..166e105ac9 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -128,7 +128,7 @@ API-key 供應商可持有字面值金鑰或環境參考。OAuth 供應商使用 | `anthropicAccountPool.enabled?` | `boolean` | `false` | 啟用 sticky 親和性與 429 冷卻容錯移轉。 | | `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | 對於新 session,當目前帳號達到此閾值時,選擇設定視窗中最低的已知快取用量。`0` 停用配額挑選。 | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | 新 session 策略;`quota` 依 `quotaWindow` 指定的視窗(預設為 5 小時列)為帳號排序,`fill-first` 也在同一視窗中判定其排空閾值。 | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所採用、由供應商回報並快取的用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已用盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用已知值中的最高值,因此每週用量尚未取得時仍可使用 5 小時用量;兩者都未知時,帳號遵循 unknown 用量排序。已知用量排在 unknown 之前,但若所有可用帳號都是 unknown,仍會依可用順序選出一個。完成前述較低 5 小時用量的同分判定後,完全相同時也保留可用順序。不會主動重新平衡健康且已有 affinity 的 session。在分配新 session 與終止錯誤復原時,`quota` 直接依此視窗排序可用候選帳號;`fill-first` 依此視窗的門檻與用盡規則按穩定順序前進;`round-robin` 忽略此設定。冷卻狀態、容錯移轉上限與重新驗證資格仍是獨立的本機狀態。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | 使用量型帳號選擇所採用、由供應商回報並快取的用量列。`five-hour` 保留原有行為。`weekly` 使用每週用量列,並在仍有其他可用帳號時略過 5 小時用量已用盡的帳號;若沒有其他帳號,則退回使用這些帳號。`max-utilization` 使用已知值中的最高值,因此每週用量尚未取得時仍可使用 5 小時用量;兩者都未知時,帳號遵循 unknown 用量排序。已知用量排在 unknown 之前,但若所有可用帳號都是 unknown,仍會依可用順序選出一個。完成前述較低 5 小時用量的同分判定後,完全相同時也保留可用順序。不會主動重新平衡健康且已有 affinity 的 session。在分配新 session 與符合條件的 429 替代後進行路由復原時,`quota` 直接依此視窗排序可用候選帳號;`fill-first` 依此視窗的門檻與用盡規則按穩定順序前進;`round-robin` 忽略此設定。冷卻狀態、容錯移轉上限與重新驗證資格仍是獨立的本機狀態。每個帳號的每週用量只有在 dashboard 的供應商頁面完成查詢後才可得知。 | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | 在一次 round-robin 選擇上保留的成功新 session 綁定。範圍 1–100。 | 啟用時,429 記錄來自 `Retry-After` 或預設 backoff 的有界冷卻,並可能在請求內輪換。親和性為行程本地且有界。憑證 401/403 將帳號標記為需要重新認證。若所有合格帳號都在冷卻,客戶端收到附帶已知 `Retry-After` 的 429,而非認證錯誤。 From d8bf7066249ff4cd3adf4805af6e00ed95f4fc64 Mon Sep 17 00:00:00 2001 From: Yoonkeee Date: Sun, 30 Aug 2026 02:44:59 +0900 Subject: [PATCH 19/24] fix(anthropic): rank known quota before unknown --- src/oauth/anthropic-routing.ts | 3 +++ tests/anthropic-account-pool.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index b982535b83..a375e7d9a6 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -252,11 +252,13 @@ export function getAnthropicPoolRetryAfterSeconds(now = Date.now()): number | nu interface ScoredAccount { accountId: string; + hasKnownUsage: boolean; score: number; fiveHourTieBreak: number; } function compareScoredAccounts(a: ScoredAccount, b: ScoredAccount): number { + if (a.hasKnownUsage !== b.hasKnownUsage) return a.hasKnownUsage ? -1 : 1; return a.score - b.score || a.fiveHourTieBreak - b.fiveHourTieBreak; } @@ -268,6 +270,7 @@ function pickLowestUsage(config: OcxConfig, excludeId: string | undefined, now: if (eligible.length === 0) return null; const scored: ScoredAccount[] = eligible.map(accountId => ({ accountId, + hasKnownUsage: hasKnownUsage(config, accountId), score: usageScore(config, accountId), fiveHourTieBreak: window === "five-hour" ? 0 : fiveHourScore(accountId), })); diff --git a/tests/anthropic-account-pool.test.ts b/tests/anthropic-account-pool.test.ts index d6af8fad33..7afb408b33 100644 --- a/tests/anthropic-account-pool.test.ts +++ b/tests/anthropic-account-pool.test.ts @@ -473,6 +473,15 @@ describe("anthropic account pool quota window scoring", () => { expect(resolveAnthropicAccountForSession("weekly-unknown-last", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(aId); }); + test("weekly mode ranks known 100% before unknown weekly usage", async () => { + const { aId, bId } = await seedTwoAccounts(); + const updatedAt = Date.now(); + setCachedProviderAccountQuotaForTests("anthropic", aId, { fiveHourPercent: 90, weeklyPercent: 100, updatedAt }); + setCachedProviderAccountQuotaForTests("anthropic", bId, { fiveHourPercent: 0, updatedAt }); + + expect(resolveAnthropicAccountForSession("weekly-known-100-before-unknown", cfg(true, 80, { quotaWindow: "weekly" })).accountId).toBe(aId); + }); + test("weekly tie breaks by lower five-hour usage", async () => { const { aId, bId } = await seedTwoAccounts(); const updatedAt = Date.now(); @@ -509,6 +518,21 @@ describe("anthropic account pool quota window scoring", () => { expect(resolveAnthropicAccountForSession("max-five-hour-tie", cfg(true, 80, { quotaWindow: "max-utilization" })).accountId).toBe(bId); }); + test("max-utilization ranks known 100% before fully unknown usage", async () => { + const { bId, cId } = await seedThreeAccounts(); + setCachedProviderAccountQuotaForTests("anthropic", bId, { + fiveHourPercent: 100, + updatedAt: Date.now(), + }); + + expect(rotateAnthropicAccountOn429( + cfg(true, 80, { quotaWindow: "max-utilization" }), + cId, + "30", + "max-known-100-before-unknown", + )).toBe(bId); + }); + test("omitted quotaWindow equals explicit five-hour", async () => { const { aId, bId } = await seedTwoAccounts(); const updatedAt = Date.now(); From af58ceb076bc423892929da8e9cd7b00f1def202 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 30 Aug 2026 13:34:00 +0900 Subject: [PATCH 20/24] fix(anthropic): keep known-before-unknown ranking inside the opt-in weekly window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Security review finding: the new comparator applied known-before-unknown ordering unconditionally, so an operator who never opted into weekly selection still got different five-hour ordering — an account measured at 100% sorted ahead of an unmeasured one purely because it had a reading. The accepted scope for #2539 preserves the five-hour default exactly, so the rule is now gated on the weekly window it belongs to. --- src/oauth/anthropic-routing.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index a375e7d9a6..b0e183adc0 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -255,10 +255,18 @@ interface ScoredAccount { hasKnownUsage: boolean; score: number; fiveHourTieBreak: number; + /** True only under the opt-in weekly window; see compareScoredAccounts. */ + knownFirst: boolean; } function compareScoredAccounts(a: ScoredAccount, b: ScoredAccount): number { - if (a.hasKnownUsage !== b.hasKnownUsage) return a.hasKnownUsage ? -1 : 1; + // known-before-unknown belongs to the OPT-IN weekly selector, not the legacy default. + // Applying it unconditionally changed five-hour ordering for operators who never opted in: + // an account measured at 100% would sort ahead of an unmeasured one purely because it had + // a reading. The accepted scope preserves the five-hour default exactly. + if (a.knownFirst && b.knownFirst && a.hasKnownUsage !== b.hasKnownUsage) { + return a.hasKnownUsage ? -1 : 1; + } return a.score - b.score || a.fiveHourTieBreak - b.fiveHourTieBreak; } @@ -273,6 +281,7 @@ function pickLowestUsage(config: OcxConfig, excludeId: string | undefined, now: hasKnownUsage: hasKnownUsage(config, accountId), score: usageScore(config, accountId), fiveHourTieBreak: window === "five-hour" ? 0 : fiveHourScore(accountId), + knownFirst: window === "weekly", })); let best = scored[0]!; for (let i = 1; i < scored.length; i++) { From 43e60a662c2607da2937939dfae73ebf58438ce0 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 30 Aug 2026 13:34:37 +0900 Subject: [PATCH 21/24] fix(anthropic): scope known-first ranking to every opt-in window, not just weekly --- src/oauth/anthropic-routing.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/oauth/anthropic-routing.ts b/src/oauth/anthropic-routing.ts index b0e183adc0..7930a5b185 100644 --- a/src/oauth/anthropic-routing.ts +++ b/src/oauth/anthropic-routing.ts @@ -260,10 +260,10 @@ interface ScoredAccount { } function compareScoredAccounts(a: ScoredAccount, b: ScoredAccount): number { - // known-before-unknown belongs to the OPT-IN weekly selector, not the legacy default. - // Applying it unconditionally changed five-hour ordering for operators who never opted in: - // an account measured at 100% would sort ahead of an unmeasured one purely because it had - // a reading. The accepted scope preserves the five-hour default exactly. + // known-before-unknown belongs to the OPT-IN windows (weekly, max-utilization), not the + // legacy five-hour default. Applying it unconditionally changed ordering for operators who + // never opted in: an account measured at 100% would sort ahead of an unmeasured one purely + // because it had a reading. The accepted scope preserves the five-hour default exactly. if (a.knownFirst && b.knownFirst && a.hasKnownUsage !== b.hasKnownUsage) { return a.hasKnownUsage ? -1 : 1; } @@ -281,7 +281,9 @@ function pickLowestUsage(config: OcxConfig, excludeId: string | undefined, now: hasKnownUsage: hasKnownUsage(config, accountId), score: usageScore(config, accountId), fiveHourTieBreak: window === "five-hour" ? 0 : fiveHourScore(accountId), - knownFirst: window === "weekly", + // Every window EXCEPT the legacy five-hour default is an explicit opt-in, so + // known-before-unknown applies to all of them and to none of the default path. + knownFirst: window !== "five-hour", })); let best = scored[0]!; for (let i = 1; i < scored.length; i++) { From 5ec56ef041d070eb3270aeabef319c9fb9d9b549 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 30 Aug 2026 13:34:58 +0900 Subject: [PATCH 22/24] test(anthropic): pin that the five-hour default keeps its legacy ranking --- tests/anthropic-account-pool.test.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/anthropic-account-pool.test.ts b/tests/anthropic-account-pool.test.ts index 7afb408b33..5f15fedf52 100644 --- a/tests/anthropic-account-pool.test.ts +++ b/tests/anthropic-account-pool.test.ts @@ -533,6 +533,28 @@ describe("anthropic account pool quota window scoring", () => { )).toBe(bId); }); + test("five-hour default does not adopt known-before-unknown ranking", async () => { + // The known-first rule belongs to the opt-in windows. Under the legacy five-hour default + // a measured 100% account must NOT outrank an unmeasured one just for having a reading — + // that would change routing for operators who never opted into anything. + const { bId, cId } = await seedThreeAccounts(); + setCachedProviderAccountQuotaForTests("anthropic", bId, { + fiveHourPercent: 100, + updatedAt: Date.now(), + }); + + // Omitted window (legacy default) and the explicit five-hour spelling must agree, and + // neither may promote the exhausted-but-measured account the way max-utilization does. + expect(rotateAnthropicAccountOn429(cfg(true, 80), cId, "30", "five-hour-default-known")) + .not.toBe(bId); + expect(rotateAnthropicAccountOn429( + cfg(true, 80, { quotaWindow: "five-hour" }), + cId, + "30", + "five-hour-explicit-known", + )).not.toBe(bId); + }); + test("omitted quotaWindow equals explicit five-hour", async () => { const { aId, bId } = await seedTwoAccounts(); const updatedAt = Date.now(); From 6f7b1fdc7d135cfde42064cb5e20b1d2d9b9bcad Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 30 Aug 2026 17:16:47 +0900 Subject: [PATCH 23/24] fix(gui): make the pool quota-window UI stage-specific at threshold 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review finding: the settings card treated the quota window as inert for fill-first when autoSwitchThreshold===0, and its description said quota-based selection was off entirely. Neither is true. A 0 threshold disables PROACTIVE usage-based switching only — new-session selection (pickLowestUsage) and 429 recovery (rotateAnthropicAccountOn429) still consult the configured window. The selector is now disabled only under round-robin, which genuinely never scores a usage bar at any stage. The threshold-0 copy names the stage that stops and the two that continue, and identifies the window still in effect, across all nine locale bundles. The two existing tests asserted the old claims, so they are updated rather than left to enforce the inaccuracy. --- .../AnthropicAccountPoolSettings.tsx | 14 ++++++++++---- gui/src/i18n/de.ts | 2 +- gui/src/i18n/en.ts | 2 +- gui/src/i18n/fr.ts | 2 +- gui/src/i18n/ja.ts | 2 +- gui/src/i18n/ko.ts | 2 +- gui/src/i18n/ru.ts | 2 +- gui/src/i18n/tr.ts | 2 +- gui/src/i18n/zh-TW.ts | 2 +- gui/src/i18n/zh.ts | 2 +- gui/tests/anthropic-pool-quota-window.test.tsx | 14 +++++++++----- 11 files changed, 28 insertions(+), 18 deletions(-) diff --git a/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx b/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx index 699331c805..4d7c66b970 100644 --- a/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx +++ b/gui/src/components/provider-workspace/AnthropicAccountPoolSettings.tsx @@ -161,9 +161,13 @@ export default function AnthropicAccountPoolSettings({ const strategy = state?.strategy ?? DEFAULT_ACCOUNT_POOL_STRATEGY; const stickyLimit = state?.stickyLimit ?? DEFAULT_ACCOUNT_POOL_STICKY_LIMIT; const quotaWindow = state?.quotaWindow ?? DEFAULT_ACCOUNT_POOL_QUOTA_WINDOW; - // Only quota scores a usage bar; fill-first scores one too, but a 0 threshold turns its - // drain point off. Neither reads a bar under round-robin, so the window is inert there. - const quotaWindowInert = strategy !== "quota" && !(strategy === "fill-first" && threshold > 0); + // The window is inert ONLY under round-robin, which never scores a usage bar at any stage. + // + // A 0 threshold is not inertness: it disables PROACTIVE usage-based switching, but + // new-session selection and 429 recovery still consult the configured window (see + // pickLowestUsage / rotateAnthropicAccountOn429). Treating fill-first + threshold 0 as + // inert told operators the window had no effect when it still governed two routing stages. + const quotaWindowInert = strategy === "round-robin"; const loading = state === null && !loadError; // Always allow turning the pool off; only block enabling when fewer than 2 accounts. const toggleDisabled = loading || saving || loadError || (!enabled && accountCount < 2); @@ -180,7 +184,9 @@ export default function AnthropicAccountPoolSettings({ ? t("common.loading") : enabled ? threshold === 0 - ? t("anthropicPool.enabledNoQuotaDesc") + ? t("anthropicPool.enabledNoProactiveDesc", { + window: t(QUOTA_WINDOW_LABEL_KEYS[quotaWindow]), + }) : t("anthropicPool.enabledDesc", { threshold, window: t(QUOTA_WINDOW_LABEL_KEYS[quotaWindow]), diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 180c1a33a1..36d058a311 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1230,7 +1230,7 @@ export const de: Record = { "codexAuth.catalogRefreshPending": "Die Änderung wurde gespeichert, aber die Aktualisierung des Codex-Modellkatalogs steht noch aus. Führe ocx sync aus, um es erneut zu versuchen.", "anthropicPool.title": "Claude-Kontenpool (experimentell)", "anthropicPool.enabledDesc": "Bei 429 wird das Konto gekühlt und umgeschaltet. Neue Sitzungen bevorzugen Nutzung unter {threshold}% ({window}).", - "anthropicPool.enabledNoQuotaDesc": "Bei 429 wird das Konto gekühlt und umgeschaltet. Die kontingentbasierte Auswahl neuer Sitzungen ist deaktiviert; gesunde Affinitäten und das aktive Konto bleiben bestehen.", + "anthropicPool.enabledNoProactiveDesc": "Bei 429 wird das Konto gekühlt und umgeschaltet. Proaktives nutzungsbasiertes Umschalten ist bei Schwellenwert 0 deaktiviert, aber die Auswahl neuer Sitzungen und die 429-Wiederherstellung verwenden weiterhin das Fenster {window}.", "anthropicPool.disabledDesc": "Nutzt nur das aktive Claude-Konto. Nur aktivieren, wenn experimentelles Routing akzeptabel ist.", "anthropicPool.experimentalWarning": "Experimentell und nicht kampferprobt. Anthropic kann Konten einschränken, die wie automatische Multi-Konto-Rotation wirken. Dieselbe Organisation kann Kontingent teilen — Pooling hilft dann nicht. Ausgeschaltet lassen, sofern das Risiko unklar ist.", "anthropicPool.needTwoAccounts": "Füge mindestens zwei Claude-OAuth-Konten hinzu, bevor du den Pool aktivierst.", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 1315a52a75..703fa71d09 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1725,7 +1725,7 @@ export const en = { "anthropicPool.title": "Claude account pool (experimental)", "anthropicPool.enabledDesc": "On 429, cools the account and fails over. New sessions prefer usage under {threshold}% ({window}).", - "anthropicPool.enabledNoQuotaDesc": "On 429, cools the account and fails over. Quota-based new-session selection is off, so healthy affinity and active-account routing stay in place.", + "anthropicPool.enabledNoProactiveDesc": "On 429, cools the account and fails over. Proactive usage-based switching is off at threshold 0, but new-session selection and 429 recovery still use the {window} window.", "anthropicPool.disabledDesc": "Uses only the active Claude account. Enable only if you accept experimental routing.", "anthropicPool.experimentalWarning": "Experimental and not battle-tested. Anthropic may restrict accounts that look like automated multi-account rotation. Same organization can share quota — pooling those accounts will not help. Keep this off unless you understand the risk.", "anthropicPool.needTwoAccounts": "Add at least two Claude OAuth accounts before enabling the pool.", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index 355bdf9676..5055c6b56b 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1697,7 +1697,7 @@ export const fr: Record = { "codexAuth.catalogRefreshPending": "La modification a été enregistrée, mais l’actualisation du catalogue de modèles Codex est en attente. Exécutez ocx sync pour réessayer.", "anthropicPool.title": "Groupe de comptes Claude (expérimental)", "anthropicPool.enabledDesc": "En cas de 429, met le compte en délai de récupération et bascule vers un autre. Les nouvelles sessions privilégient une utilisation inférieure à {threshold}% ({window}).", - "anthropicPool.enabledNoQuotaDesc": "En cas de 429, met le compte en délai de récupération et bascule vers un autre. La sélection des nouvelles sessions par quota est désactivée ; les affinités saines et le compte actif sont conservés.", + "anthropicPool.enabledNoProactiveDesc": "En cas de 429, met le compte en délai de récupération et bascule. Le basculement proactif basé sur l'usage est désactivé au seuil 0, mais la sélection des nouvelles sessions et la récupération après 429 utilisent toujours la fenêtre {window}.", "anthropicPool.disabledDesc": "Utilise uniquement le compte Claude actif. Activez cette option seulement si vous acceptez le routage expérimental.", "anthropicPool.experimentalWarning": "Fonctionnalité expérimentale et peu éprouvée. Anthropic peut restreindre les comptes présentant une rotation multicomptes automatisée. Les comptes d’une même organisation peuvent partager un quota — leur mise en groupe n’apportera rien. Laissez cette option désactivée si vous n’en comprenez pas les risques.", "anthropicPool.needTwoAccounts": "Ajoutez au moins deux comptes OAuth Claude avant d’activer le groupe.", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index eff0ab1674..69920a3510 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1658,7 +1658,7 @@ export const ja: Record = { "codexAuth.catalogRefreshPending": "変更は保存されましたが、Codex モデルカタログの更新が保留中です。ocx sync を実行して再試行してください。", "anthropicPool.title": "Claude アカウントプール(実験的)", "anthropicPool.enabledDesc": "429 時にアカウントをクールダウンしてフェイルオーバーします。新規セッションは{window}の使用率が {threshold}% 未満のアカウントを優先します。", - "anthropicPool.enabledNoQuotaDesc": "429 時にアカウントをクールダウンしてフェイルオーバーします。クォータに基づく新規セッション選択は無効で、正常な affinity とアクティブアカウントのルーティングを維持します。", + "anthropicPool.enabledNoProactiveDesc": "429 時にアカウントをクールダウンしてフェイルオーバーします。しきい値 0 では使用量に基づく事前切り替えは無効ですが、新規セッション選択と 429 復旧では引き続き {window} ウィンドウを使用します。", "anthropicPool.disabledDesc": "アクティブな Claude アカウントのみを使用します。実験的ルーティングを受け入れる場合のみ有効にしてください。", "anthropicPool.experimentalWarning": "実験的で十分に検証されていません。自動的な複数アカウント回転に見える行為は Anthropic により制限される可能性があります。同一組織はクォータを共有することがあり、その場合プールしても効果がありません。リスクを理解していない場合はオフのままにしてください。", "anthropicPool.needTwoAccounts": "プールを有効にする前に、Claude OAuth アカウントを 2 つ以上追加してください。", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 1ee0d5eba6..b2d78471fd 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1254,7 +1254,7 @@ export const ko: Record = { "codexAuth.catalogRefreshPending": "변경 사항은 저장되었지만 Codex 모델 카탈로그 새로 고침이 보류 중입니다. ocx sync를 실행해 다시 시도하세요.", "anthropicPool.title": "Claude 계정 풀(실험적)", "anthropicPool.enabledDesc": "429 시 계정을 쿨다운하고 장애 조치합니다. 새 세션은 {window}이 {threshold}% 미만인 계정을 우선합니다.", - "anthropicPool.enabledNoQuotaDesc": "429 시 계정을 쿨다운하고 장애 조치합니다. 할당량 기반 새 세션 선택은 꺼지고, 정상 어피니티와 활성 계정 라우팅은 유지됩니다.", + "anthropicPool.enabledNoProactiveDesc": "429 시 계정을 쿨다운하고 장애 조치합니다. 임계값 0에서는 사용량 기반 사전 전환이 꺼지지만, 새 세션 선택과 429 복구는 여전히 {window} 창을 사용합니다.", "anthropicPool.disabledDesc": "활성 Claude 계정만 사용합니다. 실험적 라우팅을 감수할 때만 켜세요.", "anthropicPool.experimentalWarning": "실험적이며 충분히 검증되지 않았습니다. 자동 다중 계정 로테이션처럼 보이는 동작은 Anthropic이 계정을 제한할 수 있습니다. 같은 조직은 할당량을 공유할 수 있어 풀링이 도움이 되지 않을 수 있습니다. 위험을 이해하지 못하면 꺼 두세요.", "anthropicPool.needTwoAccounts": "풀을 켜기 전에 Claude OAuth 계정을 두 개 이상 추가하세요.", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index c5aef47e27..72e743a819 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1709,7 +1709,7 @@ export const ru: Record = { "codexAuth.catalogRefreshPending": "Изменение сохранено, но обновление каталога моделей Codex ещё не завершено. Выполните ocx sync, чтобы повторить попытку.", "anthropicPool.title": "Пул аккаунтов Claude (экспериментально)", "anthropicPool.enabledDesc": "При 429 аккаунт охлаждается и выполняется переключение. Новые сессии предпочитают использование ниже {threshold}% ({window}).", - "anthropicPool.enabledNoQuotaDesc": "При 429 аккаунт охлаждается и выполняется переключение. Выбор новых сессий по квоте отключён; сохраняются здоровая привязка и маршрутизация через активный аккаунт.", + "anthropicPool.enabledNoProactiveDesc": "При 429 аккаунт охлаждается и выполняется переключение. При пороге 0 упреждающее переключение по использованию отключено, но выбор новых сессий и восстановление после 429 по-прежнему используют окно {window}.", "anthropicPool.disabledDesc": "Используется только активный аккаунт Claude. Включайте только если принимаете экспериментальную маршрутизацию.", "anthropicPool.experimentalWarning": "Экспериментально и недостаточно проверено. Anthropic может ограничить аккаунты, похожие на автоматическую ротацию. Одна организация может делить квоту — пул таких аккаунтов не поможет. Оставляйте выключенным, если не понимаете риск.", "anthropicPool.needTwoAccounts": "Перед включением пула добавьте минимум два OAuth-аккаунта Claude.", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 9de3c1f4f2..4239246487 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1716,7 +1716,7 @@ export const tr: Record = { "anthropicPool.title": "Claude hesap havuzu (deneysel)", "anthropicPool.enabledDesc": "429 alındığında hesabı bekletir ve başka bir hesaba geçer. Yeni oturumlar {window} değerine göre %{threshold} altında kullanıma sahip hesapları tercih eder.", - "anthropicPool.enabledNoQuotaDesc": "429 alındığında hesabı bekletir ve başka bir hesaba geçer. Kotaya dayalı yeni oturum seçimi kapalıdır; sağlıklı oturum bağlılığı ve aktif hesap yönlendirmesi korunur.", + "anthropicPool.enabledNoProactiveDesc": "429 alındığında hesabı bekletir ve başka bir hesaba geçer. Eşik 0 iken kullanıma dayalı öngörülü geçiş kapalıdır, ancak yeni oturum seçimi ve 429 kurtarma hâlâ {window} penceresini kullanır.", "anthropicPool.disabledDesc": "Yalnızca aktif Claude hesabını kullanır.", "anthropicPool.experimentalWarning": "Deneysel: Claude OAuth hesaplarını döndürmek desteklenmeyen bir kullanım yoludur ve Anthropic hesap kısıtlamalarına veya hesabın askıya alınmasına yol açabilir. Aynı kuruluşu paylaşan hesaplar oran limitlerini paylaşır ve döndürmeden ek kapasite kazanmaz. Riskleri anlamıyorsanız kapalı tutun.", "anthropicPool.needTwoAccounts": "Havuzu etkinleştirmeden önce en az iki Claude OAuth hesabı ekleyin.", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 58d3453b07..5c74b86447 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -1329,7 +1329,7 @@ export const zhTW: Record = { "codexAuth.pausedHint": "恢復前不會參與自動切換、重試、冷卻恢復或手動選擇。", "anthropicPool.title": "Claude 帳號池(實驗性)", "anthropicPool.enabledDesc": "遇到 429 時冷卻該帳號並故障轉移。新會話優先使用{window}低於 {threshold}% 的帳號。", - "anthropicPool.enabledNoQuotaDesc": "遇到 429 時冷卻該帳號並故障轉移。依配額選擇新會話的功能已關閉,健康的 affinity 與目前帳號路由會維持不變。", + "anthropicPool.enabledNoProactiveDesc": "429 時將帳號冷卻並切換。門檻為 0 時停用主動的用量切換,但新工作階段選擇與 429 復原仍會使用 {window} 視窗。", "anthropicPool.disabledDesc": "僅使用當前活躍的 Claude 帳號。僅在接受實驗性路由時啟用。", "anthropicPool.experimentalWarning": "實驗性功能,尚未充分驗證。看起來像自動多帳號輪換的行為可能導致 Anthropic 限制帳號。同一組織可能共享配額——對這些帳號做池化沒有幫助。除非瞭解風險,否則請保持關閉。", "anthropicPool.needTwoAccounts": "啟用帳號池前請至少新增兩個 Claude OAuth 帳號。", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 21147a4ff4..87001cbb0f 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1247,7 +1247,7 @@ export const zh: Record = { "codexAuth.catalogRefreshPending": "更改已保存,但 Codex 模型目录仍待刷新。请运行 ocx sync 重试。", "anthropicPool.title": "Claude 账户池(实验性)", "anthropicPool.enabledDesc": "遇到 429 时冷却该账户并故障转移。新会话优先使用{window}低于 {threshold}% 的账户。", - "anthropicPool.enabledNoQuotaDesc": "遇到 429 时冷却该账户并故障转移。按配额选择新会话的功能已关闭,健康的会话亲和性和当前活跃账户路由保持不变。", + "anthropicPool.enabledNoProactiveDesc": "429 时冷却账号并切换。阈值为 0 时停用主动的用量切换,但新会话选择与 429 恢复仍会使用 {window} 窗口。", "anthropicPool.disabledDesc": "仅使用当前活跃的 Claude 账户。仅在接受实验性路由时启用。", "anthropicPool.experimentalWarning": "实验性功能,尚未充分验证。看起来像自动多账户轮换的行为可能导致 Anthropic 限制账户。同一组织可能共享配额——对这些账户做池化没有帮助。除非了解风险,否则请保持关闭。", "anthropicPool.needTwoAccounts": "启用账户池前请至少添加两个 Claude OAuth 账户。", diff --git a/gui/tests/anthropic-pool-quota-window.test.tsx b/gui/tests/anthropic-pool-quota-window.test.tsx index ea37be9a59..f0a5bfeed0 100644 --- a/gui/tests/anthropic-pool-quota-window.test.tsx +++ b/gui/tests/anthropic-pool-quota-window.test.tsx @@ -144,7 +144,7 @@ describe("Anthropic account pool quota window", () => { expect(windowTrigger(fillHost).disabled).toBe(false); }); - test("quota window selector is disabled for round-robin and for fill-first with threshold 0", async () => { + test("quota window selector is disabled only for round-robin", async () => { stubPool({ enabled: true, autoSwitchThreshold: 80, @@ -165,11 +165,13 @@ describe("Anthropic account pool quota window", () => { }); const drainedHost = await mountPool(); - // fill-first only drains against a threshold; at 0 there is no bar to score. - expect(windowTrigger(drainedHost).disabled).toBe(true); + // A 0 threshold disables PROACTIVE usage-based switching only. New-session selection and + // 429 recovery still consult the configured window, so disabling the selector here told + // the operator the setting was inert while it still governed two routing stages. + expect(windowTrigger(drainedHost).disabled).toBe(false); }); - test("threshold zero description says quota-based new-session selection is off", async () => { + test("threshold zero says proactive switching is off, not all quota routing", async () => { stubPool({ enabled: true, autoSwitchThreshold: 0, @@ -179,7 +181,9 @@ describe("Anthropic account pool quota window", () => { }); const host = await mountPool(); - expect(host.textContent).toContain("Quota-based new-session selection is off"); + expect(host.textContent).toContain("Proactive usage-based switching is off"); + // The stages that still run must be named, and the window must still be identified. + expect(host.textContent).toContain("new-session selection and 429 recovery"); expect(host.textContent).not.toContain("prefer usage under 0%"); }); From 6b8b3a1c3b3ddf8147bc9543c87d91ad201319b3 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 30 Aug 2026 17:17:43 +0900 Subject: [PATCH 24/24] docs: narrow the threshold-0 and known-first claims to what actually runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review findings: the configuration reference said '0 disables quota picking' when it disables only proactive switching — new-session selection and 429 recovery still consult quotaWindow. It also stated known-before-unknown ordering unconditionally, when that rule is scoped to the opt-in weekly and max-utilization windows; five-hour keeps the legacy ordering. The Claude guide gains the same threshold-0 nuance and states that the window is inert only under round-robin. --- docs-site/src/content/docs/guides/claude-code.md | 3 +++ .../src/content/docs/reference/configuration/providers.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index 3b95d80553..d991c3a889 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -34,6 +34,9 @@ Operational contract when enabled: when known. - Recovery, including 429 failover, uses `quotaWindow` to rank eligible replacements without changing the existing cooldown or failover limits; `round-robin` ignores `quotaWindow`. +- `autoSwitchThreshold: 0` turns off **proactive** usage-based switching only. New-session + selection and 429 recovery still consult `quotaWindow`, so the window is inert only under + `round-robin`. `fill-first` evaluates its drain threshold in the selected window. See [Configuration](/reference/configuration/#anthropicaccountpool-experimental). diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 842a5a7136..11a6e9c256 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -283,9 +283,9 @@ rotation may trigger provider restrictions. | Key | Type | Default | Description | | --- | --- | --- | --- | | `anthropicAccountPool.enabled?` | `boolean` | `false` | Enable sticky affinity and 429 cooldown failover. | -| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables quota picking. | +| `anthropicAccountPool.autoSwitchThreshold?` | `number` | `80` | For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. `0` disables **proactive** usage-based switching only — new-session selection and routing recovery after an eligible 429 still consult `quotaWindow`. | | `anthropicAccountPool.strategy?` | `"quota" \| "round-robin" \| "fill-first"` | `"quota"` | New-session strategy; `quota` ranks accounts by the window set by `quotaWindow`, and `fill-first` evaluates its drain threshold in that same window. | -| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores the highest known bar, so it can use 5-hour usage before weekly usage is available; if neither is known, the account follows unknown-usage ordering. Known usage ranks before unknown usage, but if every eligible account is unknown, selection still returns one in eligible order. After the documented lower-5-hour tie-break, exact ties preserve eligible order. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and routing recovery after an eligible 429 replacement, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | +| `anthropicAccountPool.quotaWindow?` | `"five-hour" \| "weekly" \| "max-utilization"` | `"five-hour"` | The cached provider-reported utilization bar used for usage-aware account selection. `five-hour` keeps the original behavior. `weekly` scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. `max-utilization` scores the highest known bar, so it can use 5-hour usage before weekly usage is available; if neither is known, the account follows unknown-usage ordering. Known usage ranks before unknown usage under the opt-in `weekly` and `max-utilization` windows only; an omitted or explicit `five-hour` preserves the legacy ordering. If every eligible account is unknown, selection still returns one in eligible order. After the documented lower-5-hour tie-break, exact ties preserve eligible order. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and routing recovery after an eligible 429 replacement, `quota` ranks eligible candidates directly with this window; `fill-first` advances in stable order using this window's threshold and exhaustion rules; `round-robin` ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars are only known once the dashboard Providers page has polled them. | | `anthropicAccountPool.stickyLimit?` | `number` | `1` | Successful new-session binds retained on one round-robin selection. Range 1–100. | When enabled, 429 records bounded cooldown from `Retry-After` or a default backoff and may rotate