From 3a991a33a08c79532cb03dd66fc57038136861df Mon Sep 17 00:00:00 2001 From: t Date: Mon, 7 Sep 2026 01:43:52 +0900 Subject: [PATCH 1/2] fix(cli): report inert generic account thresholds truthfully --- .../fr/reference/cli/providers-accounts.md | 7 +-- .../ja/reference/cli/providers-accounts.md | 5 +- .../ko/reference/cli/providers-accounts.md | 5 +- .../docs/reference/cli/providers-accounts.md | 7 +-- .../ru/reference/cli/providers-accounts.md | 7 +-- .../tr/reference/cli/providers-accounts.md | 8 +-- .../zh-cn/reference/cli/providers-accounts.md | 7 +-- .../zh-tw/reference/cli/providers-accounts.md | 5 +- src/cli/account-extended.ts | 22 +++++++ tests/cli/cli-account-pool-verbs.test.ts | 62 ++++++++++++++++++- 10 files changed, 106 insertions(+), 29 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md index 53c5b94b91..c7c4ffa7ca 100644 --- a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md @@ -203,12 +203,11 @@ renvoient 1 ; une sonde de quota en amont qui échoue ou expire produit plutôt ### `ocx account auto-switch > [--json]` -Contrôle uniquement le groupe de comptes Codex `openai`. `on` règle 80 %, `off` règle 0 %, `status` lit la -valeur actuelle et `threshold ` accepte un entier de 0 à 100. Les autres fournisseurs et les valeurs -invalides entraînent le code de sortie 1. `--json` renvoie : +Contrôle le seuil du pool Codex `openai`, ou enregistre celui d’un pool OAuth générique. `on` enregistre 80 %, `off` 0 % et `threshold ` accepte 0–100. Les seuils génériques sont actuellement inactifs : leur sauvegarde ne change ni le basculement par seuil, ni l’activation du fournisseur, ni la rotation réactive après une erreur 429. Les sorties utilisent la réponse confirmée du serveur. Pour un pool générique, `poolEnabled` est le réglage enregistré (`null` signifie non spécifié), pas l’état effectif hérité. `inert: true` indique que le seuil ne s’applique pas ; une capacité inconnue ne produit jamais `enabled: true`. Les fournisseurs à clé API, Anthropic et les valeurs invalides sont refusés. ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md index f1b5ec4ff6..63596b369c 100644 --- a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md @@ -149,10 +149,11 @@ OAuth プロバイダーと API キー プロバイダーの場合、これに ### `ocx account auto-switch > [--json]` -`openai` Codex アカウント プールのみを制御します。 `on` は 80% を設定し、`off` は 0% を設定します。`status` は現在の値を読み取り、`threshold ` は 0 ~ 100 の整数を受け入れます。他のプロバイダーと無効な値は 1 を終了します。`--json` は次を返します。 +`openai` Codex プールのしきい値を制御するか、汎用 OAuth プールのしきい値を保存します。`on` は 80%、`off` は 0%、`threshold ` は 0–100 を保存します。汎用プールのしきい値は現在適用されません。保存しても、しきい値による切り替え、プロバイダーの有効化設定、429 エラー時のローテーションは変更されません。照会と変更の結果はサーバーの確認値を使用します。汎用プールの `poolEnabled` は保存された設定で、`null` は未指定です。継承後の実効状態ではありません。`inert: true` は未適用を示し、機能が不明な場合も `enabled: true` とは表示しません。API キープロバイダー、Anthropic、不正な値は拒否されます。 ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md index 31757f38d7..ff86f67e8d 100644 --- a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md @@ -209,10 +209,11 @@ OAuth 및 API 키 제공자에는 제공자의 할당량 보고 엔드포인트 ### `ocx account auto-switch > [--json]` -`openai` Codex 계정 풀만 제어합니다. `on`은 80%, `off`는 0%를 설정하고, `status`는 현재 값을 읽으며, `threshold `은 0부터 100까지의 정수를 받습니다. 다른 제공자와 잘못된 값은 종료 코드 1로 끝납니다. `--json`은 다음을 반환합니다: +`openai` Codex 풀의 임계값을 제어하거나 일반 OAuth 풀의 임계값을 저장합니다. `on`은 80%, `off`는 0%, `threshold `은 0–100을 저장합니다. 일반 풀의 임계값은 현재 동작에 적용되지 않습니다. 저장해도 임계값 기반 전환이나 제공자 활성화 설정이 바뀌지 않고, 429 오류에 따른 회전도 비활성화되지 않습니다. 조회와 변경 결과는 서버가 확인한 값을 사용합니다. 일반 풀의 `poolEnabled`는 저장된 제공자별 설정이며 `null`은 미지정입니다. 전역 설정을 상속한 실제 상태를 뜻하지 않습니다. `inert: true`이면 임계값이 적용되지 않으며, 기능 지원을 알 수 없을 때도 `enabled: true`로 표시하지 않습니다. API 키 제공자, Anthropic 및 잘못된 값은 거부합니다. ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index cc2471a527..7b9d9da16c 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -331,12 +331,11 @@ instead (exit 0), matching the dashboard's quota bars. ### `ocx account auto-switch > [--json]` -Controls only the `openai` Codex account pool. `on` sets 80%, `off` sets 0%, `status` reads the current -value, and `threshold ` accepts an integer from 0 through 100. Other providers and invalid values -exit 1. `--json` returns: +Controls the `openai` Codex pool threshold, or stores a threshold for a generic OAuth pool. `on` stores 80%, `off` stores 0%, and `threshold ` accepts 0–100. Generic pool thresholds are currently inert: saving one does not enable threshold-based switching, change the provider enablement override, or disable reactive 429 rotation. `status` and mutation output use the confirmed server response. For generic pools, `poolEnabled` is the stored provider override (`null` means unspecified), not inherited effective state; `inert: true` means the threshold is not applied, and unknown capability never reports `enabled: true`. API-key providers, Anthropic and invalid values are rejected. ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md index 3ad8e527f7..1e011a0dd5 100644 --- a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md @@ -187,12 +187,11 @@ quota-bar'ов дашборда. ### `ocx account auto-switch > [--json]` -Управляет только пулом аккаунтов Codex `openai`. `on` ставит 80%, `off` — 0%, `status` читает -текущее значение, а `threshold ` принимает целое число от 0 до 100. Для других провайдеров и -некорректных значений команда завершается кодом 1. `--json` возвращает: +Управляет порогом пула Codex `openai` или сохраняет порог общего пула OAuth. `on` сохраняет 80 %, `off` — 0 %, а `threshold ` принимает 0–100. Пороги общих пулов пока не применяются: сохранение не включает переключение по порогу, не меняет настройку включения провайдера и не отключает ротацию после ошибки 429. Результат чтения и изменения берётся из подтверждённого ответа сервера. Для общего пула `poolEnabled` — сохранённая настройка провайдера (`null` означает отсутствие настройки), а не итоговое унаследованное состояние. `inert: true` означает, что порог не применяется; неизвестная возможность также не даёт `enabled: true`. Провайдеры с ключом API, Anthropic и неверные значения отклоняются. ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md index 9c2e9378b7..92c0cbd2a8 100644 --- a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md @@ -229,13 +229,11 @@ eşleşen null veya eski bir rapora düşer (çıkış 0). ### `ocx account auto-switch > [--json]` -Yalnızca `openai` Codex hesap havuzunu denetler. `on` %80'i ayarlar, `off` %0'ı -ayarlar, `status` geçerli değeri okur ve `threshold ` 0 ile 100 arasında bir -tamsayı kabul eder. Diğer sağlayıcılar ve geçersiz değerler 1 ile çıkar. -`--json` şunu döndürür: +`openai` Codex havuzunun eşiğini yönetir veya genel OAuth havuzunun eşiğini kaydeder. `on` %80, `off` %0 kaydeder; `threshold ` 0–100 kabul eder. Genel havuz eşikleri şu anda uygulanmaz: kayıt işlemi eşik tabanlı geçişi, sağlayıcının etkinlik ayarını veya 429 hatasından sonraki otomatik hesap değişimini etkilemez. Çıktı sunucunun doğruladığı değerleri kullanır. Genel havuzlarda `poolEnabled`, kaydedilmiş sağlayıcı ayarıdır (`null` belirtilmemiş demektir); devralınmış etkin durumu göstermez. `inert: true`, eşiğin uygulanmadığını belirtir; yetenek bilinmiyorsa `enabled: true` bildirilmez. API anahtarlı sağlayıcılar, Anthropic ve geçersiz değerler reddedilir. ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md index 9601b0766b..a05d6cd65b 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md @@ -169,12 +169,11 @@ token,也不是简单重读账号列表。`--json` 返回 ### `ocx account auto-switch > [--json]` -只控制 `openai` 的 Codex 账号池。`on` 会设为 80%,`off` 会设为 0%,`status` 会读取 -当前值,而 `threshold ` 接受 0 到 100 之间的整数。其他提供方和无效值都会以 1 -退出。`--json` 返回: +控制 `openai` Codex 账户池阈值,或保存通用 OAuth 账户池阈值。`on` 保存 80%,`off` 保存 0%,`threshold ` 接受 0–100。通用池的阈值目前不参与运行;保存阈值不会启用阈值切换、改变提供方启用设置或禁用 429 错误后的轮换。查询和修改结果使用服务器确认值。通用池的 `poolEnabled` 是已保存的提供方设置,`null` 表示未指定,并不代表继承后的实际状态。`inert: true` 表示阈值未应用;能力未知时也不会报告 `enabled: true`。API 密钥提供方、Anthropic 和无效值会被拒绝。 ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md index d8d45133b6..2a1d55176c 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md @@ -129,10 +129,11 @@ Codex 池選擇套用於清除既有親和性後的下一個請求;進行中 ### `ocx account auto-switch > [--json]` -僅控制 `openai` Codex 帳號池。`on` 設為 80%,`off` 設為 0%,`status` 讀取目前值,而 `threshold ` 接受 0 到 100 的整數。其他供應商與無效值離開 1。`--json` 回傳: +控制 `openai` Codex 帳戶池閾值,或儲存通用 OAuth 帳戶池閾值。`on` 儲存 80%,`off` 儲存 0%,`threshold ` 接受 0–100。通用池的閾值目前不參與執行;儲存閾值不會啟用閾值切換、改變供應商啟用設定或停用 429 錯誤後的輪替。查詢與修改結果使用伺服器確認值。通用池的 `poolEnabled` 是已儲存的供應商設定,`null` 表示未指定,並不代表繼承後的實際狀態。`inert: true` 表示閾值未套用;能力未知時也不會回報 `enabled: true`。API 金鑰供應商、Anthropic 與無效值會被拒絕。 ```text -{ provider, autoSwitchThreshold: number, enabled: boolean } +openai: { provider, autoSwitchThreshold: number, enabled: boolean } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } ``` ### `ocx account login|reauth|code|cancel ...` diff --git a/src/cli/account-extended.ts b/src/cli/account-extended.ts index 82d6755e5b..d8ec552dca 100644 --- a/src/cli/account-extended.ts +++ b/src/cli/account-extended.ts @@ -367,6 +367,7 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise< if (threshold !== undefined && (!Number.isInteger(threshold) || threshold < 0 || threshold > 100)) { return usage("Error: threshold must be an integer 0-100"); } + let settings: Record = {}; const baseUrl = await resolveBaseUrl(deps); if (!baseUrl) return proxyUnreachable(); if (action === "status") { @@ -378,6 +379,7 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise< if (response.status !== 200 || (!genericPool && typeof response.json.autoSwitchThreshold !== "number")) { return apiError(response.json, "failed to read auto-switch status", response.status); } + settings = response.json; threshold = typeof response.json.autoSwitchThreshold === "number" ? response.json.autoSwitchThreshold : 0; } else { const response = genericPool @@ -385,6 +387,26 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise< : await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/auto-switch", { threshold }); if (response.status === 0) return proxyUnreachable(response.transportError); if (response.status !== 200) return apiError(response.json, "failed to update auto-switch", response.status); + settings = response.json; + } + if (genericPool) { + // Generic thresholds are stored independently of the enabled override. The + // latter may inherit global preference and never disables reactive rotation. + const stored = settings.autoSwitchThreshold; + const storedThreshold = typeof stored === "number" && Number.isInteger(stored) && stored >= 0 && stored <= 100 + ? stored : null; + const poolEnabled = typeof settings.enabled === "boolean" ? settings.enabled : null; + const inert = typeof settings.inert === "boolean" ? settings.inert : null; + const enabled = inert === false && poolEnabled === true && storedThreshold !== null && storedThreshold > 0; + if (wantsJson) { + console.log(JSON.stringify({ provider: name, autoSwitchThreshold: storedThreshold, enabled, poolEnabled, inert }, null, 2)); + } else if (inert !== false) { + const value = storedThreshold === null ? "unset" : `${storedThreshold}%`; + console.log(`auto-switch: ${inert === true ? "inactive" : "unavailable"} (stored threshold ${value}; ${inert === true ? "not applied by this pool" : "threshold support is unknown"})`); + } else { + console.log(enabled ? `auto-switch: on (threshold ${storedThreshold}%)` : "auto-switch: off"); + } + return 0; } const enabled = threshold! > 0; if (wantsJson) console.log(JSON.stringify({ provider: name, autoSwitchThreshold: threshold, enabled }, null, 2)); diff --git a/tests/cli/cli-account-pool-verbs.test.ts b/tests/cli/cli-account-pool-verbs.test.ts index 41c4c170fd..f46951cca5 100644 --- a/tests/cli/cli-account-pool-verbs.test.ts +++ b/tests/cli/cli-account-pool-verbs.test.ts @@ -349,10 +349,68 @@ describe("generic OAuth pool-settings contract (#695)", () => { const calls: Captured[] = []; const out = capture(); try { - expect(await cmdAutoSwitch(["google-antigravity", "threshold", "90"], genericDeps(() => ({ json: { ok: true, autoSwitchThreshold: 90 } }), calls))).toBe(0); + expect(await cmdAutoSwitch(["google-antigravity", "threshold", "90"], genericDeps(() => ({ json: { ok: true, autoSwitchThreshold: 90, enabled: true, inert: true } }), calls))).toBe(0); } finally { out.restore(); } expect(calls[0]).toMatchObject({ method: "PUT", path: "/api/oauth/accounts/pool", body: { provider: "google-antigravity", autoSwitchThreshold: 90 } }); - expect(out.lines.join("\n")).toContain("threshold 90%"); + expect(out.lines.join("\n")).toContain("stored threshold 90%"); + expect(out.lines.join("\n")).toContain("inactive"); + expect(out.lines.join("\n")).not.toContain("auto-switch: on"); + }); + + test("generic status preserves configured pool state without claiming an inert threshold is active", async () => { + for (const poolEnabled of [true, false, null]) { + const calls: Captured[] = []; + const out = capture(); + try { + expect(await cmdAutoSwitch(["google-antigravity", "status", "--json"], genericDeps(() => ({ + json: { kind: "generic", enabled: poolEnabled, autoSwitchThreshold: 90, inert: true }, + }), calls))).toBe(0); + } finally { out.restore(); } + expect(calls).toHaveLength(1); + expect(calls[0]).toMatchObject({ method: "GET", path: "/api/oauth/accounts/pool?provider=google-antigravity" }); + expect(JSON.parse(out.lines.join("\n"))).toEqual({ + provider: "google-antigravity", autoSwitchThreshold: 90, enabled: false, poolEnabled, inert: true, + }); + } + }); + + test("generic writes report the confirmed DTO, not the requested threshold", async () => { + const calls: Captured[] = []; + const out = capture(); + try { + expect(await cmdAutoSwitch(["google-antigravity", "on", "--json"], genericDeps(() => ({ + json: { ok: true, enabled: null, autoSwitchThreshold: null, inert: true }, + }), calls))).toBe(0); + } finally { out.restore(); } + expect(calls[0]).toMatchObject({ method: "PUT", body: { provider: "google-antigravity", autoSwitchThreshold: 80 } }); + expect(JSON.parse(out.lines.join("\n"))).toEqual({ + provider: "google-antigravity", autoSwitchThreshold: null, enabled: false, poolEnabled: null, inert: true, + }); + }); + + test("generic missing or malformed capability stays unknown rather than enabled", async () => { + for (const json of [{}, { enabled: "true", autoSwitchThreshold: "90", inert: "false" }, + { enabled: true, autoSwitchThreshold: 90 }, { enabled: true, autoSwitchThreshold: 101, inert: false }]) { + const out = capture(); + try { + expect(await cmdAutoSwitch(["google-antigravity", "status", "--json"], genericDeps(() => ({ json }), []))).toBe(0); + } finally { out.restore(); } + const result = JSON.parse(out.lines.join("\n")); + expect(result.enabled).toBe(false); + expect(result.autoSwitchThreshold === null || result.autoSwitchThreshold === 90).toBe(true); + } + }); + + test("an inert zero threshold remains distinct from an unset threshold", async () => { + for (const autoSwitchThreshold of [0, null]) { + const out = capture(); + try { + expect(await cmdAutoSwitch(["google-antigravity", "status", "--json"], genericDeps(() => ({ + json: { enabled: true, autoSwitchThreshold, inert: true }, + }), []))).toBe(0); + } finally { out.restore(); } + expect(JSON.parse(out.lines.join("\n"))).toMatchObject({ autoSwitchThreshold, enabled: false, inert: true }); + } }); test("api-key providers are still refused before any request", async () => { From c18afc93478514a878460c610d48fa2f0d0b03c3 Mon Sep 17 00:00:00 2001 From: t Date: Mon, 7 Sep 2026 01:45:27 +0900 Subject: [PATCH 2/2] fix(cli): keep unknown generic pool capabilities inactive --- .../fr/reference/cli/providers-accounts.md | 4 ++-- .../ja/reference/cli/providers-accounts.md | 4 ++-- .../ko/reference/cli/providers-accounts.md | 4 ++-- .../docs/reference/cli/providers-accounts.md | 4 ++-- .../ru/reference/cli/providers-accounts.md | 4 ++-- .../tr/reference/cli/providers-accounts.md | 4 ++-- .../zh-cn/reference/cli/providers-accounts.md | 4 ++-- .../zh-tw/reference/cli/providers-accounts.md | 4 ++-- src/cli/account-extended.ts | 17 ++++++++------- tests/cli/cli-account-pool-verbs.test.ts | 21 ++++++++++++++++--- 10 files changed, 43 insertions(+), 27 deletions(-) diff --git a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md index c7c4ffa7ca..284c8c20c3 100644 --- a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md @@ -203,11 +203,11 @@ renvoient 1 ; une sonde de quota en amont qui échoue ou expire produit plutôt ### `ocx account auto-switch > [--json]` -Contrôle le seuil du pool Codex `openai`, ou enregistre celui d’un pool OAuth générique. `on` enregistre 80 %, `off` 0 % et `threshold ` accepte 0–100. Les seuils génériques sont actuellement inactifs : leur sauvegarde ne change ni le basculement par seuil, ni l’activation du fournisseur, ni la rotation réactive après une erreur 429. Les sorties utilisent la réponse confirmée du serveur. Pour un pool générique, `poolEnabled` est le réglage enregistré (`null` signifie non spécifié), pas l’état effectif hérité. `inert: true` indique que le seuil ne s’applique pas ; une capacité inconnue ne produit jamais `enabled: true`. Les fournisseurs à clé API, Anthropic et les valeurs invalides sont refusés. +Contrôle le seuil du pool Codex `openai`, ou enregistre celui d’un pool OAuth générique. `on` enregistre 80 %, `off` 0 % et `threshold ` accepte 0–100. Les seuils génériques sont actuellement inactifs : leur sauvegarde ne change ni le basculement par seuil, ni l’activation du fournisseur, ni la rotation réactive après une erreur 429. Pour les pools génériques, les sorties utilisent la réponse confirmée du serveur. Pour un pool générique, `poolEnabled` est le réglage enregistré (`null` signifie non spécifié), pas l’état effectif hérité. `inert: true` indique que le seuil ne s’applique pas ; une capacité inconnue ne produit jamais `enabled: true`. Les fournisseurs à clé API, Anthropic et les valeurs invalides sont refusés. ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md index 63596b369c..d20483a8a2 100644 --- a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md @@ -149,11 +149,11 @@ OAuth プロバイダーと API キー プロバイダーの場合、これに ### `ocx account auto-switch > [--json]` -`openai` Codex プールのしきい値を制御するか、汎用 OAuth プールのしきい値を保存します。`on` は 80%、`off` は 0%、`threshold ` は 0–100 を保存します。汎用プールのしきい値は現在適用されません。保存しても、しきい値による切り替え、プロバイダーの有効化設定、429 エラー時のローテーションは変更されません。照会と変更の結果はサーバーの確認値を使用します。汎用プールの `poolEnabled` は保存された設定で、`null` は未指定です。継承後の実効状態ではありません。`inert: true` は未適用を示し、機能が不明な場合も `enabled: true` とは表示しません。API キープロバイダー、Anthropic、不正な値は拒否されます。 +`openai` Codex プールのしきい値を制御するか、汎用 OAuth プールのしきい値を保存します。`on` は 80%、`off` は 0%、`threshold ` は 0–100 を保存します。汎用プールのしきい値は現在適用されません。保存しても、しきい値による切り替え、プロバイダーの有効化設定、429 エラー時のローテーションは変更されません。汎用プールの照会と変更の結果はサーバーの確認値を使用します。汎用プールの `poolEnabled` は保存された設定で、`null` は未指定です。継承後の実効状態ではありません。`inert: true` は未適用を示し、機能が不明な場合も `enabled: true` とは表示しません。API キープロバイダー、Anthropic、不正な値は拒否されます。 ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md index ff86f67e8d..250b3d7b1b 100644 --- a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md @@ -209,11 +209,11 @@ OAuth 및 API 키 제공자에는 제공자의 할당량 보고 엔드포인트 ### `ocx account auto-switch > [--json]` -`openai` Codex 풀의 임계값을 제어하거나 일반 OAuth 풀의 임계값을 저장합니다. `on`은 80%, `off`는 0%, `threshold `은 0–100을 저장합니다. 일반 풀의 임계값은 현재 동작에 적용되지 않습니다. 저장해도 임계값 기반 전환이나 제공자 활성화 설정이 바뀌지 않고, 429 오류에 따른 회전도 비활성화되지 않습니다. 조회와 변경 결과는 서버가 확인한 값을 사용합니다. 일반 풀의 `poolEnabled`는 저장된 제공자별 설정이며 `null`은 미지정입니다. 전역 설정을 상속한 실제 상태를 뜻하지 않습니다. `inert: true`이면 임계값이 적용되지 않으며, 기능 지원을 알 수 없을 때도 `enabled: true`로 표시하지 않습니다. API 키 제공자, Anthropic 및 잘못된 값은 거부합니다. +`openai` Codex 풀의 임계값을 제어하거나 일반 OAuth 풀의 임계값을 저장합니다. `on`은 80%, `off`는 0%, `threshold `은 0–100을 저장합니다. 일반 풀의 임계값은 현재 동작에 적용되지 않습니다. 저장해도 임계값 기반 전환이나 제공자 활성화 설정이 바뀌지 않고, 429 오류에 따른 회전도 비활성화되지 않습니다. 일반 풀의 조회와 변경 결과는 서버가 확인한 값을 사용합니다. 일반 풀의 `poolEnabled`는 저장된 제공자별 설정이며 `null`은 미지정입니다. 전역 설정을 상속한 실제 상태를 뜻하지 않습니다. `inert: true`이면 임계값이 적용되지 않으며, 기능 지원을 알 수 없을 때도 `enabled: true`로 표시하지 않습니다. API 키 제공자, Anthropic 및 잘못된 값은 거부합니다. ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index 7b9d9da16c..b31c0f3159 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -331,11 +331,11 @@ instead (exit 0), matching the dashboard's quota bars. ### `ocx account auto-switch > [--json]` -Controls the `openai` Codex pool threshold, or stores a threshold for a generic OAuth pool. `on` stores 80%, `off` stores 0%, and `threshold ` accepts 0–100. Generic pool thresholds are currently inert: saving one does not enable threshold-based switching, change the provider enablement override, or disable reactive 429 rotation. `status` and mutation output use the confirmed server response. For generic pools, `poolEnabled` is the stored provider override (`null` means unspecified), not inherited effective state; `inert: true` means the threshold is not applied, and unknown capability never reports `enabled: true`. API-key providers, Anthropic and invalid values are rejected. +Controls the `openai` Codex pool threshold, or stores a threshold for a generic OAuth pool. `on` stores 80%, `off` stores 0%, and `threshold ` accepts 0–100. Generic pool thresholds are currently inert: saving one does not enable threshold-based switching, change the provider enablement override, or disable reactive 429 rotation. `status` and mutation output for generic pools use the confirmed server response. For generic pools, `poolEnabled` is the stored provider override (`null` means unspecified), not inherited effective state; `inert: true` means the threshold is not applied, and unknown capability never reports `enabled: true`. API-key providers, Anthropic and invalid values are rejected. ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md index 1e011a0dd5..56bde92668 100644 --- a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md @@ -187,11 +187,11 @@ quota-bar'ов дашборда. ### `ocx account auto-switch > [--json]` -Управляет порогом пула Codex `openai` или сохраняет порог общего пула OAuth. `on` сохраняет 80 %, `off` — 0 %, а `threshold ` принимает 0–100. Пороги общих пулов пока не применяются: сохранение не включает переключение по порогу, не меняет настройку включения провайдера и не отключает ротацию после ошибки 429. Результат чтения и изменения берётся из подтверждённого ответа сервера. Для общего пула `poolEnabled` — сохранённая настройка провайдера (`null` означает отсутствие настройки), а не итоговое унаследованное состояние. `inert: true` означает, что порог не применяется; неизвестная возможность также не даёт `enabled: true`. Провайдеры с ключом API, Anthropic и неверные значения отклоняются. +Управляет порогом пула Codex `openai` или сохраняет порог общего пула OAuth. `on` сохраняет 80 %, `off` — 0 %, а `threshold ` принимает 0–100. Пороги общих пулов пока не применяются: сохранение не включает переключение по порогу, не меняет настройку включения провайдера и не отключает ротацию после ошибки 429. Для общего пула результат чтения и изменения берётся из подтверждённого ответа сервера. Для общего пула `poolEnabled` — сохранённая настройка провайдера (`null` означает отсутствие настройки), а не итоговое унаследованное состояние. `inert: true` означает, что порог не применяется; неизвестная возможность также не даёт `enabled: true`. Провайдеры с ключом API, Anthropic и неверные значения отклоняются. ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md index 92c0cbd2a8..f611d7be73 100644 --- a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md @@ -229,11 +229,11 @@ eşleşen null veya eski bir rapora düşer (çıkış 0). ### `ocx account auto-switch > [--json]` -`openai` Codex havuzunun eşiğini yönetir veya genel OAuth havuzunun eşiğini kaydeder. `on` %80, `off` %0 kaydeder; `threshold ` 0–100 kabul eder. Genel havuz eşikleri şu anda uygulanmaz: kayıt işlemi eşik tabanlı geçişi, sağlayıcının etkinlik ayarını veya 429 hatasından sonraki otomatik hesap değişimini etkilemez. Çıktı sunucunun doğruladığı değerleri kullanır. Genel havuzlarda `poolEnabled`, kaydedilmiş sağlayıcı ayarıdır (`null` belirtilmemiş demektir); devralınmış etkin durumu göstermez. `inert: true`, eşiğin uygulanmadığını belirtir; yetenek bilinmiyorsa `enabled: true` bildirilmez. API anahtarlı sağlayıcılar, Anthropic ve geçersiz değerler reddedilir. +`openai` Codex havuzunun eşiğini yönetir veya genel OAuth havuzunun eşiğini kaydeder. `on` %80, `off` %0 kaydeder; `threshold ` 0–100 kabul eder. Genel havuz eşikleri şu anda uygulanmaz: kayıt işlemi eşik tabanlı geçişi, sağlayıcının etkinlik ayarını veya 429 hatasından sonraki otomatik hesap değişimini etkilemez. Genel havuz çıktısı sunucunun doğruladığı değerleri kullanır. Genel havuzlarda `poolEnabled`, kaydedilmiş sağlayıcı ayarıdır (`null` belirtilmemiş demektir); devralınmış etkin durumu göstermez. `inert: true`, eşiğin uygulanmadığını belirtir; yetenek bilinmiyorsa `enabled: true` bildirilmez. API anahtarlı sağlayıcılar, Anthropic ve geçersiz değerler reddedilir. ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md index a05d6cd65b..85633f54f5 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md @@ -169,11 +169,11 @@ token,也不是简单重读账号列表。`--json` 返回 ### `ocx account auto-switch > [--json]` -控制 `openai` Codex 账户池阈值,或保存通用 OAuth 账户池阈值。`on` 保存 80%,`off` 保存 0%,`threshold ` 接受 0–100。通用池的阈值目前不参与运行;保存阈值不会启用阈值切换、改变提供方启用设置或禁用 429 错误后的轮换。查询和修改结果使用服务器确认值。通用池的 `poolEnabled` 是已保存的提供方设置,`null` 表示未指定,并不代表继承后的实际状态。`inert: true` 表示阈值未应用;能力未知时也不会报告 `enabled: true`。API 密钥提供方、Anthropic 和无效值会被拒绝。 +控制 `openai` Codex 账户池阈值,或保存通用 OAuth 账户池阈值。`on` 保存 80%,`off` 保存 0%,`threshold ` 接受 0–100。通用池的阈值目前不参与运行;保存阈值不会启用阈值切换、改变提供方启用设置或禁用 429 错误后的轮换。通用池的查询和修改结果使用服务器确认值。通用池的 `poolEnabled` 是已保存的提供方设置,`null` 表示未指定,并不代表继承后的实际状态。`inert: true` 表示阈值未应用;能力未知时也不会报告 `enabled: true`。API 密钥提供方、Anthropic 和无效值会被拒绝。 ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account priority [<-100..100|first|earlier|normal|later|last|reset>] [--json]` diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md index 2a1d55176c..c50a6d54ae 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md @@ -129,11 +129,11 @@ Codex 池選擇套用於清除既有親和性後的下一個請求;進行中 ### `ocx account auto-switch > [--json]` -控制 `openai` Codex 帳戶池閾值,或儲存通用 OAuth 帳戶池閾值。`on` 儲存 80%,`off` 儲存 0%,`threshold ` 接受 0–100。通用池的閾值目前不參與執行;儲存閾值不會啟用閾值切換、改變供應商啟用設定或停用 429 錯誤後的輪替。查詢與修改結果使用伺服器確認值。通用池的 `poolEnabled` 是已儲存的供應商設定,`null` 表示未指定,並不代表繼承後的實際狀態。`inert: true` 表示閾值未套用;能力未知時也不會回報 `enabled: true`。API 金鑰供應商、Anthropic 與無效值會被拒絕。 +控制 `openai` Codex 帳戶池閾值,或儲存通用 OAuth 帳戶池閾值。`on` 儲存 80%,`off` 儲存 0%,`threshold ` 接受 0–100。通用池的閾值目前不參與執行;儲存閾值不會啟用閾值切換、改變供應商啟用設定或停用 429 錯誤後的輪替。通用池的查詢與修改結果使用伺服器確認值。通用池的 `poolEnabled` 是已儲存的供應商設定,`null` 表示未指定,並不代表繼承後的實際狀態。`inert: true` 表示閾值未套用;能力未知時也不會回報 `enabled: true`。API 金鑰供應商、Anthropic 與無效值會被拒絕。 ```text openai: { provider, autoSwitchThreshold: number, enabled: boolean } -generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: boolean | null } +generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null } ``` ### `ocx account login|reauth|code|cancel ...` diff --git a/src/cli/account-extended.ts b/src/cli/account-extended.ts index d8ec552dca..38179aca42 100644 --- a/src/cli/account-extended.ts +++ b/src/cli/account-extended.ts @@ -379,15 +379,17 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise< if (response.status !== 200 || (!genericPool && typeof response.json.autoSwitchThreshold !== "number")) { return apiError(response.json, "failed to read auto-switch status", response.status); } - settings = response.json; - threshold = typeof response.json.autoSwitchThreshold === "number" ? response.json.autoSwitchThreshold : 0; + settings = genericPool && (!response.json || typeof response.json !== "object" || Array.isArray(response.json)) + ? {} : response.json; + threshold = typeof settings.autoSwitchThreshold === "number" ? settings.autoSwitchThreshold : 0; } else { const response = genericPool ? await apiJson(deps, baseUrl, "PUT", "/api/oauth/accounts/pool", { provider: name, autoSwitchThreshold: threshold }) : await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/auto-switch", { threshold }); if (response.status === 0) return proxyUnreachable(response.transportError); if (response.status !== 200) return apiError(response.json, "failed to update auto-switch", response.status); - settings = response.json; + settings = genericPool && (!response.json || typeof response.json !== "object" || Array.isArray(response.json)) + ? {} : response.json; } if (genericPool) { // Generic thresholds are stored independently of the enabled override. The @@ -396,15 +398,14 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise< const storedThreshold = typeof stored === "number" && Number.isInteger(stored) && stored >= 0 && stored <= 100 ? stored : null; const poolEnabled = typeof settings.enabled === "boolean" ? settings.enabled : null; - const inert = typeof settings.inert === "boolean" ? settings.inert : null; - const enabled = inert === false && poolEnabled === true && storedThreshold !== null && storedThreshold > 0; + const inert = settings.inert === true ? true : null; + // This CLI understands only the current inert generic threshold contract. + const enabled = false; if (wantsJson) { console.log(JSON.stringify({ provider: name, autoSwitchThreshold: storedThreshold, enabled, poolEnabled, inert }, null, 2)); - } else if (inert !== false) { + } else { const value = storedThreshold === null ? "unset" : `${storedThreshold}%`; console.log(`auto-switch: ${inert === true ? "inactive" : "unavailable"} (stored threshold ${value}; ${inert === true ? "not applied by this pool" : "threshold support is unknown"})`); - } else { - console.log(enabled ? `auto-switch: on (threshold ${storedThreshold}%)` : "auto-switch: off"); } return 0; } diff --git a/tests/cli/cli-account-pool-verbs.test.ts b/tests/cli/cli-account-pool-verbs.test.ts index f46951cca5..04be2e4fa6 100644 --- a/tests/cli/cli-account-pool-verbs.test.ts +++ b/tests/cli/cli-account-pool-verbs.test.ts @@ -382,15 +382,17 @@ describe("generic OAuth pool-settings contract (#695)", () => { json: { ok: true, enabled: null, autoSwitchThreshold: null, inert: true }, }), calls))).toBe(0); } finally { out.restore(); } - expect(calls[0]).toMatchObject({ method: "PUT", body: { provider: "google-antigravity", autoSwitchThreshold: 80 } }); + expect(calls).toHaveLength(1); + expect(calls[0]?.body).toEqual({ provider: "google-antigravity", autoSwitchThreshold: 80 }); expect(JSON.parse(out.lines.join("\n"))).toEqual({ provider: "google-antigravity", autoSwitchThreshold: null, enabled: false, poolEnabled: null, inert: true, }); }); test("generic missing or malformed capability stays unknown rather than enabled", async () => { - for (const json of [{}, { enabled: "true", autoSwitchThreshold: "90", inert: "false" }, - { enabled: true, autoSwitchThreshold: 90 }, { enabled: true, autoSwitchThreshold: 101, inert: false }]) { + for (const json of [null, [], {}, { enabled: "true", autoSwitchThreshold: "90", inert: "false" }, + { enabled: true, autoSwitchThreshold: 90 }, { enabled: true, autoSwitchThreshold: 101, inert: false }, + { enabled: true, autoSwitchThreshold: 90, inert: false }]) { const out = capture(); try { expect(await cmdAutoSwitch(["google-antigravity", "status", "--json"], genericDeps(() => ({ json }), []))).toBe(0); @@ -401,6 +403,19 @@ describe("generic OAuth pool-settings contract (#695)", () => { } }); + test("a successful generic write with a null body reports unknown settings", async () => { + const calls: Captured[] = []; + const out = capture(); + try { + expect(await cmdAutoSwitch(["google-antigravity", "off", "--json"], genericDeps(() => ({ json: null }), calls))).toBe(0); + } finally { out.restore(); } + expect(calls).toHaveLength(1); + expect(calls[0]?.body).toEqual({ provider: "google-antigravity", autoSwitchThreshold: 0 }); + expect(JSON.parse(out.lines.join("\n"))).toEqual({ + provider: "google-antigravity", autoSwitchThreshold: null, enabled: false, poolEnabled: null, inert: null, + }); + }); + test("an inert zero threshold remains distinct from an unset threshold", async () => { for (const autoSwitchThreshold of [0, null]) { const out = capture();