Skip to content

[Provider compatibility] Responses passthrough is missing the chat-line tool_search rewrite and restore #1950

Description

@YotuAI

Client or integration

Codex App

Provider or upstream service

Any third-party /v1/responses gateway reached through Responses passthrough. Reproduced on opencode-go / zen (deepseek-v4-flash); the same gap applies to other API-key Responses endpoints (for example DeepSeek official /responses).

OpenCodex version

2.24.2 (@bitkyc08/opencodex@2.24.2, published 2026-08-17)

Endpoint or capability

/v1/responses passthrough — Codex private tool_search (deferred tool discovery)

On the Responses passthrough line (createResponsesPassthroughAdapter in src/adapters/openai-responses.ts), OpenCodex forwards the Codex body to third-party gateways without the two translations the chat line already performs for Codex's private tool_search tool. Routed models therefore cannot start deferred-tool discovery, and a function_call[name=tool_search] that does appear is returned as-is — the Codex client never sees a tool_search_call.

This is a passthrough-line gap, not a provider-specific bug. The chat translation path already has both halves.

Current behaviour

1. Request — {type:"tool_search"} is not exposed as a callable function.

Passthrough serializes the incoming tools array without rewriting the private type. Third-party gateways do not implement type: "tool_search", so the model never sees a callable tool_search tool and cannot start discovery.

Stock 2.24.2, passthrough to zen / deepseek-v4-flash, tools include {type:"tool_search"} plus a deferred codex_app namespace tool. The model states that tool_search is not in its tool list and emits 0 tool_search_call items (live sample, translated):

I don't have a tool_search tool — the only tools in my current environment are:
- get_weather
- codex_app__set_thread_title

(tool_search_call_count: 0)

2. Response — function_call[name=tool_search] is not restored to tool_search_call.

The chat / collaboration path already restores this shape (src/bridge.ts 615–620 / 1024–1031 / 1590–1595, collector in src/server/responses/collaboration.ts 111–128). Passthrough does not run that restore: it returns the upstream item unchanged.

Observed passthrough output (with the request-side rewrite applied locally so the model can emit the call; stock 2.24.2 never reaches this step):

{
  "type": "function_call",
  "name": "tool_search",
  "arguments": "{\"query\": \"change conversation thread title\"}"
}

The Codex client executes discovery from tool_search_call, not from a generic function_call.

Not a defect: namespace. A zen-native namespaced call already arrives as {name:"set_thread_title", namespace:"codex_app"}. Passthrough does not strip that field. This issue is only about tool_search rewrite + restore.

Expected behaviour

On the non-forward Responses line, match the chat line:

  • Request: expose {type:"tool_search"} to the routed model as a callable function (same schema as src/responses/parser.ts 199–214).
  • Response: restore the model's function_call[name=tool_search] to the private tool_search_call shape the client executes.

Canonical OpenAI forward should keep the native type: "tool_search" unchanged.

Minimal redacted request or reproduction

  1. Install @bitkyc08/opencodex@2.24.2.

  2. Point a provider at a third-party /v1/responses gateway via per-model modelAdapters (API key omitted):

    {
    "providers": {
    "opencode-go": {
    "adapter": "openai-chat",
    "modelAdapters": {
    "deepseek-v4-flash": "openai-responses"
    }
    }
    }
    }

  3. In a Codex App session, ask the model to discover a deferred session tool via tool_search (for example a codex_app thread-title tool that is defer_loading: true).

  4. Observe: the model reports that tool_search is not in its tool list and does not emit tool_search_call.

The same tools array sent directly to the gateway (no proxy) is not the comparison that matters here: the gateway never advertised type: "tool_search" as a function. The missing step is the proxy translation the chat line already does.

Actual response or error

Stock 2.24.2 passthrough (redacted):

HTTP 200
output types: reasoning, message
tool_search_call_count: 0
assistant: tool_search is not in the available tool list

When a tool_search function call does appear (same local request-side rewrite; stock 2.24.2 never reaches this step), passthrough returns:

function_call[name=tool_search]

not tool_search_call.

Upstream documentation

  • Codex client contract: deferred discovery is a client-executed private type (type: "tool_search" in, tool_search_call out). It is not a public OpenAI Responses hosted tool. No public specification exists for that private type.
  • OpenCodex chat line already implements that contract: src/responses/parser.ts 199–214 exposes tool_search as a function; src/bridge.ts relays the call back as tool_search_call.
  • Third-party Responses gateways speak generic OpenAI Responses tools (function, web_search, and similar) and ignore or reject unknown private types. DeepSeek Responses tools table: https://api-docs.deepseek.com/guides/responses_api
  • opencode.ai zen: https://opencode.ai/docs/zen/

Expected behaviour is the Codex client contract plus the translation OpenCodex already ships on the chat line — not a claim that third-party gateways should implement type: "tool_search" natively.

Suggested mapping or implementation notes

The chat line already has both halves. Passthrough is missing the same pair:

  1. Request (non-forward / non-canonical-OpenAI branch only). Next to the existing promoteClientLoadedTools / rewriteRoutedCustomToolsForUpstream calls in createResponsesPassthroughAdapter, rewrite {type:"tool_search"} to {type:"function", name:"tool_search", ...} using the schema in src/responses/parser.ts 199–214. Reuse the current gate; do not add a new flag. Leave canonical OpenAI forward on the native type.
  2. Response (passthrough return path). Restore function_call[name=tool_search] to tool_search_call on the JSON and SSE rewrite chains — the same place routed custom-tool restore already hangs. The translation-line restore in src/bridge.ts / src/server/responses/collaboration.ts does not run on passthrough today.

promoteClientLoadedTools (#1602) only merges tools already returned by a prior tool_search_output. It does not convert the private type, so it cannot start discovery.

Feasibility (local experiment, not a proposed patch). On an unmodified 2.24.2 checkout plus a minimal patch at those two sites, a live third-party gateway closed the loop: the model issued tool_search_call, discovered the deferred tool, and called the namespaced tool successfully. web_search and ordinary function calls did not regress.

Additional context and attachments

Related (checked; this is not a duplicate of any of them):

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions