feat: Responses dialect for OpenAI-compatible providers whose vendors ship /responses (MiniMax, Moonshot, Groq, OpenRouter)
Summary
On main (6389a3bbf01e55693174e936f48135c4706d55ff), only four providers can speak the OpenAI Responses wire format: openai (dialect selectable), xai, chatgpt, and copilot. Every other OpenAI-compatible provider is hard-wired to POST /chat/completions through OpenAICompatibleProvider, with no way to select the dialect — even though several of those vendors now ship a native /responses endpoint.
This is a request to add a Responses dialect for the providers whose vendor API supports it, built on the machinery that already exists in the crate.
Dialect census on main
Responses:
| provider | evidence |
|---|---|
openai |
providers/openai/client.rs:154 (impl ResponsesProviderExt for OpenAIResponses), providers/openai/responses_api/mod.rs:1243 (RESPONSES_PATH = "/responses") |
xai |
providers/xai/client.rs:84, :86 (RESPONSES_PATH = "/v1/responses") |
chatgpt |
providers/chatgpt/mod.rs:229, :486, :617 (post("/responses")) |
copilot |
providers/copilot/mod.rs:838, :911 (hybrid: /responses + /chat/completions) |
OpenAI-compatible, Chat Completions only — impl OpenAICompatibleProvider, and completion_path() defaults to /chat/completions (providers/openai/completion/mod.rs:1777):
| provider | impl |
|---|---|
azure |
providers/azure.rs:412 |
deepseek |
providers/deepseek.rs:85 |
doubleword |
providers/doubleword/client.rs:66 |
groq |
providers/groq.rs:89 |
huggingface |
providers/huggingface/client.rs:192 |
hyperbolic |
providers/hyperbolic.rs:97 |
llamacpp |
providers/llamacpp/client.rs:186 |
minimax |
providers/minimax.rs:104 |
mira |
providers/mira.rs:76 |
mistral |
providers/mistral/client.rs:94 |
moonshot |
providers/moonshot.rs:141 |
openrouter |
providers/openrouter/completion.rs:1573 |
perplexity |
providers/perplexity.rs:63 |
together |
providers/together/client.rs:65 |
venice |
providers/venice/client.rs:132 |
xiaomimimo |
providers/xiaomimimo.rs:84 |
zai |
providers/zai.rs:79 |
Non-OpenAI dialects (out of scope for this request): anthropic (Messages), cohere, gemini, ollama (/api/chat, providers/ollama.rs:865), voyageai (embeddings).
Vendors that already ship /responses
| provider | endpoint | source |
|---|---|---|
| MiniMax | https://api.minimax.io/v1/responses |
MiniMax "Create Response" API reference — the OpenAI Responses API-compatible main endpoint, with a web_search server tool declared in tools |
| Moonshot / Kimi | https://api.moonshot.ai/v1/responses |
Kimi API overview — OpenAI Responses listed as one of the three supported API formats |
| Groq | https://api.groq.com/openai/v1/responses |
GroqDocs "Responses API" — streaming SSE, tools, MCP |
| OpenRouter | https://openrouter.ai/api/v1/responses |
OpenRouter Responses API (beta) — drop-in OpenAI Responses across routed models |
Vendors that do not currently ship /responses, and would therefore stay on Chat Completions:
- z.ai / Zhipu — only
/chat/completionstoday; there is an open request on the vendor side (zai-org/GLM-5#39). - Together AI —
/responsesis absent from its documented OpenAI-compatibility matrix.
Why it matters
Consumers that want Responses features (reasoning items, hosted tools, prompt-cache routing, store: false replay) currently have to construct the generic openai::Client against these vendors' base URLs. That bypasses the OpenAICompatibleProvider impl — the place where each provider's wire quirks are supposed to live — so every quirk has to be re-handled downstream:
- MiniMax echoes
top_p(object-shaped) and can return"tools": null, which the Responses decoder rejects (#2483), and loses reported usage underserde_json/arbitrary_precision(#2493). - MiniMax completes a
function_calloutput item before streaming that item'sfunction_call_arguments.delta, so anoutput_item.doneseen in isolation carries empty arguments.
Routing through a provider-native Responses client would keep those fixups in the provider impl (where #2483/#2493 already live) instead of in every consumer, and would keep provider identity/telemetry (PROVIDER_NAME) correct.
Existing machinery to build on
ResponsesProviderExt+GenericResponsesCompletionModelalready exist (providers/openai/responses_api/mod.rs:1227) and are implemented byOpenAIResponses,XAi, andChatGPT. The trait already exposesRESPONSES_PATH, so a vendor whose path differs from/responsesis a one-const override.impl_dual_dialect_provider!(providers/internal/anthropic_compatible.rs:85) already establishes the "one provider, two wire dialects" pattern — OpenAI-compatible plus Anthropic-compatible — for MiniMax, Moonshot, ZAi, and XiaomiMimo. A third dialect could follow the same shape, or dialect selection could become a client-level option.- Related: #2042 proposes this layer split directly ("Protocol adapter: OpenAI Chat Completions / OpenAI Responses / Anthropic Messages / …").
Caveats
- OpenRouter's endpoint is beta and stateless:
store: trueand a non-nullprevious_response_idare rejected with 400, so a client would have to replay the full transcript there. - Groq's Responses surface accepts function tools only (max 128) and
storelimited tofalse/null; a dialect switch should not assume full OpenAI feature parity.
Source: 0xPlaygrounds/rig