Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,11 @@ renvoient 1 ; une sonde de quota en amont qui échoue ou expire produit plutôt

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--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 <n>` 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 <n>` 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
{ provider, autoSwitchThreshold: number, enabled: boolean }
openai: { provider, autoSwitchThreshold: number, enabled: boolean }
generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,11 @@ OAuth プロバイダーと API キー プロバイダーの場合、これに

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

`openai` Codex アカウント プールのみを制御します。 `on` は 80% を設定し、`off` は 0% を設定します。`status` は現在の値を読み取り、`threshold <n>` は 0100 の整数を受け入れます。他のプロバイダーと無効な値は 1 を終了します。`--json` は次を返します
`openai` Codex プールのしきい値を制御するか、汎用 OAuth プールのしきい値を保存します。`on` は 80%、`off` は 0%、`threshold <n>` は 0100 を保存します。汎用プールのしきい値は現在適用されません。保存しても、しきい値による切り替え、プロバイダーの有効化設定、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: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,11 @@ OAuth 및 API 키 제공자에는 제공자의 할당량 보고 엔드포인트

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

`openai` Codex 계정 풀만 제어합니다. `on`은 80%, `off`는 0%를 설정하고, `status`는 현재 값을 읽으며, `threshold <n>`은 0부터 100까지의 정수를 받습니다. 다른 제공자와 잘못된 값은 종료 코드 1로 끝납니다. `--json`은 다음을 반환합니다:
`openai` Codex 풀의 임계값을 제어하거나 일반 OAuth 풀의 임계값을 저장합니다. `on`은 80%, `off`는 0%, `threshold <n>`은 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: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,11 @@ instead (exit 0), matching the dashboard's quota bars.

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

Controls only the `openai` Codex account pool. `on` sets 80%, `off` sets 0%, `status` reads the current
value, and `threshold <n>` 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 <n>` 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
{ provider, autoSwitchThreshold: number, enabled: boolean }
openai: { provider, autoSwitchThreshold: number, enabled: boolean }
generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,11 @@ quota-bar'ов дашборда.

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

Управляет только пулом аккаунтов Codex `openai`. `on` ставит 80%, `off` — 0%, `status` читает
текущее значение, а `threshold <n>` принимает целое число от 0 до 100. Для других провайдеров и
некорректных значений команда завершается кодом 1. `--json` возвращает:
Управляет порогом пула Codex `openai` или сохраняет порог общего пула OAuth. `on` сохраняет 80 %, `off` — 0 %, а `threshold <n>` принимает 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: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,11 @@ eşleşen null veya eski bir rapora düşer (çıkış 0).

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

Yalnızca `openai` Codex hesap havuzunu denetler. `on` %80'i ayarlar, `off` %0'ı
ayarlar, `status` geçerli değeri okur ve `threshold <n>` 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 <n>` 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
{ provider, autoSwitchThreshold: number, enabled: boolean }
openai: { provider, autoSwitchThreshold: number, enabled: boolean }
generic OAuth: { provider, autoSwitchThreshold: number | null, enabled: boolean, poolEnabled: boolean | null, inert: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,11 @@ token,也不是简单重读账号列表。`--json` 返回

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

只控制 `openai` 的 Codex 账号池。`on` 会设为 80%,`off` 会设为 0%,`status` 会读取
当前值,而 `threshold <n>` 接受 0 到 100 之间的整数。其他提供方和无效值都会以 1
退出。`--json` 返回:
控制 `openai` Codex 账户池阈值,或保存通用 OAuth 账户池阈值。`on` 保存 80%,`off` 保存 0%,`threshold <n>` 接受 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: true | null }
```

### `ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@ Codex 池選擇套用於清除既有親和性後的下一個請求;進行中

### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`

僅控制 `openai` Codex 帳號池。`on` 設為 80%,`off` 設為 0%,`status` 讀取目前值,而 `threshold <n>` 接受 0100 的整數。其他供應商與無效值離開 1。`--json` 回傳:
控制 `openai` Codex 帳戶池閾值,或儲存通用 OAuth 帳戶池閾值。`on` 儲存 80%,`off` 儲存 0%,`threshold <n>` 接受 0100。通用池的閾值目前不參與執行;儲存閾值不會啟用閾值切換、改變供應商啟用設定或停用 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: true | null }
```

### `ocx account login|reauth|code|cancel ...`
Expand Down
64 changes: 57 additions & 7 deletions gui/src/components/QuotaBars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,38 @@ export type QuotaBarRow = {
/**
* Window ordering is computed from RAW wire identities BEFORE localization
* (ranking on translated labels breaks the moment a locale changes copy):
* shorter windows first — 5h, weekly, cursor first-party, cursor API, monthly.
* shorter windows first — 5h, weekly, cursor first-party, cursor API, monthly,
* then subscription credits before other custom windows.
*/
function rawCustomWindowRank(rawLabel: string): number {
if (rawLabel === "5h") return 0;
if (rawLabel === "First-party models") return 2;
if (rawLabel === "API usage") return 3;
if (rawLabel === "Total subscription credits") return 4.5;
return 5;
}

const SUBSCRIPTION_CREDITS_LABEL = "Total subscription credits";

function canonicalCustomWindowLabel(rawLabel: string): string {
return rawLabel.trim().toLowerCase() === SUBSCRIPTION_CREDITS_LABEL.toLowerCase()
? SUBSCRIPTION_CREDITS_LABEL
: rawLabel;
}

/** Coverage metadata carries raw labels, while subscription rows use a canonical identity. */
export function isCustomQuotaWindowIncomplete(
customLabel: string | undefined,
incompleteLabels?: ReadonlySet<string>,
): boolean {
if (customLabel === undefined || !incompleteLabels) return false;
const canonical = canonicalCustomWindowLabel(customLabel);
for (const label of incompleteLabels) {
if (canonicalCustomWindowLabel(label) === canonical) return true;
}
return false;
}

function localizeCustomQuotaLabel(rawLabel: string, t: TFn): string {
switch (rawLabel) {
case "First-party models":
Expand Down Expand Up @@ -84,18 +107,37 @@ export function buildQuotaRows(quota: AccountQuota | null, plan: string | null |
});
}
for (const w of displayQuota.customWindows ?? []) {
const localized = localizeCustomQuotaLabel(w.label, t);
const customLabel = canonicalCustomWindowLabel(w.label);
const localized = localizeCustomQuotaLabel(customLabel, t);
ranked.push({
rank: rawCustomWindowRank(w.label),
rank: rawCustomWindowRank(customLabel),
row: {
customLabel: w.label,
customLabel,
label: localized,
limitLabel: localized,
percent: w.percent,
resetAt: w.resetAt,
},
});
}
if (displayQuota.creditsUsd && typeof displayQuota.creditsUsd.percent === "number") {
const hasSubscriptionCreditsCustom = displayQuota.customWindows?.some(
w => canonicalCustomWindowLabel(w.label) === SUBSCRIPTION_CREDITS_LABEL,
);
if (!hasSubscriptionCreditsCustom) {
const localized = localizeCustomQuotaLabel(SUBSCRIPTION_CREDITS_LABEL, t);
ranked.push({
rank: rawCustomWindowRank(SUBSCRIPTION_CREDITS_LABEL),
row: {
customLabel: SUBSCRIPTION_CREDITS_LABEL,
label: localized,
limitLabel: localized,
percent: displayQuota.creditsUsd.percent,
resetAt: displayQuota.creditsUsd.expiresAt,
},
});
}
}
return ranked.sort((a, b) => a.rank - b.rank).map(entry => entry.row);
}

Expand All @@ -107,6 +149,12 @@ export function maxQuotaUtilisation(quota: AccountQuota | null): number {
for (const w of quota.customWindows ?? []) {
if (typeof w.percent === "number") vals.push(w.percent);
}
const hasSubscriptionCreditsCustom = quota.customWindows?.some(
w => canonicalCustomWindowLabel(w.label) === SUBSCRIPTION_CREDITS_LABEL,
);
if (!hasSubscriptionCreditsCustom && typeof quota.creditsUsd?.percent === "number") {
vals.push(quota.creditsUsd.percent);
}
return vals.length ? Math.max(...vals) : -1;
}

Expand Down Expand Up @@ -279,7 +327,7 @@ export default function QuotaBars({
locale={locale}
incomplete={row.windowKey
? incompleteWindowKeys?.has(row.windowKey) === true
: row.customLabel !== undefined && incompleteCustomWindowLabels?.has(row.customLabel) === true}
: isCustomQuotaWindowIncomplete(row.customLabel, incompleteCustomWindowLabels)}
/>
))}
</div>
Expand All @@ -291,6 +339,7 @@ export default function QuotaBars({
{rows.map(row => (
<QuotaRow
key={row.label}
credits={row.customLabel === SUBSCRIPTION_CREDITS_LABEL}
label={row.label}
percent={row.percent}
resetAt={row.resetAt}
Expand All @@ -303,7 +352,8 @@ export default function QuotaBars({
);
}

function QuotaRow({ label, percent, resetAt, threshold, t, locale }: {
function QuotaRow({ credits, label, percent, resetAt, threshold, t, locale }: {
credits?: boolean;
label: string;
percent: number;
resetAt?: number;
Expand All @@ -320,7 +370,7 @@ function QuotaRow({ label, percent, resetAt, threshold, t, locale }: {
: undefined;
const hasReset = reset.day !== "" || reset.time !== "";
return (
<div className={`quota-row${warn ? " quota-row--warn" : ""}${exhausted ? " quota-row--exhausted" : ""}`}>
<div className={`quota-row${credits ? " quota-row--credits" : ""}${warn ? " quota-row--warn" : ""}${exhausted ? " quota-row--exhausted" : ""}`}>
<span className="quota-label" title={resetTitle}>{label}</span>
<span className="quota-reset-label">{hasReset ? t("codexAuth.resets") : ""}</span>
<span className="quota-reset-day">{reset.day}</span>
Expand Down
15 changes: 15 additions & 0 deletions gui/src/styles/provider-quota.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,18 @@
color: var(--amber);
font-size: 12px;
}

/* Long subscription labels and reset/value text need separate rows in narrow cards. */
.quota-compact { container: quota-compact / inline-size; }
@container quota-compact (max-width: 440px) {
.quota-row--credits {
grid-template-columns: max-content max-content minmax(0, 1fr) max-content;
row-gap: 4px;
}
.quota-row--credits .quota-label { grid-column: 1 / -1; grid-row: 1; }
.quota-row--credits .quota-reset-label { grid-column: 1; grid-row: 2; }
.quota-row--credits .quota-reset-day { grid-column: 2; grid-row: 2; }
.quota-row--credits .quota-reset-time { grid-column: 3 / -1; grid-row: 2; }
.quota-row--credits .bar { grid-column: 1 / 4; grid-row: 3; }
.quota-row--credits .quota-val { grid-column: 4; grid-row: 3; white-space: nowrap; }
}
Loading
Loading