Skip to content
Closed
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
26 changes: 26 additions & 0 deletions docs-site/src/content/docs/reference/cli/providers-accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,32 @@ grant counts, nullable dates, cooldown, pending operation and journal availabili
Empty grants are not the same as unavailable status. It never consumes a grant
or resumes a pending spend. Consumption remains a human GUI operation.

### Use remaining included quota

To keep using a selected account past a proactive switching threshold or the main-account
protection threshold, explicitly enable its remaining-quota preference:

```bash
ocx account use-remaining openai main on
ocx account use openai main
ocx account use-remaining openai main status --json
ocx account use-remaining openai main off
```

Replace `main` with a unique pool-account ID or alias to target an added account.
Enabling this disables proactive usage switching for that account. For the main login, it also
moves both effective hard-lock thresholds to 100%, so a 95% reading can still admit a request.
The preference persists across restarts until turned off; original threshold settings remain
stored and resume applying when it is disabled. It does not select an account itself or override
round-robin scheduling, manual/automatic pause, entitlement checks, reauthentication, or upstream
cooldowns. Paid-credit permission remains a separate setting; enabling this never opts an account
into credits or redeems a reset grant. The default-on main hard lock still refuses at 100%.

`GET /api/codex-auth/accounts/use-remaining?id=<account-id>` reads the live policy.
`PUT /api/codex-auth/accounts/use-remaining` accepts `{ id, useRemainingQuota: boolean }`.
Both require the existing management authentication. This control is currently exposed through
the CLI and API.

### Main-account quota protection

In **Codex settings → Multi-auth → Advanced settings**, **Block main account**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ separate. Full request URLs such as `/api/v1/responses` are not provider base UR
| `contextCapValue?` | `number` | `350000` | Default used on first enable. A later enable restores the selected provider value. Updating the global value with `setAll: true` changes enabled caps only; `setAll: true` without a value enables all configured providers at the current global value. |
| `codexAccounts?` | `CodexAccount[]` | `[]` | ChatGPT/Codex pool account metadata managed by Codex Auth. Secrets live separately in `codex-accounts.json`. |
| `pausedCodexAccountIds?` | `string[]` | `[]` | Accounts excluded from Pool selection until resumed, including the main `__main__` account when paused. |
| `codexUseRemainingQuotaAccountIds?` | `string[]` | `[]` | Accounts opted into using their remaining included quota. Disables proactive usage switching for listed accounts; for `__main__`, effective short/long hard-lock thresholds become 100%. Original thresholds remain stored and apply again after disabling. Pause, authentication, eligibility, cooldown and paid-credit permission still apply. Managed by `ocx account use-remaining openai <id-or-main> on`; this persistent preference does not select the account. |
| `creditCodexAccountIds?` | `string[]` | `[]` | Accounts allowed to keep serving from ChatGPT credits after a usage limit, including the main `__main__` account. Upstream does not refuse an account that holds credits at 100%; it serves the request and draws the balance. Spending is opt-in: an account not listed here is skipped by selection while one of its usage windows (weekly or monthly, only monthly on 30-day plans, or the 5-hour window) reads 100% with its reset still ahead, and returns once that reset passes. A weekly or monthly reading without a reset time does not hold the account; a 5-hour reading at 100% without a reset holds it only while that reading is still fresh. When no other account is available, automatic selection finds none rather than spending credits. An unlisted `__main__` is also refused, like a hard-lock refusal, when a request names it or carries its credential. Listing `__main__` does not lift the main-account hard lock (on by default at 98%), which still stops the main login first; turn the lock off to let the main account spend credits (a lock at 100% still stops it at 100%). New accounts start unlisted. Managed by the **Use credits** switch in the Codex Auth header and the **Use credits after limit** switch in each account card's **⋯** menu. |
| `codexQuotaAutoRefresh?` | `Record<string, object>` | `{}` | Per-Codex-login-account opt-in for automatic `fiveHour` and `weekly` window activation in Pool mode; Direct mode does not run this worker. In Providers/Codex Auth **Advanced settings**, one control enables or disables both supported windows across all current main and added accounts. New accounts are not opted in automatically. Enable skips windows absent from live WHAM data; disable also clears stale enabled windows. The UI reuses granular `/api/settings` writes, reconciles partial failures, and retries the original ON/OFF intent without replacing unrelated settings or completed reset markers. The API still rejects enabling an unavailable window with HTTP 409. At a reported reset time, opencodex sends one minimal non-stored Codex message using that account's quota and persists the activated timestamp. This does not apply to API-key providers. |
| `codexAccountNamespaces?` | `Record<string, string>` | — | Optional map from an arbitrary public model selector to a stored Codex account target. When account-qualified picker rows are enabled, each selector whose target is present adds separate `<selector>/<native-openai-model>` rows to the Codex picker; each row uses only that account. With any selector active, bare native rows are hidden in the picker, but their ids remain routable and listed by raw `/v1/models` unless explicitly disabled. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ description: 提供方配置、凭据、配额,以及模型目录命令。

