Commit Graph

3409 Commits

Author SHA1 Message Date
Luis Pater
f2d272da81 fix(claude): recover OAuth tool aliases for repeated prefixes and malformed IDs
- Normalize repeated `mcp__<server>__` alias prefixes during reverse remapping to resolve stacked aliases.
- Add a semantic-suffix fallback when parsing fails, allowing unambiguous recovery from malformed tool IDs.

Closes: #4916
2026-08-14 13:52:08 +08:00
Luis Pater
7ea9c670ea feat(registry): add Gemini 3.7 Flash High model definition
- Added `gemini-3.7-flash-high` to `internal/registry/models/models.json` with completion/context limits, multimodal input/output metadata, and supported thinking levels.
2026-08-14 03:19:35 +08:00
Supra4E8C
d757063c96 fix(docs): update RunAPI registration links in README files 2026-08-13 15:21:29 +08:00
Supra4E8C
a40d0e6e9d fix(docs): update FennoAI sponsorship details and links in README files 2026-08-13 15:20:13 +08:00
Luis Pater
323b7276bc feat(registry): add model modality metadata to registry definitions
- Added `supportedInputModalities` and `supportedOutputModalities` across model entries in `internal/registry/models/models.json`.
- Filled capability metadata for text/image/audio/video-aware models (mainly Claude/Gemini-family and related provider routes), including Gemini image variants and image-generation models.
- Standardized modality descriptors for multimodal compatibility checks in model routing/validation without changing existing token or generation settings.
v7.2.131
2026-08-13 14:39:05 +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
8b54db36ae fix(translator): drop Gemini hidden thought parts during request conversion
Closes: #4914
2026-08-13 01:10:04 +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
Luis Pater
f43aad7637 fix(codex): normalize request session header to Session-Id and preload codex headers
- Replaced legacy `Session_id` usage with canonical `Session-Id` when propagating cache/session state.
- Added ensured request headers for `X-Codex-Window-Id`, `Thread-Id`, `Session-Id`, and `X-Openai-Internal-Codex-Responses-Lite`.
- Removed macOS-only `Session_id` auto-generation fallback path for request header setup.
v7.2.130
2026-08-12 18:28:40 +08:00
Luis Pater
133047de66 fix(codex): clear multi-agent-v2 optimization state on namespace conflicts
Closes: #4919
2026-08-12 18:20:13 +08:00
Luis Pater
5b5f428ad9 fix(claude): recover OAuth tool names with duplicated server alias prefixes
- Handle reversed tool names starting with `mcp__<server>__<server>__` by resolving them against the exact alias map before regular matching.
- Prevent failed reverse remapping for malformed/duplicated MCP alias patterns by short-circuiting to the direct alias match path.

Closes: #4916
2026-08-12 17:37:12 +08:00
Luis Pater
2ab25eae96 fix(codex): strip unsupported prompt_cache_options from OpenAI Responses requests
Closes: #4915
2026-08-12 17:18:21 +08: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
a2337beb24 fix(kimi): select upstream request format from source format (Claude/OpenAI)
Closes: #4910
2026-08-12 15:43:19 +08:00
Luis Pater
17a479a8db feat(management): add request-scoped proxy override for APICall
- Add `proxy_url` to APICall request payload and document it as highest-priority proxy selection.
- Validate request-provided proxy URLs and reject invalid values with `400`.
- Pass request proxy through token resolution/Antigravity token refresh and APICall transport setup.
- Update transport precedence so `proxy_url` overrides credential/global proxies; invalid request proxy falls back to direct.
2026-08-12 15:26:32 +08:00
Luis Pater
b08fe3b492 fix(codex): preserve multi-agent-v2 namespace handling across incremental websocket turns
Closes: #4909
2026-08-12 15:04:17 +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
Luis Pater
934da2379d fix(openai): preserve structured and stringified custom tool outputs during Responses conversion
- Add `setCustomToolCallOutputContent` for `custom_tool_call_output` handling in the request translator.
- Parse JSON-encoded `output` strings and route image-based tool-output parts through existing image/function-call conversion path.
- Keep non-image outputs as text via `responsesToolOutputText`, including plain text and unrecognized/invalid image payloads.

