Skip to content

fix(proxy): Responses→Chat format conversion issues (cache_control, tool_choice, stream truncation) #257

@thedavidweng

Description

@thedavidweng

Problem

Several Responses→Chat format conversion bugs can cause upstream 400 errors or masked stream failures when routing Codex through Chat Completions providers.

1. cache_control passthrough causes 400 errors

During Anthropic→OpenAI format conversion, cache_control fields are preserved on system messages, text content blocks, and tools. Strict OpenAI-compatible endpoints (e.g. MiniMax, some Chinese providers) reject these unknown fields with 400 errors.

Additionally, when a user message has exactly one text content block with cache_control, the content is kept in array-of-objects format instead of being simplified to a plain string. Some providers expect the simpler format.

Location: src-tauri/src/proxy/providers/transform.rs — system message construction (line ~122), text block construction (line ~339), tool construction (line ~187), and single-block simplification (line ~407).

2. tool_choice sent without tools

In the Responses→Chat conversion, tool_choice is copied unconditionally from the input body, but tools is only included when non-empty. If all tools are filtered out during conversion, the request contains tool_choice without a corresponding tools field, which causes errors on providers that enforce this constraint.

Location: src-tauri/src/proxy/providers/transform_codex_chat.rs — lines 308-315. The tool_choice block should be inside the if !tools.is_empty() guard.

3. Truncated chat streams masked as normal completion

When a Codex Chat→Responses stream ends without receiving [DONE] or a finish_reason, the code unconditionally calls finalize() which emits response.completed. This masks truncation as successful completion. The fix should distinguish three cases:

  • Normal completion: finish_reason or [DONE] received → response.completed
  • Incomplete with output: substantive output exists but no finish_reasonresponse.incomplete
  • Empty truncation: no output and no finish_reasonresponse.failed with stream_truncated

Location: src-tauri/src/proxy/providers/streaming_codex_chat.rsfinalize() method (line ~525) and response_status_from_finish_reason() in transform_codex_chat.rs (line ~1566).

Expected Behavior

  • cache_control fields should be stripped from the OpenAI-format request body to avoid 400 errors on strict endpoints
  • Single text content blocks should always be simplified to plain string format
  • tool_choice should be omitted when tools is empty
  • Truncated streams should be reported as incomplete or failed, not completed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions