Commit Graph

707 Commits

Author SHA1 Message Date
hkfires
e04d620cc1 feat(auth): normalize credential metadata keys
Canonicalize legacy config-style credential keys across stores,
management handlers, plugin auth, and file synthesis while preserving
explicit canonical values. Expose per-auth request_retry in auth file
management and add max-retry-credentials management routes.
2026-08-22 12:01:06 +08:00
hkfires
0a14eb70ce feat(auth): add retry round credential filtering 2026-08-22 01:20:02 +08:00
hkfires
601ca43090 feat(auth): add credential retry round contract
Redefine request-retry as additional credential retry rounds and
enforce max-retry-credentials per round. Home dispatch now carries
excluded and pinned auth constraints, supports remote retry limits, and
propagates cooldown retry-after metadata across exhausted rounds.

Move Antigravity upstream retries under conductor ownership to avoid
double-consuming retry attempts. Update configuration comments and add
coverage for Home retry rounds, cooldown handling, pinned credentials,
and legacy dispatcher compatibility.
2026-08-22 01:19:58 +08:00
Luis Pater
1d5b7612c6 fix(cliproxy): add protocol-aware plugin executor usage parsing for response and streaming payloads
Closes: #5122
2026-08-21 13:05:54 +08:00
sususu98
4b9d404fb0 feat(codex): add opt-in stream bootstrap buffering and overload failover (#5115)
* feat(codex): add opt-in stream bootstrap buffering

The upstream smuggles capacity rejections into an HTTP 200 stream. The
handshake events arrive normally and only a later event carries
{"error":{"type":"service_unavailable_error","code":
"server_is_overloaded"}}. By then the executor has already handed the
first chunk downstream, the response is committed, and the conductor can
no longer retry on another credential, so the request fails even though
other credentials were available.

When codex.stream-bootstrap-buffering is enabled the executor holds back
the handshake events until it can tell whether the stream carries real
output or a rejection. An overload rejection then fails the attempt
before any chunk is delivered, letting the conductor retry on another
credential; every other terminal failure is flushed in order and
delivered in-stream exactly as before.

Detection uses an event-type allow-list rather than a fixed count. On the
websocket transport codex.rate_limits and codex.response.metadata arrive
before response.created, making the first generated event the fifth
frame, so a small counter would release the stream before the rejection
is visible. Buffering is bounded and hitting the bound degrades to the
original unbuffered behaviour.

Two details are load-bearing. The error must be returned synchronously:
delivering it as the first stream chunk makes ExecuteStream downgrade it
into a committed 200 and the status is lost. And the websocket path must
not signal an upstream disconnect for a rejection it intends to retry,
because the downstream handler closes the client connection on that
signal and the retry would have nowhere to deliver.

The 503 status is produced only on this path rather than in the shared
codexTerminalFailureStatus mapping, so disabling the feature restores the
previous behaviour exactly, including cooldown classification and
retry-after parsing.

Defaults to false: response headers are withheld until generation
starts, which can trip client or reverse-proxy read timeouts.

* test(codex): pin bootstrap overload failover through the conductor

Executor-level tests cannot show what the client finally receives. These
exercise ExecuteStream end to end to pin three properties that are easy
to regress:

- consecutive overloaded credentials are skipped until one serves the
  request, and retries are capped by max-retry-credentials rather than
  multiplying with request-retry
- exhausting the pool surfaces the upstream status instead of a
  committed 200 stream
- with buffering disabled the rejection stays an in-stream error on a
  committed stream, which is the behaviour the feature must preserve

The third case also documents why the executor returns its error
synchronously: an error arriving as the first stream chunk is wrapped and
downgraded into a committed 200, silently losing the status.
2026-08-20 21:31:53 +08:00
Luis Pater
92d96e0b72 fix(cliproxy): support base_URL-only config credentials and skip stale auth headers
- Preserve API key entries with empty `api_key` when `base_url` is configured, and extend config dedupe/ID logic to include base URL, proxy, prefix, and headers so identities are stable.
- Update config/auth handling so `auth_kind=apikey` is treated as config API-key auth even without an `api_key` field, enabling base_url-only credential records.
- Ensure Gemini/Codex/XAI request path clears `Authorization`/provider auth headers when token is empty to avoid leaking unrelated auth state.
2026-08-20 15:19:07 +08:00
Luis Pater
4874971764 fix(cliproxy): add warn-level diagnostics for auth cooldown and upstream execution failures
Closes: #5085
2026-08-20 03:40:46 +08:00
Luis Pater
9dc51b1f87 feat(cliproxy): add OAuth request-scoped error rules support
- Add `oauth-request-scoped-errors` configuration with normalization, sanitization, and YAML management persistence/hot-reload hooks.
- Route request-scoped error classification to use per-provider rules only for OAuth auth entries.
- Add config diff reporting and management CRUD endpoints for `oauth-request-scoped-errors` (get/put/patch/delete) with input sanitization.

Closes: #5085
2026-08-20 02:55:49 +08:00
Luis Pater
ec105dac94 fix(cliproxy): handle responses/compact auth cooldowns and fallback semantics
- Add compact-specific error classification to mark transient/non-credential failures as availability-neutral instead of triggering cooldown penalties.
- Stop auth fallback immediately on compact request-fault errors (e.g., bad/not-found/unsupported request errors) and return the upstream compact error.
- Preserve existing cooldown behavior for auth/credential faults (`401`, `403`, `429`) while allowing non-auth compact failures to fail fast without tainting normal traffic routing.

Closes: #5031
2026-08-19 22:05:28 +08:00
DragonFSKY
45c90e8d0e fix(websocket): drop consumed compaction triggers
After remote compaction succeeds, the normalized previous request still contains the consumed compaction_trigger. The next WebSocket-to-HTTP merge replays that trigger before the compaction output and current input, causing the upstream 400 error.

Remove compaction_trigger only from previous request items when the previous response contains compaction or compaction_summary. Add a two-step regression test covering trigger normalization followed by compact replay.

Fixes #5041
2026-08-18 04:33:00 +08:00
hkfires
5bffd1514f Refactor cooling management across configuration and handlers
- Changed `DisableCooling` from a boolean to a pointer in various config types to allow explicit inheritance.
- Updated tests to reflect the new pointer usage for `DisableCooling`.
- Enhanced the `BuildConfigChangeDetails` function to handle optional boolean changes for `DisableCooling`.
- Added new tests to ensure proper handling of cooling overrides in configurations.
- Refactored the `SetQuotaCooldownDisabled` function and related logic to clarify the purpose of cooldown management.
- Introduced new tests for cooling override precedence in the auth manager.
- Ensured that all relevant handlers and synthesizers correctly manage the `DisableCooling` setting.
2026-08-17 10:54:07 +08:00
Luis Pater
b8fbe70b37 fix(auth): stop old selectors when replacing manager selector and harden cache stop concurrency
- Add `isSameSelector` using type-aware comparable checks to avoid unnecessary selector replacement.
- Update `Manager.SetSelector` to:
  - serialize swaps with a dedicated selector mutex,
  - no-op when replacing with the same selector instance/type,
  - stop the previous selector when it implements `StoppableSelector`.
- Protect `SessionCache.Stop()` with `sync.Once` and nil-check to make repeated/concurrent stops safe and idempotent.

Closes: #5018
2026-08-16 22:28:45 +08:00
Luis Pater
00c4377a21 fix(auth): share session affinity across model variant suffixes
- Canonicalize model IDs when building session-affinity cache/fallback keys so variant suffixes (for example thinking modes) map to the same binding.
- Normalize model values from result metadata/on-result release paths to release and rebind bindings consistently across canonical model keys.

Closes: #5016
2026-08-16 21:55:50 +08:00
Luis Pater
aa10847e12 feat(auth): add request-scoped error action handling in conductor
- Add request-scoped error rule extraction from auth metadata or runtime provider config (including OpenAI compatibility fallback)
- Match rules by HTTP/status-code plus error body substring or regex patterns
- Support `stop`, `stop-and-cooldown`, `continue`, `continue-and-cooldown` actions with normalized validation
- Apply matched actions to execution results via request-scoped vs force-cooldown error codes and stop/continue flow control
- Introduce request-stop error wrappers/helpers for matching and unwrapping scoped stop state

Closes: #5006
2026-08-16 21:39:38 +08:00
Luis Pater
92f03e68e3 fix(claude,gemini,openai): preserve upstream stream errors when no data payload is emitted
- Return buffered pending stream errors from `errChan` when the data channel closes in streaming handlers.
- Emit error response instead of silently falling back to normal SSE completion paths.
- Replace Claude’s local `pendingClaudeStreamError` usage with shared `handlers.PendingStreamError`.

Closes: #4710
2026-08-16 04:29:51 +08:00
Luis Pater
9169ad56e1 fix(auth): make session-affinity updates safe for rebound sessions
- Propagate session-affinity provider/model metadata through selection and execution results so affinity cleanup uses the same namespace/model keys as selection.
- Forward execution `Options` in `Result` across normal, home, and stream paths, then hook manager cooldown handling to notify the affinity selector.
- Add `Touch`/`CompareAndDelete` to session cache and switch affinity release logic to compare-and-delete semantics, preventing stale failures from deleting a session already rebound to another auth.

Closes: #4989
2026-08-15 16:37:49 +08:00
Luis Pater
e0b4956242 fix(openai): ensure Responses usage includes token detail fields
- add shared `EnsureResponsesUsageDetails` helper to patch `usage` objects with:
  - `output_tokens_details.reasoning_tokens = 0`
  - `input_tokens_details.cached_tokens = 0`
  - for both plain JSON and SSE `data:` frames, including multi-line frames
- apply the helper to OpenAI Response format outputs in non-stream and stream paths across executors/plugins so translated payloads consistently include required usage details
- update websocket/completion payload builders to emit default `usage` detail fields for prewarm/finish responses

Closes: #4985
2026-08-15 15:00:19 +08:00
Luis Pater
ac82bedfaf fix(claude): add Anthropic unified rate-limit parsing helpers
- Add new Claude rate-limit helper utilities to detect unified header-based quota rejections (5h/7d), handling case-insensitive headers and missing canonical forms.
- Compute deterministic retry duration from `Retry-After` and header reset timestamps, preferring the longest applicable unified window so cooldowns can be applied consistently.
- Introduce reusable rate-limit classification logic to distinguish header-authoritative rejections from request-scoped errors (e.g. fast-mode entitlement failures) for correct credential/model cooldown behavior.

Closes: #4874
2026-08-15 14:08:16 +08:00
Luis Pater
203f5b1a1d fix(auth): avoid cooldown for request-scoped 401 request-faults
- Treat request-invalid/invalid-parameter 401 faults as request-scoped so auth/model cooldown is skipped.
- Export request-scoped/connection-lifecycle error codes and add helpers to explicitly create or mark request-scoped `Error`s.
- Improve request-fault detection by checking the raw message body when `Error.Code` formatting hides JSON payloads.

Closes: #4976
2026-08-15 05:46:10 +08:00
Luis Pater
810d4dddb3 Merge pull request #4360 from router-for-me/perf/skip-inactive-request-interceptors 2026-08-15 05:12:18 +08:00
Luis Pater
8b02fedec2 Merge pull request #4928 from ramapitecusment/codex/websocket-transcript-allocations-v2
perf(openai): reduce websocket transcript merge allocations
2026-08-15 04:58:38 +08:00
Luis Pater
b90d8ee9eb fix(auth): preserve custom auth-file metadata during token refresh and relogin
- Added metadata merge helpers in `sdk/cliproxy/auth` to retain existing auth-file settings while excluding token lifecycle fields.
- Applied merge before saving in management handler and `sdk/auth.Manager.Login` so re-login overwrites credentials without discarding user-configured fields.
- Updated token save paths for Claude, Codex, Kimi, XAI, and Vertex to persist merged metadata payloads when writing auth files.

Closes: #4945
2026-08-15 00:07:27 +08:00
Luis Pater
db35b91e2a feat(openai): add xAI Grok Imagine Image 2.0 image model support
- Added `grok-imagine-image-2.0` as a first-class xAI image base model across validation, canonicalization, and routing checks.
- Registered the model in built-in model definitions so it appears in model metadata.
- Updated image model allowlists and request validation error messaging to include the new model.
- Marked the new model as hidden in client visibility override handling.
2026-08-13 14:37:29 +08:00
Luis Pater
6f2cea9484 feat(config): add per-credential request-retry override support
Closes: #4931
2026-08-13 01:50:47 +08:00
Luis Pater
75d2c4a4b4 fix(openai): avoid JSON copies in websocket responses tool-call repair path
Closes: #4925
2026-08-12 20:32:10 +08:00
Ramapitecus
baa11ed6dd fix(openai): match websocket item metadata case-insensitively 2026-08-12 16:52:16 +05:00
Ramapitecus
49b2f891ac fix(openai): preserve duplicate websocket input semantics 2026-08-12 16:43:56 +05:00
Ramapitecus
f8bcd1cc5f test(openai): harden websocket transcript allocation coverage 2026-08-12 16:23:35 +05:00
Ramapitecus
e7c3fb1983 test: make race detector suite deterministic 2026-08-12 16:17:14 +05:00
Ramapitecus
f9bd9def2b perf(openai): reduce websocket transcript merge allocations 2026-08-12 16:09:14 +05:00
Luis Pater
c845ce15c9 fix(openai): refactor websocket responses request merge/repair path
- Consolidate Responses websocket transcript merging by introducing typed input-item parsing for previous request input, previous response output, and appended input, replacing ad-hoc raw JSON-array string merges.
- Keep deduplication behavior in a single shared pipeline with metadata parsed once (type/id/call_id), then dedupe function-calls and item IDs before reserializing.
- Refactor fallback-turn tool-call repair into a unified prepare path that returns repaired payload + turn context, and only commits request state after successful forwarding to avoid stale per-turn history.

Closes: #4913
2026-08-12 17:06:27 +08:00
Luis Pater
522b4de54a fix(openai): handle premature SSE stream termination with terminal error emission
- Propagate pending terminal errors when image/response data streams close, emitting `error`/`failed` SSE events (or HTTP error responses when no stream started) instead of dropping them.
- Sanitize and normalize streamed terminal errors before writing, and reuse normalized errors for cancellation.
- Improve SSE parsing/frame handling for multiline and cross-chunk payloads, prioritize payload/event-level failures, and avoid mutating emitted frame buffers by cloning chunks before queueing.

Closes: #4904
2026-08-12 13:05:06 +08:00
sususu
5fa66293db fix(antigravity): preserve request plugin hook semantics 2026-08-11 16:07:42 +08:00
Luis Pater
ba5ab795a2 feat(plugin): add schema-v3 stream chunk contract to omit payload request bodies
- Bump plugin schema to version 3 and introduce `SchemaVersionStreamChunkOmitRequestBody`.
- Treat missing plugin schema versions as legacy during RPC registration (`0 -> 1`) and expose schema on plugin descriptors.
- In stream interception, keep request headers/bodies on header-init chunk and stop re-sending them on payload chunks for schema-v3+ plugins, with per-chunk cloning for legacy plugins.

Closes: #4876
2026-08-11 04:32:20 +08:00
sususu
9c8e4a07e6 fix(auth): prioritize rate-limit status over error body 2026-08-10 14:50:40 +08:00
sususu
d0d77182ee fix(auth): rotate DeepSeek authentication failures 2026-08-10 14:17:15 +08:00
sususu
45ffd115fd fix(auth): rotate keys after DeepSeek insufficient balance 2026-08-10 10:13:29 +08:00
Luis Pater
3522e481aa fix(openai): emit response.failed stream errors for Codex requests
Closes: #4854
2026-08-09 03:47:14 +08:00
sususu
9992920984 Avoid copying large payloads in Antigravity reads
gjson.GetBytes copies the matched subtree before returning it, so every read of
request.contents on a multi-megabyte request allocated another copy of it. The
reasoning replay, executor, signature validation and session identity paths all
read whole arrays this way, and hasExplicitSession parsed the entire body with
gjson.ParseBytes on every request.

Use the no-copy helpers for these reads. Each call site only reads from the
result and writes through sjson, which allocates a new buffer, so the payload
stays immutable while the results are alive.
2026-08-09 00:40:57 +08:00
Luis Pater
9829bd9d3e fix(cliproxy): stop non-streaming keep-alive after OpenAI handler execution
Closes: #4782
2026-08-08 06:32:09 +08:00
Luis Pater
01a21b77f4 fix(cliproxy): delegate OpenAI-compatible OAuth refresh to plugin auth providers
- Added a plugin refresh-compat executor wrapper that forwards normal OpenAI-compat execution paths while routing `Refresh` to plugin `AuthProvider`/Home refresh logic.
- Updated refresh lookup to use the effective executor key from auth metadata so namespaced compatibility providers can resolve their refresh executors correctly.
- Changed OpenAI-compat registration to wrap built-in executors with the plugin-refresh wrapper when a matching plugin auth provider exists, while preserving bare executors otherwise.
- Made `OpenAICompatExecutor.Refresh` fail fast for OAuth-style credentials (with refresh tokens) instead of silently returning unchanged auth.

Closes: #4719
2026-08-08 06:08:23 +08:00
Luis Pater
e64cdbf559 fix(codex): resolve credential-aware model before forwarding API-key alpha search requests
Closes: #4714
2026-08-08 05:36:20 +08:00
Luis Pater
4b3cc55cdc fix(cliproxy): centralize client error status mapping and apply context cancellation/deadline HTTP codes
Closes: #4601
2026-08-08 04:53:34 +08:00
Luis Pater
31a4e9b487 fix(cliproxy): preserve route model on built-in selector cooldown errors
- Restore missing model names on `modelCooldownError` for built-in selectors using the originally requested model.
- Apply model restoration in both standard and mixed-provider conductor selection error paths before returning cooldown failures.

Closes: #4831
2026-08-07 22:38:11 +08:00
Luis Pater
0a95fa62a1 feat(compat): preserve Claude thinking/tool-call content for is-compat OpenAI compatibility models
- Add `is-compat` support to OpenAI compatibility model config, capabilities, hashing, and example config.
- Propagate `IsCompat` through API-key model resolution and switch OpenAI-compat executor translation to compatibility-aware routing.
- Keep Claude assistant thinking content in compatibility mode while keeping default behavior unchanged when `is-compat` is disabled.

Closes: #4776
2026-08-07 06:26:33 +08:00
Luis Pater
8392b180ce fix(cliproxy): avoid credential cooldown for connection-lifecycle disconnect errors
- Added a dedicated `connection_lifecycle` error code and lifecycle detection for context cancellation, EOF variants, and websocket close conditions.
- Introduced shared gating (`shouldSkipCredentialCooldown`) so transport/session lifecycle failures do not mark auth/model as unavailable or set retry cooldown.
- Kept request-scoped/invalid failures as high-priority and preserved status-bearing HTTP errors behavior so auth cooldown still applies when appropriate.

Closes: #4819 #4787
2026-08-06 22:52:45 +08:00
Luis Pater
1674aaf43b fix(cliproxy): canonicalize thinking-suffix model states for cooldown and scheduler sharing
Closes: #4814
2026-08-06 21:00:32 +08:00
sususu
b148af80bd fix(api): escape the model name in the unroutable model error
Build the error body with sjson instead of formatting the requested model name into a JSON literal. The name is client supplied and is echoed back verbatim, so a quote in it produced a malformed body and a crafted name could append fields that overwrote the error code clients branch on, for example turning model_not_found into insufficient_quota.

Reported by the Codex reviewer on #4820.
2026-08-06 20:49:23 +08:00
sususu
579f5e30fb fix(auth): rotate credentials for unknown upstream failures
Stop treating an upstream 500 carrying "status":"UNKNOWN" as a request fault. It is an internal upstream failure, so the request now falls through to the next credential instead of failing immediately, and the resulting cooldown lands on the failing credential and model pair only, leaving sibling models on that credential selectable.

Move the store=false item-miss detection into the shared client-error package. The upstream sends that 404 as plain text rather than a JSON error body, so the structured identifiers could never match it and only the conductor recognized it. The proxy now reports it to the client, which is the only party able to rebuild the request without the stale item reference; a reconnect resends the full input and the conversation continues unchanged.
2026-08-06 20:49:23 +08:00
sususu
c1d69e7b47 fix(auth): avoid penalizing credentials for client faults 2026-08-06 20:49:23 +08:00