Repository navigation
Merge latest upstream while preserving Pinable runtime contracts - #8
Merged
Merged
Conversation
… enforce bound principal - Guard gin.Context identity writes in authenticateResponsesWebsocketTurn behind updateRequestContext so executor-side duplex turn checks do not mutate a shared or recycled gin.Context. - Return the authenticated principal and reject duplex turns in WithWebsocketTurnAuthorizer with 401 when the re-authenticated principal differs from the stream's bound principal. Closes: router-for-me#6949
- Retain file snapshot bindings on cached and queued copies in dispatchPersistedAuthUpdateWithRevision so delayed login snapshots stay fenced against concurrent disk updates and match discarded-snapshot rereads. - Track persisted auth update revisions in the watcher so full scans still reread file-backed auths persisted during an in-flight scan even when their snapshot binding is retained. - Add regression tests for delayed login hooks racing operator disables, subsequent config reloads on incomplete files, and bound login snapshots persisted during a scan. Closes: router-for-me#6951
…ilures - Clear carried invalid_grant and non-cooldown unavailable states when updated credentials replace expired or failed tokens while preserving active retry and quota cooldowns. - Reset refresh failure counters and stale refresh backoff deadlines when credentials change across file snapshots, model states, and SIWC bindings. Closes: router-for-me#6948
…llOf resolution
- Defer missing array items placeholder synthesis and array type inference from Phase 0 schema normalization to Phase 3 `sanitizeArrayItems` so synthetic `{"type":"string"}` placeholders do not overwrite real `items` supplied via `allOf` branches or `$ref` targets.
- Add regression test coverage across all Gemini and Antigravity tool schema cleaners for array `items` defined directly in `allOf`, through `$ref`, or across split `allOf` branches.
Closes: router-for-me#6950
sjson.DeleteBytes copies the whole document even when the path is missing: appendRawPaths builds a prefix copy that is discarded, then set() copies the input again before SetBytes returns the original slice. The summary config and the Gemini/Antigravity level and budget appliers defensively delete several alias spellings that are almost always absent, so each request paid several full-body copies. Add util.DeleteJSONBytesIfExists, which checks the path with GetGJSONBytesNoCopy first, and use it at those call sites. Output is unchanged. On a 1 MiB Antigravity body, applySummaryConfigForProvider drops from 7.4 MB to 1.1 MB allocated per call. In a production heap profile these deletes were 35 GB of 192 GB allocated over 72 minutes.
… aliases - Expose the opaque caller scope handle on request interception structs and accept it on HostModelExecutionRequest and internal model execution requests so replayed executions retain caller-scoped metadata without exposing client API keys. - Resolve Claude MCP tool alias secrets from caller_scope metadata before falling back to hashed context API keys so plugin replays reproduce the original upstream tool names and derived session identities. Closes: router-for-me#6961
- Deduplicate normalized xAI tool declarations and promoted additional_tools entries by tool name with first-wins precedence so top-level tools remain authoritative over historical copies. - Deduplicate flattened tool counts and merge repeated namespaces during folding so overlapping historical tools do not prematurely trigger namespace folding or emit duplicate dispatchers. - Align namespace reference collection and dispatcher child unwrapping with the winning declaration to preserve response tool call restoration. Closes: router-for-me#6962
Track when a home config subscription is published and measure its healthy lifetime before teardown. Use `homeSubscriberRetryBackoff` with random jitter to delay reconnect attempts after unplanned disconnects while still reconnecting immediately on planned rebalances.
The Codex backend now sends a response.metadata event with prompt-moderation flags. It is not a public Responses event, and clients that parse a closed event list (Grok Build 1.0.60 and 1.0.70: "unknown variant response.metadata") fail the whole turn on it. Filter it for standard Responses clients like the other private Codex events; official Codex clients still receive it.
fix(cliproxy): back off home subscriber retries
…streams - Set candidates.0.finishReason to STOP on normal response.completed terminal events in Codex to Gemini streaming translation. - Continue to the finish_reason handler when an OpenAI streaming delta carries content or reasoning alongside a non-empty finish_reason. - Add regression tests for Codex response.completed and OpenAI combined content/reasoning plus finish_reason streaming chunks. Closes: router-for-me#6971
- Aggregate -{level}-lightning compound suffixes into a single swe-2-lightning catalog entry and preserve the Lightning display name suffix across registry and fetch_devin_models.
- Resolve -lightning base models into stem-{effort}-lightning UIDs while passing through complete upstream lightning UIDs and regenerating the embedded Devin catalog.
Closes: router-for-me#6975
…ta channel closes first - Drain any pending buffered error from errs via handlers.PendingStreamError before treating a closed data channel as a duplex socket close or incomplete stream timeout. - Add regression test covering both standard and duplex websocket forwarding when errs holds a buffered error and both channels are closed. Closes: router-for-me#6981
…nceled context - Check whether the request context is already canceled when the stream chunk channel closes in nextStreamChunk so select randomization does not report a canceled stream as EOF. - Add unit tests verifying that a closed stream channel with a canceled context deterministically reports cancellation while a live context reports normal stream completion. Closes: router-for-me#6979
- Number test server connections before upgrading in TestWebsocketRetryBindFailureClearsActiveSessionState so connection order matches sequential client dials. - Wait for the client request payload to arrive before sending the completion frame and closing the server socket in xAI keepalive upload tests, and guard hook channel closes with sync.Once. Closes: router-for-me#6983
- Split SSE-formatted wsrelay stream chunks at blank lines into individual data frames while preserving non-SSE JSON payloads whole. - Run response model observation, usage extraction, metadata filtering, and stream translation per frame so trailing finish reasons and token usage are not dropped. - Add regression tests covering multi-frame relay chunks across OpenAI Chat, OpenAI Responses, and Claude formats. Closes: router-for-me#6977
- Route the initial stream bootstrap read in readInitialStreamChunks through nextStreamChunk so a channel closed alongside context cancellation is reported as canceled rather than normal stream EOF. - Add a regression test exercising ExecuteModelStream cancellation before the first deliverable chunk to verify the request lifecycle completes as canceled. Closes: router-for-me#6987
…ick and UsageRecord - generate a unique selection_id for each handled Scheduler.Pick decision and freeze it into the execution context and UsageReporter - propagate SelectionID through core usage records and pluginapi.UsageRecord while clearing stale IDs on unhandled, rejected, Home, and credits fallback paths Closes: router-for-me#6988
…ream traffic - Compress finalized Codex OAuth request bodies with zstd on the verified ChatGPT backend path, with size/benefit gating, header-body alignment, and one uncompressed retry on 400/415 rejection. - Negotiate downstream HTTP response compression under server.response-compression (zstd/gzip with q-values, SSE-safe flushing, commit-time Vary merging) and enable negotiated permessage-deflate on Codex and downstream WebSockets. - Advertise Accept-Encoding to Codex upstreams and decode gzip/deflate/br/zstd upstream responses through a shared decoder before parsing or relaying. Closes: router-for-me#6905
hasClientSupportingWebSearchLocked scanned every client to answer "does any client advertise web search for this model". registerClientLocked calls it once or twice per registered model while holding the registry write lock, so registering N clients with M models each is O(N^2 * M) map iterations under an exclusive lock. Large installations (thousands of clients) spend minutes in this convoy, and because Service.Run blocks in model registration it never reaches its shutdown select, so SIGTERM is not consumed and systemd escalates to SIGKILL after TimeoutStopSec. Replace the scan with two incremental counters maintained on every mutation: webSearchClientCounts[modelID] -> number of clients webSearchProviderCounts[modelID][provider] -> number of clients per provider Both are updated in registerClientLocked (pure-add and reconcile paths), unregisterClientInternal, ApplyClientModelCapabilities and the RemoveClientModels paths, and the excludeClientID form subtracts the excluded client instead of skipping it. Benchmarked with whole-registry registration (20 models per client, one model advertising web search): clients before after speedup 250 68.2 ms 6.8 ms 10x 500 298 ms 15.6 ms 19x 1000 1492 ms 28.7 ms 52x The pre-fix curve grows 4.4x per doubling (quadratic); the indexed curve grows 1.8x (near linear). Tests compare the index against a full-scan oracle across the model x provider x exclude-client cross product after every mutation kind, and also assert the published Info/InfoByProvider flags agree with the scan, so a self-consistent but stale index still fails.
…enAI chat usage - Record output_tokens_details.thinking_tokens in claudeUsageTokens and emit it as usage.completion_tokens_details.reasoning_tokens in the stream message_delta, trailing usage chunk, and non-stream response. - Emit the field only when upstream reports it, preserving an explicitly reported 0 while leaving responses without thinking usage unchanged. Closes: router-for-me#6991
…gnature marker - Stamp Codex commentary thinking blocks with the CLIProxyAPI-internal marker `cpa:commentary:v1` in streaming and non-streaming Claude responses, keeping real reasoning signatures isolated. - Restore marked blocks on replay before any provider signature handling: as an assistant `phase: "commentary"` message for Codex targets and as plain assistant text for Claude-compatible, OpenAI, Gemini, Interactions, and Antigravity targets, so the marker never reaches an upstream. - Add the unconditional marker restore at the Claude upstream sanitize entry so non-Claude model names cannot bypass it, with round-trip and cross-format regression tests. Closes: router-for-me#6994
- Replace the wildcard Access-Control-Allow-Headers value with an explicit list covering Authorization, Content-Type, and the API key and Anthropic headers the API accepts, since Fetch treats Authorization as a non-wildcard request-header name. - Echo requested headers on preflight responses so arbitrary custom client headers keep working without widening allowed origins or enabling credentials. - Add real-server preflight regression tests for the models, chat completions, alternative credential, and Anthropic header cases. Closes: router-for-me#6999
…a v7 opt-out - Bump plugin SchemaVersion to 7 and let request interceptors declare per-phase body opt-out via request_interceptor_omit_body_before/after capabilities, so metadata-only plugins no longer pay for body cloning, Base64 encoding, and cross-ABI copies. - Skip the body clone in interceptRequest for schema v7+ plugins that opt out of the current phase while preserving chain state, blind body replacement, and termination semantics. Closes: router-for-me#6998
… Antigravity and Gemini streams - Cache the latest valid usage frame in both Interactions translators and emit interaction.completed with the done sentinel once usage at or after the finish frame arrives, keeping earlier cached usage only as the explicit-[DONE] fallback so split finish/usage frames keep the final token counts. - Recognize a bare [DONE] payload in the Antigravity stream parser (previously misparsed as a JSON array) and restore filter-renamed cpaUsageMetadata in the Gemini translator so executor-filtered frames still complete the stream. - Document that a clean Antigravity EOF terminates Interactions streams even without finishReason, matching real tool-call streams, while read errors still surface without a synthetic completion. Closes: router-for-me#7003
- Advertise the nine draft TXT keys (txtvers, path, tls, auth, protocols, auth_type, product, id, role) with 0/1 booleans and priority packing within the 255/400-byte limits; drop api_*, features and management. - Derive the protocols attribute from advertised subtypes and add the extended subtype constants. - Parse TXT with first-key-wins semantics, strict mount path validation, and legacy CPA schema fallback; build protocol endpoints from path. - Show path, TLS and per-protocol URLs in the discover CLI.
Management endpoints are never advertised under the draft schema. Keep the field so existing configs still load, and document the TXT keys and extended subtypes in config.example.yaml.
- Detect compaction_trigger in DevinExecutor Execute/ExecuteStream, generate the summary through the same Devin model, and synthesize a sealed single-item compaction response and SSE stream via the shared helpers. - Expand replayed CPA compaction capsules into summary context before translation and drop foreign compaction items, so repeated compaction keeps prior context. - Add a native Devin regression test covering streaming/non-streaming triggers, summary history and tool preservation, payload-rule final barrier, capsule replay, repeated compaction, and OriginalRequest-only triggers. Closes: router-for-me#7291
…sted-image-request-scope fix(codex): reject unsupported hosted image requests
- Preserve refresh failure stages and prevent immediate or later grant replay. - Coordinate durable claims and recovery receipts across file, PostgreSQL, Git, and S3 stores. - Add regression coverage for retries, concurrency, persistence, and recovery. Closes: router-for-me#7289
Report the OAuth login entry a credential was signed in through, resolved from Attributes then Metadata via oauth_provider and portal and normalized the way sign-in and re-login already resolve it. A console can then group credentials of a multi-entry plugin by entry instead of by provider. The field is v8-only, so the deprecated /v0/management/auth-files response keeps its frozen shape, and it is omitted when a credential carries no entry, leaving built-in providers, single-entry plugins and files written before router-for-me#7126 unchanged. type and provider stay as they are because routing and quota lookup key on them. Fixes router-for-me#7307
- Raise positive integer max_output_tokens below 16 to the Codex minimum on the translated payload across HTTP and WebSocket paths, before user payload rules. - Send inference verification with a 16-token budget so healthy Codex OAuth credentials are no longer rejected by the upstream minimum. - Add regression coverage for VerifyInference and small budgets from every Codex source format. Closes: router-for-me#7304
…nd trips - Flatten nested custom tool definitions and grammar formats into Responses API declarations while resolving nested names for tool choice, shortening, reverse mapping, and conversation history. - Normalize nested custom tool declarations when collecting response metadata so custom apply_patch calls emit the JSON input wrapper across streaming and non-streaming responses. Closes: router-for-me#7300
…via ToolSearch - Withhold defer_loading tools from top-level Codex tools[] and emit discovered definitions as additional_tools input items at the tool_result or tool_reference discovery point. - Deduplicate repeated tool_reference declarations across turns and prioritize non-deferred tools in short-name mapping so newly inserted deferred tools never alter existing top-level tool names. - Add regression tests covering ToolSearch discovery cache-prefix stability, repeated references, colliding >64-char MCP tool names, mixed tool_result content, and standalone reference ordering. Closes: router-for-me#7303
…oauth-provider feat(management): expose oauth_provider in credential list
…etadata preservation
Use a Claude-specific 256-bit base64url state for CLI and management logins. Include the native Console scope only during authorization while preserving existing token refresh scopes. Cover callback state preservation and case-sensitive session checks. Refs router-for-me#7316.
…auth-authorization-parameters fix(claude): restore OAuth login compatibility
…erve prompt cache - Emit mid-session text-only system and developer message items as role=system turns immediately after the next user message while preserving source order for top-level fallbacks. - Pin system item placement in the Claude executor before translation so legacy models, strict or uncloaked wire policies, mid-system rebuilds, and local token counts retain the top-level path. - Centralize the legacy system-reminder model allowlist in internal/util for shared lookup across the translator and executor. Closes: router-for-me#7310
… and antigravity chunks - Treat empty-text thoughtSignature carrier parts as non-content payloads in Gemini and Antigravity OpenAI Chat Completions response translators so trailing signature parts do not overwrite earlier deltas. - Accumulate reasoning_content and content across all parts within a candidate chunk using strings.Builder before writing the delta fields. - Add regression tests covering trailing empty-text signature parts and multi-part thought and text concatenation. Closes: router-for-me#7315
Preserve unknown refresh recovery state without inventing an access-token rejection. Evaluate availability for the requested partition and block missing tokens before model-level selection in both auth guards. Cover partial refresh and later inference through real OAuth HTTP responses, CodexExecutor, and Manager execution. Auth, executor, integration, race, and build checks pass. The full suite encounters an existing WebSocket temporary-log cleanup race, reproduced on clean dev.
…fresh-uncertain-outcome fix(auth): keep native Codex available during SIWC recovery
… calls are unanswered - Treat subsequent responses websocket requests without previous_response_id that leave pending tool calls unanswered as transcript replacements regardless of incremental upstream mode. - Add regression tests covering both unanswered and answered pending tool calls when incremental previous_response_id injection is disabled. Closes: router-for-me#7320
…restarts - Reuse existing content-addressed shadow binaries in `<plugin_dir>/.shadow` (with a per-directory shared cache fallback) and verify target digests before creating any temporary files. - Publish shadow binaries without unlinking active targets and coordinate plugin loading with stale shadow cleanup via OS-level file locks. - Add regression tests covering cross-restart shadow reuse, zero-write cache hits, fallback isolation, and concurrent load/prune coordination. Closes: router-for-me#7323
…ing blocks - Ignore whitespace-only reasoning deltas and non-streaming reasoning entries when no thinking block is currently active so Anthropic-incompatible blank thinking blocks are never emitted. - Allow whitespace reasoning segments to extend already-open streaming thinking blocks, tool-call-buffered thinking chunks, and non-streaming thinking builders. - Add streaming and non-streaming regression tests covering whitespace-only and intra-thought whitespace reasoning. Closes: router-for-me#7325
… upstream connections - Add NewDetachableContext in executor helpers so upstream requests follow downstream cancellation until message_stop and detach afterward before the handler cancels the request context. - Close the output stream channel first and then drain up to 64 KiB of the remaining raw upstream response body to EOF so net/http returns completed streaming connections to the idle pool. - Treat valid message_stop data lines in the translated streaming path as completion before translation so terminal-event cancellation does not abort connection pooling. Closes: router-for-me#7326
…penAI requests - Map explicit output_config.effort to reasoning_effort in ConvertClaudeRequestToOpenAI when the thinking object is omitted. - Preserve existing budget_tokens, disabled thinking, model suffix, and final payload rule precedence across translation and thinking stages. - Add regression tests for standalone effort translation, chained thinking normalization, and finalized OpenAICompatExecutor payloads. Closes: router-for-me#7327
…treaming - Track SSE event newline boundaries across streamed image chunks and suppress keep-alive comments while inside an incomplete SSE event. - Reset streaming keep-alive tickers when downstream bytes are written and ensure terminal error events start on a clean SSE boundary. Closes: router-for-me#7330
PinableAgents
marked this pull request as ready for review
November 8, 2026 12:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Compatibility and regression fixes
Verified final commit
af6b2c2b0f175ba8f18123d72b233a6c8c04c62eAll applicable CI checks passed:
Additional local verification: 60 Python tests; 1,000 OAuth refresh regression repetitions; 500 WebSocket cleanup repetitions; 30 Home regressions; 100 HTTP interrupt repetitions; 20 bootstrap repetitions; 50 shutdown race-detector repetitions; plugin isolation/inherited-writer/output-limit tests. Local netlink restrictions were covered by the unrestricted CI suite.
Release impact
Merged with owner approval using merge commit f4d741c. The existing version policy advances the runtime from 20261022021453-f9e84579cb58 to 20261108124215-f4d741cb5547. Main validation and stable publication are running; release completion will be verified separately.