Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
418b824
docs(readme): mention cpa-status in Hermes entry
duu261 Nov 5, 2026
59a2b35
docs(readme): add SWE-2 Flash to Devin models
kdcokenny Nov 6, 2026
68cd757
fix(codex): prevent replay after uncertain refresh outcomes
luispater Nov 6, 2026
0559fa3
feat(management): expose oauth_provider in credential list
WizisCool Nov 6, 2026
bfac858
fix(codex): normalize small output budgets for all source formats
luispater Nov 6, 2026
2443e88
fix(codex): normalize nested custom tools across Chat Completions rou…
luispater Nov 6, 2026
80ee739
fix(codex): preserve SIWC rotations and honor renewal windows
JehudaRajasa Nov 6, 2026
3891eba
fix(codex): reuse the issued SIWC client during CLI reauthorization
JehudaRajasa Nov 6, 2026
8c6d8b1
fix(codex): defer SIWC renewal during macOS maintenance wakes
JehudaRajasa Nov 6, 2026
baec297
fix(auth): retain partition failures alongside SIWC wake cancellation
JehudaRajasa Nov 6, 2026
03c394e
test(codex): make SIWC power-state checks deterministic
JehudaRajasa Nov 6, 2026
a46be7a
fix(codex): retain SIWC renewal windows in refresh receipts
JehudaRajasa Nov 6, 2026
374056f
fix(codex): preserve prompt cache prefix when loading deferred tools …
luispater Nov 6, 2026
c9f9739
Merge pull request #7308 from WizisCool/feat/management-oauth-provider
luispater Nov 6, 2026
482efbf
feat(auth): enhance OAuth provider handling with explicit hints and m…
luispater Nov 6, 2026
ea0b471
fix(claude): align OAuth authorization state and scopes
GavinKung Nov 7, 2026
eabcd01
Merge pull request #7317 from router-for-me/fix/claude-oauth-authoriz…
GavinKung Nov 7, 2026
2776281
fix(claude): keep mid-session responses system items in place to pres…
luispater Nov 7, 2026
c5251b5
fix(translator): preserve reasoning and text across multi-part gemini…
luispater Nov 7, 2026
5f38d55
fix(auth): keep native Codex available during SIWC recovery
GavinKung Nov 7, 2026
bbefed6
Merge pull request #7305 from router-for-me/fix/codex-refresh-uncerta…
GavinKung Nov 7, 2026
f4d3f06
fix(openai): replace responses websocket transcript when pending tool…
luispater Nov 7, 2026
b5b724d
fix(pluginhost): reuse content-addressed plugin shadow copies across …
luispater Nov 7, 2026
69637a6
fix(openai): skip whitespace-only reasoning when opening Claude think…
luispater Nov 7, 2026
ce38c75
fix(claude): drain streamed response body after message_stop to reuse…
luispater Nov 7, 2026
e6931d5
fix(translator): preserve standalone Claude output_config.effort on O…
luispater Nov 7, 2026
91e0dee
fix(openai): prevent SSE keep-alive injection mid-line during image s…
luispater Nov 7, 2026
0e07108
Merge pull request #7306 from kdcokenny/docs/swe-2-flash-availability
luispater Nov 7, 2026
f69db99
feat(plugin-store): support opt-in release cache refresh on store lis…
luispater Nov 7, 2026
466b28d
Merge branch 'codex/pr-7311' into dev
luispater Nov 7, 2026
09b3a57
fix(codex): harden SIWC refresh deferral and expires_in parsing
luispater Nov 7, 2026
05e668c
fix(responses): sanitize orphan tool outputs lacking call_id
luispater Nov 8, 2026
40c1618
feat(auth): support session-affinity-rebind-on never mode
luispater Nov 8, 2026
0074897
feat(pluginhost): expose token modality details in usage.handle
luispater Nov 8, 2026
681ce41
Merge pull request #7281 from duu261/docs/readme-cpa-status
luispater Nov 8, 2026
2c5b01f
fix(claude): disable default upstream connection reuse and revert pos…
luispater Nov 8, 2026
14e5771
fix(pluginhost): support credential-scoped rate limit errors from plu…
luispater Nov 8, 2026
098af7d
fix(auth): share and migrate model cooldowns across equivalent model …
luispater Nov 9, 2026
f36b195
perf(antigravity): skip reasoning replay KV for symmetric gemini/inte…
sususu98 Nov 8, 2026
0561334
fix(home): preserve fork kind in home dispatch
hkfires Nov 9, 2026
d204515
feat(auth): compensate weighted routing for sticky sessions
hkfires Nov 9, 2026
e479ab1
perf(auth): compact weighted round-robin pools
hkfires Nov 9, 2026
bebd92c
feat(cmd): add -login-proxy flag for CLI OAuth logins
whale9820 Nov 9, 2026
eb6e25a
fix(devin): count cache-write tokens in total input
ngvoicu Nov 9, 2026
aa3fe8e
Merge pull request #7377 from router-for-me/home
luispater Nov 9, 2026
542a525
fix(claude): recognize native client user-agent extensions
GavinKung Nov 9, 2026
5930433
fix(claude): retain request fields with stabilized client profiles
GavinKung Nov 9, 2026
d1218c0
Merge pull request #7375 from router-for-me/fix/claude-code-user-agen…
GavinKung Nov 9, 2026
e84d483
fix(auth): account pinned traffic in bare weighted routing
hkfires Nov 9, 2026
cc95661
feat(gemini): support asymmetric thoughtSignature replay KV across Ge…
sususu98 Nov 9, 2026
00c6ae8
perf(cache): eliminate double base64 encoding for reasoning replay KV…
sususu98 Nov 9, 2026
bce665d
Merge pull request #7374 from sususu98/feat/asymmetric-signature-replay
sususu98 Nov 9, 2026
216f294
feat(home): report reused dispatch turns in usage
hkfires Nov 9, 2026
beb4335
fix(claude): materialize top-level cache_control in cloaked requests
luispater Nov 9, 2026
23e31fe
fix(executor): stabilize Codex refresh and websocket tests under CPU …
luispater Nov 9, 2026
2c4322c
Merge pull request #7380 from router-for-me/auth
luispater Nov 9, 2026
4b3f2c7
fix(logging): skip request error dumps for unrouted 404 responses
luispater Nov 9, 2026
628e54a
fix(watcher): update active auth directory and fsnotify watch on runt…
luispater Nov 9, 2026
8de6e3f
Merge pull request #7378 from ngvoicu/fix/devin-usage-cache-write
luispater Nov 9, 2026
dae6007
Merge pull request #7370 from whale9820/feat/oauth-login-proxy
luispater Nov 9, 2026
19a0a85
feat(models): add claude-fable-5-2 and gemini-omni-1.2-flash to model…
luispater Nov 9, 2026
714f052
fix(codex): allow hosted image_generation injection on standard Respo…
luispater Nov 9, 2026
b4e7dbd
fix(claude): bump Claude Code CLI fingerprint baseline to 2.1.321
adamentwistle Nov 10, 2026
1649a00
Merge pull request #7392 from adamentwistle/fix/claude-cli-baseline-2…
luispater Nov 10, 2026
6db7d4b
fix(claude): extend preserved thinking and per-turn timing gates to C…
luispater Nov 10, 2026
c604496
chore: merge upstream/main through 6db7d4b8
jiroamato Nov 10, 2026
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ You can access the following providers locally and with multiple CLI accounts th
</tr>
<tr>
<td align="center"><a href="https://devin.ai/cli">Devin</a></td>
<td>Devin models (SWE-2, GPT-6 Astra, Claude Fable 5.1, etc.). Connect a Devin account with <code>--devin-login</code> to route the models available to that account.</td>
<td>Devin models (SWE-2, SWE-2 Flash, GPT-6 Astra, Claude Fable 5.1, etc.). Connect a Devin account with <code>--devin-login</code> to route the models available to that account.</td>
</tr>
</tbody>
</table>
Expand Down Expand Up @@ -406,7 +406,7 @@ A Cloudflare-based LLM gateway and proxy server: CLIProxyAPI deployed to Cloudfl

### [Hermes CLIProxyAPI Provider](https://github.com/duu261/hermes-cliproxyapi-provider)

Model-provider plugin for [Hermes Agent](https://github.com/NousResearch/hermes-agent) that registers one Hermes provider per CLIProxyAPI provider, each on the wire its executor accepts (Anthropic Messages, Responses or Chat Completions), with context windows and reasoning levels read from CLIProxyAPI. Optional read-only Management API pass for exact provider grouping. Listed in the [Hermes plugin catalog](https://hermes-agent.nousresearch.com/docs/plugins/cliproxyapi-provider): `hermes plugins install cliproxyapi-provider`.
Model-provider plugin for [Hermes Agent](https://github.com/NousResearch/hermes-agent) that registers one Hermes provider per CLIProxyAPI provider, each on the wire its executor accepts (Anthropic Messages, Responses or Chat Completions), with context windows and reasoning levels read from CLIProxyAPI. Optional read-only Management API pass for exact provider grouping. A companion plugin adds a `/cpa` chat command for each pool's account health and, on request, each account's live quota. Listed in the [Hermes plugin catalog](https://hermes-agent.nousresearch.com/docs/plugins): `hermes plugins install cliproxyapi-provider` and `hermes plugins install cpa-status`.

### [Ebb](https://github.com/lucasnsaraujo/ebb)

Expand Down
4 changes: 2 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ CLIProxyAPI 是一个为 CLI 提供 OpenAI/Gemini/Claude/Codex/Grok 兼容 API
</tr>
<tr>
<td align="center"><a href="https://devin.ai/cli">Devin</a></td>
<td>Devin 提供的模型(SWE-2、GPT-6 Astra、Claude Fable 5.1 等)。使用 <code>--devin-login</code> 连接 Devin 账户,即可路由该账户可用的模型。</td>
<td>Devin 提供的模型(SWE-2、SWE-2 Flash、GPT-6 Astra、Claude Fable 5.1 等)。使用 <code>--devin-login</code> 连接 Devin 账户,即可路由该账户可用的模型。</td>
</tr>
</tbody>
</table>
Expand Down Expand Up @@ -301,7 +301,7 @@ Claude Code 状态栏,按当前 CPA 实例展示 Codex / Grok / Antigravity /

### [Hermes CLIProxyAPI Provider](https://github.com/duu261/hermes-cliproxyapi-provider)

[Hermes Agent](https://github.com/NousResearch/hermes-agent) 的模型提供方插件:为每个 CLIProxyAPI 提供方注册一个 Hermes 提供方,各自使用其执行器接受的协议(Anthropic Messages、Responses 或 Chat Completions),上下文窗口与推理等级读取自 CLIProxyAPI。可选只读 Management API 查询,用于精确的提供方分组。已收录于 [Hermes 插件目录](https://hermes-agent.nousresearch.com/docs/plugins/cliproxyapi-provider):`hermes plugins install cliproxyapi-provider`。
[Hermes Agent](https://github.com/NousResearch/hermes-agent) 的模型提供方插件:为每个 CLIProxyAPI 提供方注册一个 Hermes 提供方,各自使用其执行器接受的协议(Anthropic Messages、Responses 或 Chat Completions),上下文窗口与推理等级读取自 CLIProxyAPI。可选只读 Management API 查询,用于精确的提供方分组。配套插件提供 `/cpa` 聊天命令,可查看每个号池的账号状态,并可按需查看每个账号的实时额度。已收录于 [Hermes 插件目录](https://hermes-agent.nousresearch.com/docs/plugins):`hermes plugins install cliproxyapi-provider` 与 `hermes plugins install cpa-status`。

### [Ebb](https://github.com/lucasnsaraujo/ebb)

Expand Down
4 changes: 2 additions & 2 deletions README_JA.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ CLIProxyAPI は、CLI向けのOpenAI/Gemini/Claude/Codex/Grok互換APIインタ
</tr>
<tr>
<td align="center"><a href="https://devin.ai/cli">Devin</a></td>
<td>Devinのモデル(SWE-2、GPT-6 Astra、Claude Fable 5.1など)。<code>--devin-login</code>でDevinアカウントに接続し、そのアカウントで利用可能なモデルにリクエストを送れます。</td>
<td>Devinのモデル(SWE-2、SWE-2 Flash、GPT-6 Astra、Claude Fable 5.1など)。<code>--devin-login</code>でDevinアカウントに接続し、そのアカウントで利用可能なモデルにリクエストを送れます。</td>
</tr>
</tbody>
</table>
Expand Down Expand Up @@ -300,7 +300,7 @@ Cloudflare ベースの LLM ゲートウェイ兼プロキシサーバー。CLIP

### [Hermes CLIProxyAPI Provider](https://github.com/duu261/hermes-cliproxyapi-provider)

[Hermes Agent](https://github.com/NousResearch/hermes-agent) 向けのモデルプロバイダープラグイン。CLIProxyAPI のプロバイダーごとに Hermes プロバイダーを 1 つ登録し、それぞれの executor が受け付けるプロトコル(Anthropic Messages、Responses、Chat Completions)を使います。コンテキストウィンドウと推論レベルは CLIProxyAPI から取得します。正確なプロバイダーグループ分けのための読み取り専用 Management API 照会はオプションです。[Hermes プラグインカタログ](https://hermes-agent.nousresearch.com/docs/plugins/cliproxyapi-provider)に掲載されています:`hermes plugins install cliproxyapi-provider`。
[Hermes Agent](https://github.com/NousResearch/hermes-agent) 向けのモデルプロバイダープラグイン。CLIProxyAPI のプロバイダーごとに Hermes プロバイダーを 1 つ登録し、それぞれの executor が受け付けるプロトコル(Anthropic Messages、Responses、Chat Completions)を使います。コンテキストウィンドウと推論レベルは CLIProxyAPI から取得します。正確なプロバイダーグループ分けのための読み取り専用 Management API 照会はオプションです。コンパニオンプラグインの `/cpa` チャットコマンドで、各プールのアカウント状態と、必要に応じて各アカウントのリアルタイムのクォータを確認できます。[Hermes プラグインカタログ](https://hermes-agent.nousresearch.com/docs/plugins)に掲載されています:`hermes plugins install cliproxyapi-provider` および `hermes plugins install cpa-status`。

### [Ebb](https://github.com/lucasnsaraujo/ebb)

Expand Down
5 changes: 5 additions & 0 deletions cmd/fetch_codex_models/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,12 @@ func ensureAccessToken(ctx context.Context, store *sdkauth.FileTokenStore, auth
return "", false, fmt.Errorf("missing access_token and refresh_token")
}

journal, errJournal := store.RefreshJournalFor(auth)
if errJournal != nil {
return "", false, errJournal
}
svc := codexauth.NewCodexAuthWithProxyURL(nil, auth.ProxyURL)
svc.SetRefreshJournal(journal)
tokenData, errRefresh := svc.RefreshTokensWithRetry(ctx, refreshToken, 3)
if errRefresh != nil {
return "", false, errRefresh
Expand Down
3 changes: 3 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func main() {
var claudeLogin bool
var noBrowser bool
var oauthCallbackPort int
var loginProxyURL string
var antigravityLogin bool
var kimiLogin bool
var kimiAILogin bool
Expand Down Expand Up @@ -140,6 +141,7 @@ func main() {
flag.BoolVar(&claudeLogin, "claude-login", false, "Login to Claude using OAuth")
flag.BoolVar(&noBrowser, "no-browser", false, "Don't open browser automatically for OAuth")
flag.IntVar(&oauthCallbackPort, "oauth-callback-port", 0, "Override OAuth callback port (defaults to provider-specific port)")
flag.StringVar(&loginProxyURL, "login-proxy", "", "Proxy URL used for the OAuth login flow and saved as the new credential's proxy_url (http, https, socks5, socks5h)")
flag.BoolVar(&antigravityLogin, "antigravity-login", false, "Login to Antigravity using OAuth")
flag.BoolVar(&kimiLogin, "kimi-login", false, "Login to Kimi (.com) using OAuth")
flag.BoolVar(&kimiAILogin, "kimi-ai-login", false, "Login to Kimi.ai using OAuth")
Expand Down Expand Up @@ -656,6 +658,7 @@ func main() {
options := &cmd.LoginOptions{
NoBrowser: noBrowser,
CallbackPort: oauthCallbackPort,
ProxyURL: loginProxyURL,
}

commandMode := vertexImport != "" || siwcBind != "" || antigravityLogin || codexLogin || codexDeviceLogin || claudeLogin || kimiLogin || kimiAILogin || xaiLogin || devinLogin || metaLogin
Expand Down
21 changes: 18 additions & 3 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,19 @@ routing:
strategy: "round-robin" # round-robin (default), weighted-round-robin, fill-first
# weighted-round-robin uses each credential's integer weight (default 1, maximum 1,000,000).
# Non-positive weights exclude the credential while this strategy is active.
# Sticky requests (including inherited and LCP bindings) also consume weighted credits
# without moving existing bindings. Compensation is scoped to each eligible credential set;
# priority tiers, transport preferences, and retry/cooldown subsets do not share credits.
# Pinned requests use the eligible pool before pinning, within the pinned credential
# priority tier and transport class, even when session-affinity is disabled.
# Returning to a pool resumes its retained credits.
# Settled credits are capped at +/-32 times the eligible pool total; ratios are approximate.
# In-flight offsets are preserved until the pool settles, regardless of commit order.
# Sticky requests keep their bindings but stop adding compensation when the full
# credit delta cannot fit the cap; new sessions keep earning credit to avoid starvation.
# Credits are process-local and reset on restart or selector replacement.
# In Home mode, compatible Home versions charge retained WS turns from usage after completion;
# CPA preserves the retained credential and concurrency slot instead of redispatching each turn.
# For OAuth/file credentials, add a top-level numeric "weight" field to the auth JSON.
# Enable universal session-sticky routing for all clients.
# Explicit Claude Code, Codex, OpenCode, and pi session headers are preferred,
Expand Down Expand Up @@ -198,6 +211,8 @@ routing:
# - "quota": transient errors (408, 5xx, empty stream, buffer limit) fail over for that
# request only while keeping the session bound to its original credential; quota and auth
# errors (429, 401/402/403, 404/model support, invalid_grant) rebind as normal.
# - "never": never move or fail over an established session to another credential;
# keep the binding and return the error (or retry the same credential) instead.
# Ignored when routing.session-affinity is false.
session-affinity-rebind-on: "any"

Expand Down Expand Up @@ -512,9 +527,9 @@ upstream:
# the same exact software baseline rather than learning newer client versions (unless the
# credential resolves to cloak_mode: never, which preserves the confirmed caller's headers).
# header-defaults:
# user-agent: "claude-cli/2.1.280 (external, cli)"
# package-version: "0.112.1"
# runtime-version: "v26.3.0"
# user-agent: "claude-cli/2.1.321 (external, cli)"
# package-version: "0.128.0"
# runtime-version: "v26.12.1"
# os: "MacOS"
# arch: "arm64"
# timeout: "600"
Expand Down
34 changes: 34 additions & 0 deletions docs/codex-refresh-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Codex refresh recovery

A lost refresh response does not establish whether the provider consumed the refresh token. CLIProxyAPI records a claim **before** sending a Codex or local SIWC refresh request. Transport failures, unreadable or invalid successful responses, and HTTP responses without a qualified no-effect contract stop replay. A 429 or 5xx status alone is not such a contract. Redirects cannot replay refresh POSTs.

## Recovery

- An unresolved outcome is reported as `refresh_outcome_unknown`, with its failure stage. An unexpired access token remains usable; the refresh grant requires recovery.
- Scheduled and forced refreshes cannot resubmit a claimed grant. There is no lease expiry or time-based reset.
- A successful owner saves a receipt before returning its new credentials. Late callers reuse unexpired receipts and follow subsequent recorded rotations rather than resubmitting old tokens. An expired receipt requires recovery, including when a successful response returned the same refresh token; it is never reported as successful renewal.
- Adopt credentials from the successful refresh owner, or sign in again to obtain a **new** grant. Re-importing the same token, deleting an auth record, or restarting the server is not recovery.
- If a process stops between recording its claim and saving its result, the outcome remains uncertain. A failed journal write also fails closed; do not delete the claim to retry.

## Persistence and deployment

The journal must accompany credentials across backups, migrations, and restores:

| Credential storage | Authoritative journal |
| --- | --- |
| Files | `.codex-refresh/` under the auth directory |
| PostgreSQL | `<auth-table>_refresh_journal` in the configured schema |
| Git | Immutable `refs/tags/cliproxy-refresh/` references in the same remote |
| S3-compatible storage | `codex-refresh/` under the configured object prefix |

Receipts contain credentials. Protect them with the same access controls as auth files. Do not publish the Git repository, bucket, database, or journal directory. Claims and receipts are retained rather than expiring automatically. Revoke the associated grants before administratively removing their journal history. Restoring older credentials without their complete journal history is unsafe.

On Unix, file claims synchronize their canonical directory ancestry before an exchange; those directories must permit synchronization. Windows preserves claims across process restarts but does not support directory fsync, so this is not a power-loss durability guarantee.

PostgreSQL initializes the journal before refresh workers start, using a transaction-scoped advisory lock to coordinate concurrent initialization. It requires permission to create the journal table and acquire that lock. Git requires permission to create the journal tags; all journal pushes are non-force. S3 backends must implement atomic `If-None-Match: *` writes. A capability check rejects backends that ignore that condition. Storage failure never falls back to refreshing against a local mirror.

All processes sharing credentials must use the same authoritative journal and this safety protocol. Older deployments or external clients refreshing the same grant cannot be coordinated by a journal they do not participate in.

Direct SDK users can attach a `codex.RefreshJournal` using `WithRefreshJournal` or `SetRefreshJournal`. Without a configured auth directory or explicit journal, protection is process-local only; a caller persisting credentials across restarts must also provide a durable journal.

CLIProxyAPIHome owns its separate outer-token refresh implementation. These changes cover refreshes performed in this repository (including local SIWC refreshes in Home mode); the Home service requires its own corresponding update.
44 changes: 43 additions & 1 deletion docs/management-api-v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,48 @@ The Home-owned revision fields
`credentials.concurrency.observation-barrier-revision`, and `plugins.auth-revision`
cannot be changed through these endpoints.

### Weighted routing with session affinity

Set `routing.strategy` to `weighted-round-robin` to use per-credential integer
`weight` values (default `1`, maximum `1000000`; non-positive values are excluded).
With `routing.session-affinity` enabled, sticky requests, parent inheritance,
Guardian selections, and LCP matches also consume weighted credits without moving
existing bindings. New or reselected sessions favor under-served credentials
within the same eligible credential set. For example, with weights `3:1`, four
sticky requests to the second credential can be compensated by twelve new
requests to the first when both remain eligible.

Different priority tiers, transport preferences, and retry/cooldown candidate
sets keep separate credits; returning to a set resumes its retained credits.
Pinned requests account against the eligible pool before pinning, within the
pinned credential's priority tier and transport class, even when session affinity
is disabled. Pinned and ordinary requests share the same weighted credits. A
lower-priority binding never shifts the request ratio among higher-priority credentials.

Settled credits are capped at ±32 times the eligible pool total, so request
ratios are approximate rather than guaranteed. While reservations are in flight,
committed balances can temporarily exceed the cap; their pending offsets remain
intact until the pool settles, including when the last reservation rolls back. Sticky requests still use their binding at
the cap, but stop adding compensation when their full credit delta cannot fit.
New sessions continue repaying debt and earning credit, so excessive sticky
traffic does not starve smaller-weight peers.

Credits are local to the CPA process and reset on restart, selector replacement,
or bounded pool-cache eviction. Concurrent selections see pending credit; local
selection/binding success confirms it, while failure releases it. In Home mode,
scheduling and credit accounting remain owned by Home. With matching CPA and Home
versions, retained WebSocket responses (including Codex duplex successors) report
`home_selection.reused` through usage so Home can charge later turns without
redispatching or acquiring another concurrency slot. The scheduling model is
carried separately from the upstream model. First-turn primary usage does not
charge again. Split additional-model billing records (such as image usage) carry
`home_selection.additional` and never add scheduling charges, even when they have
independent execution IDs. Each reused execution is charged once through primary
usage; redelivery of that execution ID is deduplicated.
Compensation arrives after completion and uses Home's current eligible
pool and API-key permissions, so it remains approximate. Old peers ignore the
optional metadata; missing usage cannot compensate a reused turn.

### Codex multi-agent configuration migration

`client.codex.optimize-multi-agent-v2` is the sole runtime setting. Loading older
Expand Down Expand Up @@ -114,7 +156,7 @@ retain the corresponding business operation's fields.
| `/oauth/callback` | GET, POST | Submit an OAuth callback. |
| `/plugins` | GET | List installed plugins. |
| `/plugins/<id>` | DELETE | Delete a plugin. |
| `/plugins/store` | GET | List the plugin store. |
| `/plugins/store` | GET | List the plugin store (supports optional `?refresh=true` to bypass cached release checks). |
| `/plugins/store/<id>/install` | POST | Install or update a plugin. |
| `/plugins/<id>/quota` | GET, POST, DELETE | Read, fetch, or reset plugin quota. |

Expand Down
26 changes: 26 additions & 0 deletions internal/api/handlers/management/auth_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,11 @@ func (h *Handler) buildAuthFileEntryLocked(auth *coreauth.Auth, quota authFileQu
entry["request_retry"] = requestRetry
}
if quota.v8 {
// OAuth login entry the credential was signed in through; multi-entry plugins store it
// next to the provider, and the deprecated v0 route keeps its frozen response shape.
if oauthProvider := authOAuthProvider(auth); oauthProvider != "" {
entry["oauth_provider"] = oauthProvider
}
nowUTC := time.Now().UTC()
if exp, okExp := auth.AccessTokenExpirationTime(); okExp && !exp.IsZero() && exp.Unix() > 0 {
entry["access_token_expires_at"] = exp.UTC().Format(time.RFC3339)
Expand Down Expand Up @@ -980,6 +985,27 @@ func authProjectID(auth *coreauth.Auth) string {
return ""
}

func authOAuthProvider(auth *coreauth.Auth) string {
if auth == nil {
return ""
}
for _, key := range []string{"oauth_provider", "portal"} {
if auth.Attributes != nil {
if v := strings.ToLower(strings.TrimSpace(auth.Attributes[key])); v != "" {
return v
}
}
if auth.Metadata != nil {
if raw, ok := auth.Metadata[key].(string); ok {
if v := strings.ToLower(strings.TrimSpace(raw)); v != "" {
return v
}
}
}
}
return ""
}

func extractCodexIDTokenClaims(auth *coreauth.Auth) gin.H {
if auth == nil || auth.Metadata == nil {
return nil
Expand Down
Loading
Loading