这些命令用于配置上游提供方、认证账号、管理凭据池,并控制暴露给 Codex 的模型目录。

使用剩余套餐额度可通过 `ocx account use-remaining openai main on` 开启,再用
`ocx account use openai main` 选择主账号;`status --json` 查看状态,`off` 关闭。
将 `main` 替换为唯一的池账号 ID 或别名即可控制该账号。开启后关闭该账号按用量提前切换;
主账号的短、长窗口硬锁阈值延后到 100%。开关跨重启保留,关闭后恢复原有阈值。
暂停、重新认证、上游冷却和付费 credits 权限仍然生效;此开关不会开启付费额度。
详见[英文说明](/reference/cli/providers-accounts/#use-remaining-included-quota)。

## 提供方

### `ocx provider <subcommand>`
Expand Down
1 change: 1 addition & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"version": 1,
"root": "tests",
"explicit": {
"codex-use-remaining-quota.test.ts": "codex-integration",
"cli-log-view-filter.test.ts": "cli", "cli-usage-model-search.test.ts": "cli", "cli-companion-usage.test.ts": "cli", "cli-account-key-quota.test.ts": "cli",
"cli-observe-snapshot.test.ts": "cli", "cli-access-data-plane.test.ts": "cli", "cli-access-key-guard.test.ts": "cli", "cli-access-rename.test.ts": "cli",
"cli-access-audio.test.ts": "cli", "cli-access-audio-live.test.ts": "cli", "cli-log-follow.test.ts": "cli", "cli-injection-follow.test.ts": "cli",
Expand Down
10 changes: 7 additions & 3 deletions skills/ocx/references/01_management_surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ These answer in the CLI head and never reach the proxy, so they work with nothin
|---|---|
| [lifecycle](01_surface_lifecycle.md) | 12 |
| [providers-models](01_surface_providers-models.md) | 47 |
| [accounts](01_surface_accounts.md) | 40 |
| [accounts](01_surface_accounts.md) | 41 |
| [agents-routing](01_surface_agents-routing.md) | 48 |
| [integrations](01_surface_integrations.md) | 40 |
| [observe-system](01_surface_observe-system.md) | 92 |
Expand Down Expand Up @@ -599,6 +599,10 @@ Original invocation order. These headings preserve links to the previous single-

[State-changing task](01_surface_accounts.md#ocx-account-pool)

### `ocx account use-remaining`

[State-changing task](01_surface_accounts.md#ocx-account-use-remaining)

### `ocx account credits`

[State-changing task](01_surface_accounts.md#ocx-account-credits)
Expand Down Expand Up @@ -1361,6 +1365,6 @@ Original invocation order. These headings preserve links to the previous single-

## Counts

- declared capabilities: 328
- of those, state-changing: 198
- declared capabilities: 329
- of those, state-changing: 199
- head-resolved invocations: 2
25 changes: 24 additions & 1 deletion skills/ocx/references/01_surface_accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Use these declarations to choose a task, then check its flags and authority before execution.
Non-mutating probes may still contact providers, consume quota or refresh caches.

Declared capabilities: 40.
Declared capabilities: 41.

### `ocx account login`

Expand Down Expand Up @@ -868,6 +868,29 @@ JSON mode: `envelope`.
- Reads the target supported-field list before a narrow PUT. Null/unsupported, stored/effective and generic inert policy stay distinct; disabling does not promise that reactive429 rotation stops.
- No local classification/defaults are round-tripped into the live target; supported-field reads are not CAS.

### `ocx account use-remaining`

Usage: `ocx account use-remaining openai <id|alias|main> <on|off|status> [--json]`

Choose whether one Codex account keeps using its remaining included quota.

State-changing: yes.

| Method | Route |
|---|---|
| GET | `/api/codex-auth/accounts` |
| GET | `/api/codex-auth/accounts/use-remaining` |
| PUT | `/api/codex-auth/accounts/use-remaining` |

| Flag | Value | Meaning |
|---|---|---|
| `--json` | boolean | Emit the validated account policy. |

JSON mode: `envelope`.

- On disables proactive usage switching for the selected account and raises main-account protection to 100%. Off restores the configured thresholds.
- This is a persistent preference, not account selection. Pause, reauthentication, cooldown, entitlement and paid-credit policy still apply.

### `ocx account credits`

Usage: `ocx account credits openai <id|alias|main> <on|off> [--json]; ocx account credits openai --all <on|off> [--json]`
Expand Down
6 changes: 6 additions & 0 deletions src/cli/account-policy-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const threshold = z.number().int().min(0).max(100);
const nullableThreshold = threshold.nullable();
const row = z.object({
id: accountId, alias: text.optional(), autoSwitchThresholdOverride: nullableThreshold.optional(),
useRemainingQuota: z.boolean().optional(),
quota: z.object({ shortWindowSeconds: z.number().finite().optional(), shortResetAt: z.number().finite().optional(),
weeklyResetAt: z.number().finite().optional() }).nullable().optional(),
quotaAutoRefresh: z.object({ fiveHourAvailable: z.boolean(), weeklyAvailable: z.boolean(),
Expand Down Expand Up @@ -39,6 +40,11 @@ export const poolSchema = z.object({
export const thresholdReceipt = z.object({ ok: z.literal(true), id: accountId,
autoSwitchThresholdOverride: nullableThreshold, autoSwitchThreshold: threshold });
export const creditReceipt = z.object({ ok: z.literal(true), id: accountId, creditsAfterLimit: z.boolean() });
export const remainingQuotaReceipt = z.object({ ok: z.literal(true), id: accountId,
useRemainingQuota: z.boolean(), autoSwitchThreshold: threshold,
mainAccountHardLock: z.object({ enabled: z.boolean(), state: z.enum(["off", "unknown", "ready", "blocked"]),
thresholds: z.object({ short: threshold, long: threshold }) }).optional(),
});
export const creditAllReceipt = z.object({ ok: z.literal(true), all: z.boolean(),
ids: z.array(accountId).refine(ids => new Set(ids).size === ids.length) });
const window = z.enum(["five_hour", "seven_day", "seven_day_overage_included"]);
Expand Down
36 changes: 32 additions & 4 deletions src/cli/account-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { resolveCodexAccountTargetFromRows } from "./account-target";
import { runCatalogAction } from "./catalog-command-result";
import { serializeManagementJson } from "./json-input";
import { creditAllReceipt, creditReceipt, grantsSchema, parseDto, poolSchema, record,
rosterSchema, threshold, thresholdReceipt } from "./account-policy-dto";
rosterSchema, threshold, thresholdReceipt, remainingQuotaReceipt } from "./account-policy-dto";
import { CliUsageError, RuntimeApiError, printData, runtimeBaseUrl, runtimeRequest, summaryLines,
takeFlag, takeOptionWithSyntax, type RuntimeApiDeps } from "./runtime-api";

type Subcommand = "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants";
type Subcommand = "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants" | "use-remaining";
const USAGE = "Usage: ocx account pool <provider> [--enabled on|off] [--threshold 0-100] [--strategy NAME] [--sticky 1-100] [--quota-window NAME] [--json]\n"
+ " ocx account auto-switch openai <status|on|off|inherit|threshold N> --account ID [--json]\n"
+ " ocx account credits openai <ID on|off|--all on|off> [--json]\n"
+ " ocx account use-remaining openai ID <on|off|status> [--json]\n"
+ " ocx account quota-activation openai ID --window <fiveHour|weekly> <on|off> [--json]\n"
+ " ocx account anthropic-reset-grants [ID] [--json]";
const READ_ERRORS = { no_account: "No matching Anthropic OAuth account.", auth_failed: "Sign in to this Anthropic account again.",
Expand Down Expand Up @@ -52,6 +53,32 @@ function output(data: Record<string, unknown>, json: boolean, note?: string): nu
}
function requireEqual(actual: unknown, expected: unknown): void { if (actual !== expected) throw new Error("Unverified account policy result"); }

async function useRemaining(args: string[], json: boolean, deps: RuntimeApiDeps): Promise<number> {
if (args.shift() !== "openai") usage();
const requested = selector(args.shift()), action = args.shift();
if (!["on", "off", "status"].includes(action ?? "")) usage();
done(args);
const pinned = { ...deps, baseUrl: await runtimeBaseUrl(deps) };
const { id } = await target(pinned, requested);
const path = "/api/codex-auth/accounts/use-remaining";
const result = parseDto(remainingQuotaReceipt, await request(
action === "status" ? path + "?id=" + encodeURIComponent(id) : path,
pinned, action === "status" ? undefined : { id, useRemainingQuota: action === "on" },
));
requireEqual(result.id, id);
if (action !== "status") requireEqual(result.useRemainingQuota, action === "on");
if (result.useRemainingQuota) {
requireEqual(result.autoSwitchThreshold, 0);
if (id === MAIN_CODEX_ACCOUNT_ID) {
requireEqual(result.mainAccountHardLock?.thresholds.short, 100);
requireEqual(result.mainAccountHardLock?.thresholds.long, 100);
}
}
return output(result, json, result.useRemainingQuota
? "Use remaining included quota: on. Paid-credit permission is unchanged."
: "Use remaining included quota: off. Configured usage thresholds apply.");
}

async function pool(args: string[], json: boolean, deps: RuntimeApiDeps): Promise<number> {
const provider = selector(args.shift()).toLowerCase();
if (!isValidProviderName(provider)) usage();
Expand Down Expand Up @@ -121,12 +148,12 @@ async function codex(sub: "auto-switch" | "credits" | "quota-activation", args:
const override = selected?.autoSwitchThresholdOverride;
if (override === undefined) throw new Error("Missing account threshold evidence");
const active = record(await request("/api/codex-auth/active", pinned));
const effective = override ?? parseDto(threshold, active.autoSwitchThreshold);
const effective = selected?.useRemainingQuota ? 0 : override ?? parseDto(threshold, active.autoSwitchThreshold);
return output({ ok: true, id, autoSwitchThresholdOverride: override, autoSwitchThreshold: effective }, json);
}
const result = parseDto(thresholdReceipt, await request("/api/codex-auth/auto-switch", pinned, { id, threshold: value }));
requireEqual(result.id, id); requireEqual(result.autoSwitchThresholdOverride, value);
if (value !== null) requireEqual(result.autoSwitchThreshold, value);
if (value !== null && !rows.find(row => row.id === id)?.useRemainingQuota) requireEqual(result.autoSwitchThreshold, value);
return output(result, json, "Account threshold saved.");
}
if (sub === "credits") {
Expand Down Expand Up @@ -156,6 +183,7 @@ export async function handleAccountPolicyCommand(sub: Subcommand, args: string[]
const rest = [...args], json = takeFlag(rest, "--json");
if (rest.includes("--json")) usage();
if (sub === "pool") return pool(rest, json, deps);
if (sub === "use-remaining") return useRemaining(rest, json, deps);
if (sub !== "anthropic-reset-grants") return codex(sub, rest, json, deps);
const id = rest.length ? selector(rest.shift()) : undefined;
done(rest);
Expand Down
5 changes: 3 additions & 2 deletions src/cli/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const ACCOUNT_USAGE = `Usage:
ocx account login <provider> [--id <account-id>] [--reauth] [--open-browser on|off] [--add-account on|off] [--code -] [--no-wait] [--json]
ocx account pool <provider> [--enabled on|off] [--threshold N] [--strategy NAME] [--sticky N] [--quota-window W] [--json]
ocx account credits openai <ID on|off|--all on|off> [--json]
ocx account use-remaining openai <ID|main> <on|off|status> [--json]
ocx account quota-activation openai ID --window fiveHour|weekly <on|off> [--json]
ocx account anthropic-reset-grants [ID] [--json]
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
Expand Down Expand Up @@ -423,11 +424,11 @@ async function cmdClear(rest: string[], deps: AccountDeps): Promise<number> {
export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promise<number> {
const [sub, ...rest] = args;
try {
if (["pool", "credits", "quota-activation", "anthropic-reset-grants"].includes(sub ?? "")
if (["pool", "credits", "quota-activation", "anthropic-reset-grants", "use-remaining"].includes(sub ?? "")
|| (sub === "auto-switch" && rest[0]?.trim().toLowerCase() === "openai"
&& rest.some(arg => arg === "--account" || arg.startsWith("--account=")))) {
const { handleAccountPolicyCommand } = await import("./account-policy");
return handleAccountPolicyCommand(sub as "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants", rest, deps);
return handleAccountPolicyCommand(sub as "pool" | "auto-switch" | "credits" | "quota-activation" | "anthropic-reset-grants" | "use-remaining", rest, deps);
}
if (sub === "list") return await cmdList(rest, deps);
if (sub === "history") {
Expand Down
11 changes: 11 additions & 0 deletions src/cli/capabilities-accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ export const ACCOUNT_CAPABILITIES: readonly Capability[] = [
json: "envelope",
details: ["Reads the target supported-field list before a narrow PUT. Null/unsupported, stored/effective and generic inert policy stay distinct; disabling does not promise that reactive429 rotation stops.","No local classification/defaults are round-tripped into the live target; supported-field reads are not CAS."],
},
{
command: ["account", "use-remaining"],
usage: "ocx account use-remaining openai <id|alias|main> <on|off|status> [--json]",
summary: "Choose whether one Codex account keeps using its remaining included quota.",
routes: [{ method: "GET", path: "/api/codex-auth/accounts" },
{ method: "GET", path: "/api/codex-auth/accounts/use-remaining" }, { method: "PUT", path: "/api/codex-auth/accounts/use-remaining" }],
flags: [{ name: "--json", value: "boolean", summary: "Emit the validated account policy." }],
mutates: true, json: "envelope",
details: ["On disables proactive usage switching for the selected account and raises main-account protection to 100%. Off restores the configured thresholds.",
"This is a persistent preference, not account selection. Pause, reauthentication, cooldown, entitlement and paid-credit policy still apply."],
},
{
command: ["account","credits"],
usage: "ocx account credits openai <id|alias|main> <on|off> [--json]; ocx account credits openai --all <on|off> [--json]",
Expand Down
Loading
Loading