Repository navigation
Preserve Chat Completions for chat-native open-source agents #1467
Description
Area
Proxy and routing
What are you trying to accomplish?
OpenAI Chat Completions-native open-source agents should be able to use OpenCodex routing without having their requests and responses translated through the Responses protocol when the selected upstream provider also speaks openai-chat.
This applies to generic third-party agents configured with POST /v1/chat/completions, not to Codex clients using Responses or Claude Code clients using Messages.
What prevents this today?
The inbound Chat Completions handler always converts the request into the internal Responses shape, runs the Responses pipeline, converts an openai-chat provider response into Responses events, and then converts those events back into Chat Completions.
For a Chat-native client routed to a Chat-native provider, the effective path is:
Chat client -> Chat request -> Responses AST -> Chat upstream
<- Chat response <- Responses events <-
The double conversion adds work and can alter protocol details that the original client and provider both understand natively, including assistant reasoning fields, tool-call history, message roles, tool schemas, IDs, and streaming padding frames. A direct Chat client-to-provider configuration avoids those transformations.
What should OpenCodex do?
When an inbound /v1/chat/completions request resolves to an openai-chat target, OpenCodex should keep a Chat-native execution and output path instead of converting through Responses.
The path must still preserve OpenCodex behavior that is independent of wire format: admission authentication, model and combo routing, provider credential selection, API-key pools, pre-stream retry and failover, cancellation, usage accounting, request logging, and safe provider-specific policy.
If the selected target uses a different protocol, or if a feature requires the existing Responses bridge, OpenCodex should retain the current translation path. Existing Codex Responses and Claude Messages behavior must remain unchanged.
Example usage or interface
OpenAI-compatible open-source agent
POST /v1/chat/completions
model: synthetic/hf:moonshotai/Kimi-K3
|
v
OpenCodex admission + routing + key pool + logging
|
v
Synthetic POST /openai/v1/chat/completions
Expected protocol matrix:
Chat inbound + openai-chat target -> Chat-native path
Chat inbound + non-Chat target -> Existing translation bridge
Responses inbound + Chat target -> Existing Responses-to-Chat adapter
Messages inbound -> Existing Claude-compatible path
Alternatives or workarounds
- Configure the third-party agent directly with each provider endpoint and API key. This avoids conversion but loses OpenCodex model aggregation, credential isolation, key pools, combos, and logging.
- Continue translating every Chat request through Responses and add provider-specific compatibility patches. This addresses individual regressions but retains the structural source of lossy round trips.
- Relay raw upstream bytes immediately. This is too narrow unless Chat-specific inspection preserves usage, terminal state, cancellation, retry/failover, tool aliases, and request logs.
Additional context
This is a focused architectural follow-up to the external third-party agent gateway implemented after #357. The current documentation describes Chat Completions as translating into the internal Responses pipeline so all providers and sidecars share one lifecycle.
A safe implementation should reuse that lifecycle rather than duplicate it. One possible boundary is a Chat output mode in the shared core after route/auth/recovery decisions, with the existing bridge retained for protocol-mismatched targets and features that require it.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
Ingwannu commentedon Aug 11, 2026
Confirmed against current
dev@20c513198: this limitation is real.src/server/chat-completions.tscallschatCompletionsToResponsesBody()before route execution, then sends the translated request throughhandleResponses(). When the selected target isopenai-chat, the adapter turns that internal Responses request back into Chat Completions upstream, andsrc/chat/outbound.tsconverts the returned Responses events back into Chat Completions for the caller. The existing "native passthrough" logging path is for a native Responses transport; it is not a Chat-native client-to-provider path.The direction is valuable, but this should not be implemented as a raw-byte shortcut. A safe patch needs one shared execution lifecycle and must preserve admission, route/combo selection, credential and key-pool ownership, pre-stream retry/failover, cancellation, terminal status, usage accounting, request logs, provider policy, and the existing bridge whenever the selected wire or an enabled feature requires it.
Before merge, I would require:
[Decision Log]for the shared lifecycle boundary and fallback rule;Keeping this open as a valid architectural enhancement. There is no merge-ready implementation attached yet.
fix(chat): address CodeRabbit review for chat-native path (lidge-jun#…
test(chat): cover native credential isolation and tool calls (lidge-j…
fix(chat): address remaining CodeRabbit findings (lidge-jun#1467)
test(chat): prove native bounded reader releases on overflow (lidge-j…
feat(chat): native chat->chat path for openai-chat providers (lidge-j…
fix(chat): address CodeRabbit review for chat-native path (lidge-jun#…
test(chat): cover native credential isolation and tool calls (lidge-j…
fix(chat): address remaining CodeRabbit findings (lidge-jun#1467)
test(chat): prove native bounded reader releases on overflow (lidge-j…
1 remaining item
fix(chat): address additional CodeRabbit findings (lidge-jun#1467)
fix(chat): address maintainer review — replay, bounds, validation (li…
test(chat): cover native credential isolation and tool calls (lidge-j…
test(chat): prove native bounded reader releases on overflow (lidge-j…
feat(chat): rebuild native chat transport (lidge-jun#1467)
fix(chat): preserve native Chat request fidelity (lidge-jun#1467)
lidge-jun commentedon Aug 15, 2026
Implemented on
devand verified at 02728d2 — closing.A Chat inbound request whose resolved target is
openai-chatand needs nothing from the Responses lifecycle dispatches natively with the original Chat body (src/server/chat-native.ts:52gates eligibility,src/server/chat-completions.ts:134dispatches). Anything ineligible — or any non-Chat target — still goes through the existing bridge atsrc/server/chat-completions.ts:158and:240, so nothing regressed for those clients.Smoke evidence on the remote validation host:
bun test tests/chat-completions-endpoint.test.ts→ 74 pass / 0 fail. That file covers both halves: native Chat fields surviving unchanged, and Chat inbound still succeeding against anopenai-responsestarget through the bridge.fix(chat): keep the structured-output opt-out exact on the native cha…
fix(chat): keep the structured-output opt-out exact on the native cha…
test(chat): cover native credential isolation and tool calls (lidge-j…
test(chat): prove native bounded reader releases on overflow (lidge-j…
feat(chat): rebuild native chat transport (lidge-jun#1467)
fix(chat): preserve native Chat request fidelity (lidge-jun#1467)
fix(chat): keep the structured-output opt-out exact on the native cha…