- Resolve conflict in openai_codex_models_service_test.go: keep both
GPT-6 Astra instructions template assertion (from this PR) and ultra
reasoning level + multi_agent fields (from #6718 on main)
- Fix compilation error on main: add missing `group *Group` parameter
to buildCodexModelsManifestForAccounts calls in
openai_codex_model_metadata_test.go (lines 24, 32) — caused by #6688
changing the function signature while #6718 added new callers with
the old signature
Follow-up to the event-time proxy snapshot: every upstream attempt now
carries attribution, the direct/unknown contract is enforced everywhere,
and the queued upstream_errors array has hard bounds again.
Attribution coverage
- compact fallback retry helper and the streaming compact failover event
now stamp proxy_id/proxy_name; the non-streaming SSE compact retry
previously dropped its first attempt on `continue` and now records it
- raw chat-completions stream truncation events (new on main) attributed
- Anthropic/Bedrock transport errors get attribution inside the shared
handleUpstreamTransportError helper, so all five callers inherit it
- OpenAI first-output timeout takes attribution from the caller: HTTP
callers pass the HTTP rule, the WS passthrough relay passes the WS rule
(no managed proxy => unknown, never direct, because http.DefaultClient
may honor environment proxies)
Contract hardening
- single opsUpstreamProxyAttribution returns (id, name) from one decision;
a hydrated Proxy without a durable ID is unknown, not id=null+name
- both normalizers (struct at append time, JSON on detail reads) enforce
proxy_id==null <=> sentinel name; blank managed name => "proxy"
- detail-read normalization edits only the attribution keys via sjson, so
keys from older struct versions and stored key order are preserved
Queue bounds
- keep at most 256 attempts and ~512 KiB serialized per entry, newest
first; the oldest retained event records dropped_earlier_attempts
- attempts outside the 16-event body window trim url/message to 512 bytes
- restore the empty-event drop rule, judged before the body window clears
detail so older real attempts survive
Tests cover each site above (managed proxy, direct, WS unknown), the
normalizer invariants, key/order preservation, and the count/byte bounds.
Moonshot pay-as-you-go and Kimi For Coding both expose native
/v1/responses. Stop converting Kimi Responses traffic to Chat
Completions, and treat Kimi like DeepSeek for protocol selection while
keeping /v1/responses URL construction.
Merge origin/main into contrib/routed-codex-model-catalog.
Single conflict in openai_gateway_forward.go: main added
ClearActualOpenAIUpstreamEndpoint + SetActualOpenAIUpstreamEndpoint
(commit 4795650d2) at the same insertion point where the PR added
filterOpenAIResponsesNoneReasoningEffortForAccount. Both changes
are independent — endpoint observability first, then body filtering.
Keep catalog membership on schedulable accounts and intersect advertised
capabilities across all active group members that map an alias. Preserve
upstream plugin, service-tier, and models-list body-limit changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Accept fast|priority (canonical priority), flex|auto|default|scale on
/v1/responses and /v1/chat/completions; reject unknown/empty/non-string
with HTTP 400; omitted and null stay compatible.
- Propagate service_tier through JSON/SSE, Responses<->Chat conversions,
fallback paths and HTTP->upstream WebSocket bridge.
- Billing prefers the upstream terminal tier; the outbound (policy-
transformed) tier is used only when upstream omits the field.
Explicit upstream default bills Standard even when Fast was requested.
- Pricing: Fast premium 2x Standard for gpt-5.6-sol/terra/luna and
gpt-5.4; 2.5x for gpt-5.5; channel FastMultiplier stays authoritative.
- Live verification (official Codex 0.149.0 + gateway, HTTP & WS):
upstream ChatGPT backend may return terminal default even when the
account catalog advertises priority; billing follows the actual tier.
Account model mappings decide availability, but generated Codex catalogs previously inferred capabilities from local model-name tables. Compatible upstreams can expose unknown models such as OpenCode x-preview-f-free, so reasoning levels, image input, and context limits were missing or wrong.
Sync capability metadata from the account model endpoint, enrich incomplete lists from the Models.dev provider matching the account base URL, and persist only complete snapshots. Mixed groups consume the safe intersection across schedulable accounts. When IDs sync without complete metadata, return an explicit warning and preserve the previous snapshot.
Third-party Responses providers often omit /models. If that endpoint returns 404 or 405 and the account already has concrete model mappings, use those configured upstream IDs for capability enrichment. Authentication, rate-limit, server, and network failures remain hard errors, and metadata is never guessed across providers by model name alone.
Advertise a single none choice for non-reasoning models so current Codex can select them, then omit that catalog placeholder when forwarding to compatible Responses upstreams while preserving official OpenAI request semantics.
Token exchange, PAT whoami, models, probes, and pre-writes now follow
the same UA/version chain as Codex inference instead of hardcoded
codex-cli/0.91.0 or compile-time constants.
Default codex_fingerprint_mode to off. v0.1.175 treated a missing key as
"session", so upgrading silently rewrote installation/session/thread/turn/
window identifiers for every existing OAuth account that had never configured
this field. The quota regressions in #5555, #5556 and #5582 line up with that
version boundary, with A/B reports that rolling back to v0.1.173 restores
quota. Convergence is now explicit opt-in (#5610).
Only accounts that never set the field change behaviour; explicit off /
device / session / full keep working exactly as configured. That required
flipping the persistence condition in all three account modals from
"!== 'session'" to "!== 'off'": the old rule deleted the key when it equalled
the default, which after the flip would have silently discarded an
administrator's explicit opt-in to session.
Also extend convergence to the passthrough path, which previously left client
identifiers untouched:
- resolve the ids once in forwardOpenAIPassthrough and rewrite
client_metadata on the raw bytes (gjson extract + sjson splice) because
passthrough is a hot path that must not fully unmarshal multi-MB bodies;
a shared core keeps the raw and map variants from drifting
- both request builders apply the staged ids at the same relative position
(after session isolation, before identity enforcement) so headers and body
share one id set and turn_id stays consistent
- stage the ids unconditionally, including nil: a failover from a converged
account to an off account must not leave the previous account's ids behind