- Replaced `ExecuteWithAuthManager` with `ExecuteImageWithAuthManager`.
- Replaced `ExecuteStreamWithAuthManager` with `ExecuteImageStreamWithAuthManager` for improved clarity and specificity.
- Refactored websocket output collection to restore completed events using fallback items and indexed payloads.
- Updated `CodexExecutorCompact` tests to ensure no injection of `image_generation` tools and maintain compact input order.
- Added new tests to validate sustained reasoning, proper output restoration, and compaction replay behavior.
- Improved logic to handle incremental response appends and preserved reasoning continuations.
- Enhanced test coverage for normalization, tool calls, and downstream payload restoration scenarios.
Closes: #4176
- Replaced PKCE-based OAuth for xAI with device authorization flow for enhanced compatibility.
- Introduced cancelable OAuth sessions with server-side invalidation to prevent token persistence on flow interruption.
- Updated OAuth polling and session tracking to handle device flow specifics, such as `user_code` and extended timeouts.
- Refactored TUI OAuth model to track polling generations, distinguish device flow, and handle session cancellation gracefully.
- Extended test cases for device flow scenarios, stale session handling, and cancelation logic.
- Streamlined xAI device flow management in handler logic and improved error handling.
- Updated Codex client to validate and restrict input modalities to "text" and "image" only.
- Added deduplication logic for input modalities in `codex_client_models.go`.
- Introduced test cases to verify behavior with mixed and invalid modalities.
- Added `EnsureClaudeModelIDPrefix` and `ResolveClaudeModelIDPrefix` utilities for standardizing and decoding Claude model IDs.
- Updated handling of `claude-fable-5-dd-<reversed>` model IDs for request routing and response formatting.
- Incorporated logic to enforce disabled states for plugin virtual auths and their expanded children.
- Refactored code for applying and persisting disabled state metadata in plugin multi-auth scenarios.
- Implemented and extended unit tests to validate expected behavior.
- Added `tryRefreshAfterUnauthorized` to refresh OAuth credentials on 401 errors during requests.
- Implemented `refreshLocks` to prevent concurrent refreshes for the same auth ID.
- Updated auth/state handling to reset unauthorized model states and resume operations after a successful refresh.
- Enhanced refresh logic with error handling, synchronization, and state updates.
Closes: #4087
- Added handling of input/output modalities metadata for Codex and compatibility clients.
- Updated model registry and configurations to support "text" and "image" modalities.
- Introduced new tests to validate input/output modalities registration and processing.
Closes: #3976
- Added fallback and suspension handling for `invalid_grant` errors in auth conductor.
- Introduced helper methods to detect `invalid_grant` error patterns.
- Updated tests to validate `invalid_grant` fallback and suspension scenarios in execution and stream flows.
Closes: #4120
- Introduced API handlers and executor logic for Google Interactions
- Added request and response transformations for OpenAI and Claude Interactions.
- Integrated Gemini API with Interactions support.
- Updated tests to validate Interactions request parsing and error handling.
- Refactored translator logic for Interactions data flows.
shouldRetryAfterError approves a retry using the pre-jitter wait, so the jitter added in waitForCooldown could push the actual sleep up to 2s past the configured max-retry-interval ceiling. Clamp the jitter range to the remaining headroom below maxWait so the documented maximum stays a hard bound; waits at the ceiling simply sleep unjittered.
Previously every 429 incremented the backoff level unconditionally, so N concurrent in-flight failures from a single quota event inflated the level by N at once (12+ failures jump straight to the 30 minute plateau even when the upstream limit clears in seconds). Failures that land while a previous quota window is still open now reuse that window instead of escalating, so the ladder advances at most once per window. Provider supplied retry hints still take effect unchanged.
waitForCooldown also slept until the exact recovery deadline, waking every waiting request in lockstep against the single recovered credential and re-escalating it in one burst. Cooldown waits now carry a small random jitter (up to wait/4, capped at 2s) to spread the wakeups.
For downstream websocket with CPA-mediated HTTP/SSE upstream (non-passthrough),
always merge the full conversation transcript instead of sending incremental
previous_response_id turns. Release pinned websocket auths after timeout and
gateway failures, and after WS bootstrap failover pin the successful SSE
credential so the next turn keeps merged context instead of retrying a
suspended websocket auth.
Fixes#4048
Force-mapping rewrites streaming OpenAI Responses SSE through StreamRewriter before the /v1/responses websocket forwarder. Antigravity/Gemini and Codex emit frames without reliable trailing newlines, so buffered chunks glued as ...}event:..., ...}data:..., or event:/data: without separators. The rewriter dropped pending tails and downstream WS synthesized 408 without response.completed even when upstream returned HTTP 200.
- safeReplaceGlued for }event: and }data: when data JSON is complete\n- Finish flush with glue normalization and line-wise fallback\n- Newline between pending event: and next data: line (Codex scanner lines)\n- Regression tests: Antigravity sim, Codex data lines, force-map WS forward
- Added Manifest struct to encapsulate plugin metadata and installation details.
- Implemented ManifestFromRelease and ManifestFromPlugin functions for creating manifests from releases and plugins.
- Enhanced Plugin struct to include Versions and InstallPlan for direct installations.
- Introduced validation for direct install type, ensuring artifacts are correctly specified.
- Updated registry validation to support new schema version and direct install requirements.
- Added tests for parsing and validating direct install plugins, ensuring correct artifact handling.
* fix(oauth): force-map responses to config alias not request suffix
Rewrite client-visible model fields using oauth-model-alias Alias from config
(e.g. gpt-5.4-fast) instead of mirroring the requested model string when the
client adds thinking suffixes (e.g. gpt-5.4-fast(high)). Upstream routing
still preserves suffixes on the resolved upstream model.
* test(oauth): gate response rewrite on ForceMapping and document OriginalAlias
- Document that OriginalAlias is only applied when ForceMapping is true
- Set OriginalAlias to config alias only when force-mapping is enabled
- Add tests: no non-stream/stream payload rewrite when ForceMapping is false
* test(oauth): assert disabled force mapping response
- Introduced `model` field in `videoAuthBinding` for model tracking.
- Enhanced binding store with `setWithModel` and `getBinding` methods for binding models.
- Updated handlers to propagate execution models during video processing.
- Added tests to verify model binding, retrieval, and propagation logic.
Add OAuthModelAlias.ForceMapping to rewrite client-visible model fields in non-stream and SSE stream responses while leaving request-log upstream payloads unchanged.
- Wire config, watcher diff, and conductor execute/stream paths
- Rewrite force-mapped responses in Antigravity credits fallback paths
- Prevent buffered SSE chunks from being emitted by line-wise fallback
- Preserve thinking suffixes for same-base force-mapping aliases
- Cover codex, antigravity, kimi, and xai with live-derived fixtures and regression tests
- Document a conservative antigravity-only force-mapping example in config.example.yaml
- Introduced `AuthKind` and `AuthSourceKind` methods for improved credential type and source classification.
- Replaced legacy fallback logic with normalized attribute-based handling in `AuthKind`.
- Consolidated metadata inspection for both API key and OAuth attributes.
- Updated calls to replace direct `AccountInfo` usage with `AuthKind` and `AuthSourceKind`.
- Enhanced unit tests to validate explicit and fallback credential resolution scenarios.
- Added NodeID field to HomeConfig for better identification.
- Updated ConfigFromJWT to populate NodeID from claims.
- Introduced new Redis keys for managing plugin status and tasks.
- Implemented RPushPluginStatus and GetPluginTasks methods in the client for handling plugin-related data.
- Enhanced Sync functionality to include detailed reporting on plugin installation and deletion tasks.
- Added error handling and reporting for plugin synchronization failures.
- Created tests for new plugin synchronization and management features.
- Improved the overall structure and readability of the plugin synchronization logic.
- Added `RefreshAPIKeyModelAlias` for explicit alias table rebuilds.
- Introduced deferred rebuild support with `WithDeferredAPIKeyModelAliasRebuild` and context flag validation.
- Implemented `openAICompatibilityRegistrationCache` to streamline OpenAI compatibility model registrations.
- Updated executor and model registration workflows to utilize cached compatibility data, improving efficiency in batch operations.
- Adjusted max worker limits dynamically based on model categories.
Closes: #3953
- Added `rpcPluginError` to encapsulate plugin errors with HTTP status codes.
- Enhanced `decodeEnvelopeResult` to preserve and return detailed plugin errors with status codes.
- Introduced `isPluginErrorEnvelope` to identify plugin error envelopes.
- Updated plugin call logic in Unix and Windows loaders to differentiate plugin errors from system errors.
- Added unit tests to verify error handling and status code preservation.
- Added logic to expand single auth JSON payloads into multiple plugin virtual auth records.
- Updated related API endpoints such as `PatchAuthFileStatus` and `DeleteAuthFile` to handle plugin virtual auths with rollback mechanisms.
- Introduced `NormalizePluginOAuthCallbackProvider` and other normalization functions for better handling of OAuth callbacks.
- Enhanced tests to validate multi-auth parsing, rollback behavior, and API response consistency.
- Added `applyCodexClientNonTemplatePriorities` to assign higher priorities to non-template Codex client models dynamically.
- Implemented `maxCodexClientTemplatePriority` to set base priority for non-template models relative to template models.
- Updated unit tests to validate priority calculation for custom models.
- Introduced a new "max" level for reasoning depth in Codex client model configuration, providing maximum problem-solving capability.
- Added `service_tiers` field to model responses for better tier categorization.
- Updated unit tests to validate the inclusion and default behavior of `service_tiers` and the new "max" reasoning depth.
- Introduced a new `/reset-quota` API endpoint in the management handler to clear quota and cooldown state for auth records.
- Implemented `ResetQuota` method in the auth manager to handle runtime and registry state resets for affected models.
- Added tests to validate quota reset behavior, including proper state updates and registry consistency.
- Refactored utility functions to support deduplication and registered models handling in quota resets.
Closes: #3866
- Introduced `SetOAuthModelAliasesAttribute` and `OAuthModelAliasesFromAttributes` for managing per-auth model aliases.
- Enhanced OAuth model resolution logic to prioritize per-auth aliases over global aliases.
- Updated metadata handling to extract and sanitize per-account model aliases.
- Added tests to validate alias precedence, empty attributes, and conflict scenarios.
Closes: #3764
- Replaced direct `strings.ToLower` usage with `util.OpenAICompatibleProviderKey` for generating provider keys.
- Updated auth and executor workflows to use namespaced keys for OpenAI-compatible providers.
- Adjusted tests to validate namespaced key handling, including new test cases for provider registration and execution logic.
- Added `OpenAICompatibleProviderKey` helper in `util` for consistent key transformations.
Closes: #3600
- Introduced `CooldownStateStore` interface for managing independent cooldown state persistence.
- Implemented `FileCooldownStateStore` for storing cooldown states as per-auth `.cds` files with atomic writes and stale file cleanup.
- Enhanced `Manager` to support restoring state from `CooldownStateStore` and persisting state changes during auth updates.
- Updated tests to validate cooldown state saving, loading, concurrency handling, and error scenarios.
Closes: #3368
- Introduced `SetTransientErrorCooldownSeconds` to enable configurable cooldowns for transient errors (e.g., 408/500/502/503/504).
- Updated retry scheduling logic to use the new `nextTransientErrorRetryAfter` function.
- Modified config parsing to include `transient-error-cooldown-seconds` with support for disabling or defaulting to legacy behavior.
- Expanded tests to validate transient cooldown logic with various configurations and edge cases.
Closes: #3315
- Refactored `ConfigReloadHook` to use `reloadConfigFromWatcher` for consistency.
- Added async `reloadConfigAfterManagementSaveAsync` to handle post-save operations.
- Introduced `ReloadConfigIfChanged` in watcher for manual trigger support.
- Enhanced config reload paths to separate auth synthesis from standard updates.
- Updated `applyConfigUpdate` logic to allow more granular reload behaviors.
Closes: #3235
- Updated error handling in `RPopAuth` to distinguish `auth_not_found` from transport errors.
- Added a new test, `TestPickNextViaHomeClassifiesTransportErrorsAsHomeUnavailable`, to validate correct error classification and retryable property.
- Introduced the `gpt-image-2` model in Codex built-ins and updated visibility logic in the registry.
- Added direct proxy support for OpenAI image generation and editing endpoints.
- Implemented new execution paths for `/images/generations` and `/images/edit`, ensuring seamless handling for both JSON and multipart payloads.
- Expanded test coverage to validate the new model and direct proxy features, including streaming scenarios and error handling.
- Deleted `geminicli` provider and related `Apply` logic.
- Removed all translator packages specific to Gemini CLI (Claude, Codex integrations).
- Purged associated test files for Gemini CLI translation.
- Removed `GeminiAuthenticator` and all associated authentication logic (OAuth flows, token handling, refresh logic).
- Deleted internal/executor Gemini OAuth support, including bearer token handling and runtime API logic.
- Purged all tests, configs, and command-line flags specific to Gemini OAuth flows.
- Updated documentation and aliases to reflect Gemini removal.
- Renamed `parseRetryDelay` to `ParseRetryDelay` and `deleteJSONField` to `DeleteJSONField`.
- Updated references in `antigravity_executor` and tests to use the new `helps` package.
- Adjusted import paths and test cases to ensure compatibility with the new location.
- Updated README files to reflect changes in the retry logic references.
- Updated `.github/ISSUE_TEMPLATE/bug_report.md` to remove deprecated Gemini CLI mention.
- Added auth binding logic to tie video requests to specific authentication IDs.
- Enhanced video content handlers to support proxy configuration based on selected auth.
- Introduced helper functions for creating HTTP clients with direct or global proxy fallback.
- Expanded unit tests to validate auth binding, proxy usage, and fallback behavior.
- Updated `openai_videos_handlers` to extract and set `video_url` from payloads when available.
- Enhanced unit tests to validate correct `video_url` extraction and inclusion in responses.
* feat(plugin): add ModelRouter before auth with single-slot routing targets
## Motivation
Plugins that need to change execution based on the **original inbound request**
(protocol format, raw body, headers, query, stream flag, metadata, etc.) often
resorted to virtual/trampoline models or routing inside interceptors. This
commit adds **ModelRouter**: a pluggable layer **before** model-to-provider
resolution and AuthManager credential selection, so plugins can declare who
executes a request without spoofing the client model name.
This is a **new capability**, not a bugfix on the existing chain. With no
ModelRouter plugins loaded, behavior matches upstream.
## Pipeline placement
- `execute`, `stream`, and `count` (and image paths via AuthManager) call
`applyModelRouter()` before building `coreexecutor.Request`.
- Routing runs **before** the request interceptor (before auth), so routers see
the client’s original context. After a plugin executor is chosen, the existing
**after-auth interceptor → response/stream interceptor** chain still applies.
- Internal `ExecuteModel` / `ExecuteModelStream` (host callbacks) support
`SkipRouterPluginID` so nested calls do not re-enter the same router.
## Routing API (single slot, mutually exclusive)
`ModelRouteResponse` uses **one target slot** to avoid ambiguity when both
`TargetExecutorPluginID` and `TargetProvider` were set and the host ignored one:
| Field | Meaning |
|-------|---------|
| `Handled` | `false`: this router declines; try the next router or default path |
| `TargetKind` | `self` \| `executor` \| `provider` (pick one) |
| `Target` | `self`/`executor`: plugin ID; `provider`: built-in provider key |
| `TargetModel` | Optional on `provider` only; empty keeps client `RequestedModel` |
| `Reason` | Optional diagnostic text |
- **self**: the router plugin’s own executor (`Target` normalized to the router’s plugin ID).
- **executor**: another plugin’s executor; host pre-checks with `executorPluginReady()`
(executor declared and provider identifier resolvable) to avoid handled routes that 500 at execution.
- **provider**: skip registry model resolution; fixed built-in AuthManager path; optional
`TargetModel` for execution model only—**does not** change outward requested-model metadata.
Routers run in **descending plugin priority** (tie-break: ascending plugin ID). Panic, error,
invalid target, or unavailable executor/provider → log and **fall through to the next router**;
if none handle, use the original provider+auth flow.
## Context exposed to routers
`ModelRouteRequest` includes:
- `SourceFormat`, `RequestedModel`, `Stream`
- `Headers`, `Query`, `Body` (defensive copies)
- `Metadata` (best-effort read-only context snapshot)
- `AvailableProviders`: built-in provider keys with at least one **non-disabled** auth
(`AuthManager.AvailableProviders()`). **Does not** reflect per-model cooldown or transient
unavailability—treat as an optimistic snapshot.
Adds `AuthManager.HasProviderAuth()` and `AvailableProviders()`, excluding `Disabled` and
`StatusDisabled` auths consistently with credential selection.
## Host and RPC
- Go plugins: `pluginapi.ModelRouter` + `RouteModel()`.
- RPC plugins: `pluginabi.MethodModelRoute` (`model.route`), capability flag `model_router`.
- `pluginhost.Host` implements `RouteModel` / `RouteModelExcept`; handlers use
`SetModelRouterHost` or a `PluginHost` type assertion; **direct executor** paths use
`ExecutePluginExecutor*` / `CountPluginExecutor`.
- No bundled example ModelRouter plugin; capability is active only when a third-party plugin
declares `model_router` and loads.
## Plugin RPC schema (policy A, upstream-aligned)
- `pluginabi.SchemaVersion` stays **1**: capability additions (`model_router`, `model.route`)
do not bump the number; increment only on breaking RPC JSON changes.
- Host sends `schema_version` at register; reject only if the plugin declares a **higher**
version than the host.
- No unpublished “ModelRouter requires schema ≥ 3” gate (v3 single-slot API was never public).
- Existing plugins and examples without `model_router` (`schema_version: 1`) need no changes.
- RPC ModelRouter: `schema_version: 1` + `model_router: true` + implement `model.route`.
## Path consistency within this commit
- Provider routes reuse image-only model checks (e.g. `gpt-image-2`) on the normalized model,
same as the default AuthManager path.
- `count` aligned with execute/stream: `SkipRouterPluginID`, query/headers injection,
interceptor skip semantics.
- Handlers: `modelRoutersEnabled` treats hosts without `HasModelRouters` as disabled
(same as before ModelRouter existed); `pluginhost.Host` implements the detector.
- API docs: `ModelRouter` explicitly includes built-in **provider** targets (in addition to
plugin executors and the router’s own executor).
## Testing
go test ./internal/pluginhost ./sdk/api/handlers ./sdk/pluginapi ./sdk/pluginabi ./sdk/cliproxy/auth
go build -o test-output ./cmd/server && rm test-output
go test ./...
* fix(handlers): address ModelRouter review feedback
- Use modelExecutionQuery for plugin executor and AuthManager paths so
inbound URL query matches router/header behavior
- Guard queryFromContext when gin Request.URL is nil
- Read plugin executor stream chunks via nextStreamChunk to exit on cancel
- Drop redundant clonePluginMetadata on capability record meta
Tests cover query propagation, stream cancel, and nil URL safety.
* feat(plugin): add Claude web search router example
Add a Claude Code web_search ModelRouter example that can route matching Claude requests through Antigravity, Codex, xAI, or Tavily.
The plugin includes executor orchestration, backend fallback/penalty handling, Tavily API key support, Claude-compatible response assembly, stream forwarding, and focused unit coverage for detection, fallback routing, model resolution, penalties, stream forwarding, and Tavily behavior.
Verification: go test -count=1 ./... in examples/plugin/claude-web-search-router/go; go build -buildmode=c-shared for the plugin; go build ./cmd/server; live local CPA curl coverage for plugin load, four explicit routes, fallback, and Codex spark routing.
* fix(pluginhost): validate executor routes before fallback
* fix(pluginhost): skip oauth-only executor routes