Closes: #4902
v7.2.129
2026-08-12 02:31:10 +08:00
Luis Pater
e9c44ae256 fix(codex): fall back to current tool-call state when item IDs don’t match
- Update `findToolCallState` to only return `toolCallStates` entries when a non-nil state exists.
- Preserve `currentToolCall` fallback for unknown `item_id`, `id`, or `output_index` lookups to keep streaming tool-call context intact.

Closes: #4901
2026-08-12 01:44:07 +08:00
Luis Pater
a59caebc68 fix(kimi): treat [reasoning unavailable] as unusable reasoning in message normalization
- Introduce a shared `isUsableKimiReasoning` helper and constant for the placeholder string.
- Prevent tool-call normalization from propagating or reusing placeholder/empty reasoning content.
- Preserve and reuse the latest valid assistant reasoning when normalizing `reasoning_content` for tool messages.

Closes: #4899
2026-08-12 01:30:18 +08:00
Luis Pater
db143aebac fix(codex): make input ID sanitization collision-resistant and deterministic
- Track normalized ID state (`occupied`/`preserved`) during preprocessing to avoid remapping valid existing IDs.
- Resolve collisions by appending deterministic hash-based suffixes until a free ID is found.
- Share occupancy tracking across shortening and remapping so sanitized IDs remain stable and idempotent.

Closes: #4891
2026-08-12 01:04:22 +08:00
sususu
189776aab1 fix(claude): validate legacy-model system turns before sending
A caller can put a {"role":"system"} turn inside messages. Models older than
the role=system turn reject it outright, verified against api.anthropic.com on
both /v1/messages and /v1/messages/count_tokens:

    400 role 'system' is not supported on this model

claude-haiku-4-5, claude-sonnet-4-5, claude-sonnet-4-6 and claude-opus-4-6
answer that way, while claude-sonnet-5 and claude-opus-5 accept the turn.
claudeLegacySystemReminderModels already enumerates that boundary, which is why
claudeCodeCLIBetas withholds mid-conversation-system-2026-04-07 for those
models, but caller-provided turns were forwarded unchanged and always spent an
upstream call on a guaranteed rejection.

The native client does not produce the pairing either: it gates the turn on the
model. In 314 captured native requests the turn appears only on
claude-opus-5 and claude-sonnet-5, and on none of the 43 requests addressed to
a model in that set. That is an observation about the captures rather than a
proof about the upstream, so it only corroborates the measured rejection.

Validate the finished body, and only inside the evidence that produced the
rule:

- The check runs after the body is finalized and before
  http.NewRequestWithContext. Payload rules can rewrite model and messages long
  after translation, so an earlier check would not describe what is sent.
- Only Anthropic's first-party origin is covered, matching the reasoning
  shouldUseClaudeUpstreamTokenCount already applies to count_tokens. A
  third-party gateway may map these model IDs onto something that accepts the
  turn and therefore decides for itself.
- A confirmed native caller keeps the passthrough. It gates the turn itself, so
  its body is forwarded untouched and the upstream error reaches it unchanged.
- Unknown and future model IDs stay optimistic and are forwarded, matching how
  checkSystemInstructions treats them.
- rebuild_mid_system_message still folds caller turns into the system slot; the
  final check therefore preserves the explicit escape hatch.

Cloaking adds one extra ordering case: it can place a caller's top-level system
prompt into a role=system turn for a modern model before a payload rule changes
the model to legacy. Track only the exact contiguous turns that CPA inserted,
using both their position and the corresponding message-count increase as
provenance. After payload rules settle the model, replay those turns through the
existing legacy <system-reminder> path. Pre-existing caller turns, even if they
have identical content, remain caller-owned and are still rejected. If payload
rules also rewrite the tracked messages, reconciliation fails closed and final
validation returns 400 rather than guessing provenance.

The 400 is request-scoped like claudeCallerSystemBlockError: the invalid
body/model pairing is independent of first-party credential health, so no
credential is cooled or retried.

Translated requests never carry the pairing, because every non-Claude source
format hoists system content into the top-level system field. Tests pin that for
OpenAI, Gemini, Responses and Interactions, and separately drive Execute,
ExecuteStream and CountTokens through an injected first-party transport.
2026-08-11 22:56:41 +08:00
sususu
8638f28db5 fix(claude): drop auto context_management without eligible thinking
Anthropic rejects the injected clear_thinking_20251015 strategy unless
thinking is enabled or adaptive:

  `clear_thinking_20251015` strategy requires `thinking` to be enabled or
  adaptive

Both sides of the automatic injection only special-cased the literal
{"type":"disabled"}, so an absent thinking field slipped through
gjson's empty string and the request was sent with a strategy the API
refuses. Two reachable paths produced it:

- a cloaked caller that never sent thinking at all;
- a caller whose thinking was enabled at injection time and then removed
  by disableThinkingIfToolChoiceForced, which runs between injection and
  reconciliation.

Both now test the accepted values instead of excluding the disabled one,
via a shared claudeThinkingAcceptsClearThinking helper, so reconciliation
also withdraws an object CPA injected itself once thinking disappears.
Caller-owned and payload-rule-owned objects keep their existing
precedence and are never withdrawn.

Verified against api.anthropic.com: context_management with thinking
absent or disabled returns 400, with thinking enabled returns 200. The
existing expectation that forced tool choice retains the automatic object
pinned the rejected shape and is inverted accordingly.
2026-08-11 19:33:40 +08:00
sususu
a8bbbea2b9 fix(claude): recognize and pass through native Haiku helpers
Claude Code issues measured Haiku helper requests that intentionally omit the
claude-code beta and, for the minimal shape, the system field. Treat those
profiles as confirmed native clients so CPA does not cloak or rewrite them.

Accept the native metadata builder optional parent_session_id, require
platform headers only for presence and software baseline rather than exact
equality, derive timeout and version from the same header defaults the emitter
uses, and keep discriminating signals strict: exact beta allowlist, body
shape, lowercase hex CCH, and session binding. Preserve helper transport
headers, omit synthetic stream false on markerless helpers, and gate the
billing and CCH fallback on system presence so a later pipeline-attached system
prompt cannot go upstream unsigned while the measured no-system wire stays
intact.
2026-08-11 19:33:40 +08:00
sususu
f0034ca663 fix(claude): restore cloaked prompt-cache ownership and native shape
Cloaked Responses/Chat/Gemini→Claude traffic was skipping ensureCacheControl
once the first-user cloak marker existed, freezing multi-turn cache hits.
Always ensure section-independent system and rolling-message breakpoints for
non-native callers, keep confirmed Claude Code placement intact, and leave
tools unstamped when a usable system prompt already covers the prefix.

Align the wire shape with the Claude Code 2.1.220/2.1.221/2.1.227 cache-control
constructor: the default is {"type":"ephemeral"} with no ttl. A 1h body ttl is
applied only after placement, only for OAuth credentials, and only onto blocks
that already carry cache_control, so it stays strictly paired with
extended-cache-ttl-2025-04-11. Explicit caller-owned 5m choices survive.

Match the native rolling selector (skip thinking-like assistant tails; final
system string special case), fall back to a tools breakpoint when system is
absent or blank, and leave empty/whitespace string systems unconverted so they
cannot double-stamp tools plus a useless system host.
2026-08-11 19:33:40 +08:00
sususu
516ec3a000 fix(antigravity): harden per-credential transport pooling
Review and live-test follow-ups to the shared upstream transport.

Bound the cache with an LRU that closes idle connections on eviction, so
rotating a credential's proxy or supplying a per-request base transport can
no longer leak pools.

Stop deriving a pool scope from Auth.Label: it is documented as an optional
human readable label for logging and carries no uniqueness guarantee, so two
OAuth identities sharing a label would share one TCP/TLS pool. Prefer a
refresh-token digest, which stays stable across access-token rotation and is
available to refresh requests that run before any access token exists.

Replace a typed-nil *http.Transport taken from the request context. It passes
the interface nil check, so leaving it in place made http.Client fall back to
http.DefaultTransport, which advertises h2 over ALPN and breaks the
HTTP/1.1-only fingerprint.

Only widen pool limits: treat MaxIdleConns == 0 and IdleConnTimeout == 0 as
unlimited, and leave a negative MaxIdleConnsPerHost alone because that is how
an operator disables pooling.

Size the cache for large deployments. An unused entry costs under 1 KB and no
goroutines, whereas evicting a live pool forces a fresh TCP + TLS handshake,
so capacity is not the lever for bounding memory.
2026-08-11 18:33:50 +08:00
sususu
c33a33e14a perf(antigravity): reuse native upstream connections 2026-08-11 18:33:50 +08:00
sususu
5fa66293db fix(antigravity): preserve request plugin hook semantics 2026-08-11 16:07:42 +08:00
sususu
cf8c27fe90 perf(antigravity): translate each upstream request once
Execute, executeClaudeNonStream and ExecuteStream all assign the validated
original payload to the request and then translate both values. Since both
translations saw the same bytes, every Antigravity request paid for a second
full scan of the client payload. On a captured 24MB tool-history request that
second pass cost roughly 0.9s of CPU and 651MB of allocations.

Translate once when both inputs share a backing array and hand the caller an
independent duplicate, because later stages edit the working copy in place.
Payloads that genuinely differ still get two translations.
2026-08-11 16:07:42 +08:00
sususu
d5f68856f6 perf(antigravity): batch pre-upstream JSON rewrites 2026-08-11 16:07:42 +08:00
sususu
177c7619b6 test(antigravity): clear credits state in place to fix a cleanup data race
resetAntigravityCreditsRetryState assigned a fresh sync.Map to each
package-level credits variable. Credits hint refreshes run on background
goroutines that outlive the request, so they can still be writing those
maps when a test's cleanup runs. Replacing the variable is an
unsynchronized write to the same memory the goroutine is mutating, which
made `go test -race ./internal/runtime/executor` fail with two data races
in TestAntigravityExecute_NoCreditsWithoutConductorFlag.

Empty each map in place with sync.Map.Clear instead. Clear is safe against
concurrent users, and the reset semantics are unchanged.

The races were pre-existing and reproduce identically on b921b5d0; only
test code changes here. Production code never assigned these variables, so
it was never affected.

  before  go test -race ./internal/runtime/executor  FAIL, 2 data races
  after   go test -race ./internal/runtime/executor  ok, 0 data races
2026-08-11 09:57:15 +08:00
sususu
bb7278a1af perf(antigravity): skip replay index rebuilds no item can observe
The replay request index exists solely so that the next item in a
sequential apply loop observes the mutated payload. Both loops rebuilt it
after every successful mutation, including after the final item, whose
rebuild no successor ever reads.

insertAntigravityReasoningReplayItemsWithSchemas is worse than that: its
index parameter is a local pointer, so a rebuild is discarded the moment
the function returns. applyAntigravityReasoningReplayItems only ever
passes a single eligible item, which made every one of those rebuilds
dead work, and the caller then rebuilt the same payload again.

Rebuild only when a successor still has to read the index. Behavior is
unchanged: 800 differential rows over 400 randomized multi-turn payloads
(570 of them mutating) hash-match the previous implementation byte for
byte, under both nil and populated tool schemas.

BenchmarkApplyAntigravityReasoningReplayItems/indexed (1 MiB, 32 turns):
  before  ~302 ms/op  66309 allocs/op
  after   ~269 ms/op  62153 allocs/op

A real 24 MiB production payload (182 contents, 96 ledger items) shows
the effect the synthetic benchmark understates:
  before  6723 ms/op  223381 allocs/op
  after   4035 ms/op  144789 allocs/op
2026-08-11 09:57:15 +08:00
sususu
984836ba37 refactor(antigravity): route replay merge through the request index
The indexing change left two parallel implementations of the same replay
matching logic: the filter path used the request index while the merge and
insert paths still rescanned the payload. Filter and merge must agree on which
part a ledger item targets, so the duplication was a latent source of signatures
being replayed onto the wrong part.

Move the write path onto the index. Every lookup in the merge path already ran
before the first mutation, so one index describes the whole call; insert rebuilds
it after each mutation to keep sequential semantics. Collapse the thought
signature locator into thoughtSignaturePartIndex, shared by the eligibility check
and the write path.

Delete the superseded payload-scanning implementations along with functions that
had no production callers left: antigravityNeedsSignatureReplayForExistingFunctionCall,
antigravityRequestHasMatchingFunctionResponse, antigravityPayloadHasFunctionCallID,
filterAntigravityReasoningReplayItemsForRequest, insertAntigravityReasoningReplayItems,
mergeAntigravityFunctionCallPartReplay and antigravitySetReplayItemContextHash.

Freeze the pre-index implementations into a dedicated oracle test file. The
differential tests previously called the production functions they were meant to
check, so consolidating the logic would have silently turned them into
self-comparisons.

Malformed non-array parts now fail closed consistently. gjson's Result.Array()
yields a one-element slice for a non-array non-null value, so the old fallback
scans could match a functionCall inside a parts object while the primary ID
lookup could not. The end state was already identical because such a payload
cannot be written to; the behavior is pinned by a test.

Also cover the positional fallback for pre-targetHash cache entries, which had
no test at all, and add a write-path benchmark.
2026-08-11 09:57:15 +08:00
sususu
0e4c0dab73 refactor(antigravity): document replay index lifecycle and align empty-contents semantics
Document that the replay request index is request-scoped, retains no-copy GJSON
results aliasing the payload, and is not safe for concurrent use, and that the
context fingerprints snapshot a running hash lazily in content order.

Replace the fingerprint byte counter with a boolean since it only distinguishes
an empty fingerprint from a hashed one.

Guard reasoningReplayItemsFromRequest on validContents so a valid but empty
contents array keeps returning an empty non-nil slice, exactly as it did before
the index was introduced, and lock that invariant with a test.
2026-08-11 09:57:15 +08:00
sususu
9eedbc27bd perf(antigravity): index reasoning replay requests
Cache request contents, function locations, and incremental context fingerprints so large replay histories are scanned once. Rebuild the request index only after mutations and reuse the precomputed response context hash in replay accumulators.
2026-08-11 09:57:15 +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
Luis Pater
5d9b629962 fix(runtime): close per-request uTLS HTTP/2 connections with request context
- Create a fresh uTLS/HTTP-2 connection per request instead of reusing cached connections.
- Pass request context through dialing and TLS handshake to make `RoundTrip` cancelable.
- Wrap response bodies so the underlying HTTP/2 connection is closed when the body is closed, and clean up promptly on request/response failures.

Closes: #4878
2026-08-11 00:58:13 +08:00
Luis Pater
bd34ceca04 feat(codex): add realtime hangup forwarding and local client-secret support
- Add Codex live handlers for unsupported translation/transcription/SIP endpoints, returning standardized `realtime_capability_not_supported` errors.
- Implement `HandleHangup` to validate call ownership, select/refresh pinned OAuth credentials, forward hangup requests to upstream, and complete local session on success.
- Add ephemeral client-secret infrastructure for local ephemeral auth: create/authenticate endpoints, token storage with expiry/capacity limits, session normalization/model mapping, and unified realtime error handling.

Closes: #4726
v7.2.128
2026-08-10 22:52:56 +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
ecc9aa72b3 fix(openai): preserve assistant content when converting Responses tool-call turns
- Merge buffered `tool_calls` into the latest mergeable assistant message instead of always appending a new one.
- Combine deferred reasoning segments across the same assistant turn, while ignoring `[reasoning unavailable]` placeholders.
- Reset merge state on role/tool-output boundaries to keep tool-call attachment behavior correct across turns.

Closes: #4676
v7.2.127
2026-08-10 05:13:24 +08:00
Luis Pater
93c378b791 fix(claude): recover malformed OAuth MCP aliases when reverse-remapping Claude tool names
Closes: #4867
2026-08-10 04:32:28 +08:00
Luis Pater
a6825fe992 fix(xai): normalize forced web_search tool_choice during Responses request prep
Closes: #4718
v7.2.126
2026-08-09 23:15:46 +08:00
Luis Pater
6710a5af30 Merge pull request #4865 from shoucandanghehe/fix/codex-sequential-cutoff-summary
fix(codex): forward sequential cutoff reasoning summaries
2026-08-09 21:52:06 +08:00
Luis Pater
673bac5fc6 fix(codex): normalize custom_tool_call_output IDs with ctco_ prefix during Codex input sanitization 2026-08-09 21:34:01 +08:00
shoucandanghehe
5314b29da9 fix(codex): forward sequential cutoff reasoning summaries 2026-08-09 18:01:28 +08:00
Luis Pater
37411842e8 fix(openai,claude): normalize Responses custom tool-call identity with shared namespace handling
Closes: #4850
2026-08-09 15:30:13 +08:00
Luis Pater
4906ead34f fix(openai): finalize open response items on [DONE] when final chunk lacks finish_reason
Closes: #4860
2026-08-09 05:28:15 +08:00