Skip to content

[Compatibility] Add an opt-in Responses stream/terminal repair escape hatch for custom providers #1809

Description

@Wibias

Summary

Custom openai-responses providers can hit compatibility failures where the upstream turn completes but Native Codex does not receive a terminal lifecycle it can commit. PR #1367 reproduced this class of failure and explored a per-model bounded-JSON fallback, but that PR cannot land as written and should be treated as evidence/design exploration rather than an implementation to salvage.

The remaining requirement is still valid: custom Responses providers need a narrow, explicit compatibility escape hatch that uses the current Responses repair architecture instead of adding another broad transport/configuration path.

Why this issue exists

PR #1367 reproduced the following boundary on both macOS arm64 and GNU/Linux x86_64:

  • the same Responses upstream completed when Native Codex connected directly;
  • through OpenCodex, the upstream/internal path reached response.completed;
  • Native Codex did not commit the turn and later entered its stream-idle retry path;
  • the reproduction did not require the reporter's separate OCI relay;
  • minimal calls could succeed, while richer turns reproduced the delivery failure.

That is useful evidence that there is a real Responses compatibility class after upstream completion and before the client commits the terminal event. It does not prove one specific Bun primitive is the sole cause.

The maintainer triage on #1367 also makes the current direction explicit:

The failure class is real and still uncovered. Current terminal repair is a narrower registry-controlled case, and no custom-provider escape hatch exists on current dev. A redesign around the current registry-only compatibility hint + terminal-repair architecture would be welcome.

PR #1367 itself is conflicting, far behind dev, and has unresolved blockers, so this issue intentionally does not copy its implementation surface.

Current architecture to build on

Current dev already has a registry-only Responses terminal-repair path in:

  • src/providers/registry.ts
  • src/server/responses-terminal-repair.ts
  • src/server/responses/core.ts

That path was designed for the narrow DeepSeek case where output items are structurally complete but a real terminal is missing or indefinitely delayed. It keeps upstream streaming, relays healthy streams unchanged, and can synthesize one terminal only after strict structural checks and a grace period.

This issue should extend that architecture rather than create a second broad Responses validation/reframing subsystem.

Proposed direction

Add a custom-provider, per-model compatibility hint for effective openai-responses routes. The hint should resolve through the same model/wire policy machinery used by registry compatibility policies and should be inactive unless explicitly configured.

The implementation should prefer the smallest repair that matches the observed failure:

  1. Terminal repair first. Allow an eligible custom Responses model to opt into the existing bounded terminal-repair state machine, with the same fail-closed structural requirements used by registry-controlled repair.
  2. Preserve healthy streams byte-for-byte where possible. A real upstream terminal remains authoritative and must never be duplicated or replaced.
  3. Do not silently apply repair to unrelated providers/models. Resolve after the effective per-model wire is known and reject the setting when that wire is not openai-responses.
  4. Keep forward-auth/canonical OpenAI routes protected. Do not allow a custom compatibility setting to mutate the canonical forward-auth contract unless a separate design explicitly approves that.
  5. If the feat(responses): add an opt-in bounded JSON fallback for custom providers #1367 reproduction remains broken because the upstream terminal is present but is lost after the relay/inspection split, evaluate a bounded non-streaming fallback as a second, explicit compatibility mode. That fallback should live behind the same narrow compatibility-policy boundary. It should not reintroduce feat(responses): add an opt-in bounded JSON fallback for custom providers #1367's broad shared WebSocket validation changes or make non-streaming the default.

A shape such as the following is illustrative, not prescribed:

{
  "providers": {
    "<custom-provider>": {
      "adapter": "openai-responses",
      "modelResponsesCompatibility": {
        "<model>": "terminal-repair"
      }
    }
  }
}

If more than one repair mode is eventually justified, prefer an explicit enum/policy object over multiple overlapping booleans.

Acceptance criteria

  • A custom provider model whose effective wire is openai-responses can explicitly opt into the supported repair mode.
  • The default behavior of unconfigured providers/models is unchanged.
  • Registry-controlled repair continues to work unchanged.
  • Healthy SSE with a real response.completed, response.failed, or response.incomplete remains authoritative and is not duplicated.
  • Partial, malformed, tainted, cancelled, aborted, unknown-item, or over-budget output never gets fabricated as successful completion.
  • Function calls preserve item ids, call ids, names, arguments, ordering, and continuation usability.
  • HTTP/SSE and Responses WebSocket behavior have focused regression coverage where the selected repair mode applies.
  • Client cancellation aborts/releases the repair state and any upstream read without retained timers or buffers.
  • Case-insensitive per-model configuration cannot contain conflicting duplicate keys.
  • The policy is validated against the effective per-model wire, including modelAdapters/virtual-model resolution where applicable.
  • Diagnostics, if added, remain content-free and do not log prompts, response text, credentials, provider secrets, or raw payloads.
  • Live regression coverage includes the failure shape from feat(responses): add an opt-in bounded JSON fallback for custom providers #1367 on macOS arm64 and Linux x86_64 before the issue is considered fixed.

Design guardrails

Do not use this issue to:

The important distinction is that terminal repair and downstream relay loss are related compatibility symptoms, but they are not automatically the same bug. The implementation should prove which repair mode covers the current reproduction instead of assuming it.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    streamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions