Some providers (e.g. xiaomi-tokenplan -claude models) return OpenAI-format
responses even when request was translated to Claude. Early-return now detects
choices[]. Also strip reasoning_content only when content is non-empty so
thinking models keep their only output.
Closes#1836
Co-authored-by: Cursor <cursoragent@cursor.com>
Gom block SSE headers + CORS lặp ở streamingHandler + responsesHandler
vào sseConstants.SSE_HEADERS_CORS. Codex format-routing giữ nguyên
(logic-driven theo kim chỉ nam DATA/LOGIC docs 07).
Co-authored-by: Cursor <cursoragent@cursor.com>
sseToJsonHandler.js unconditionally deleted reasoning_content from all
non-streaming responses (added for Firecrawl SDK compatibility). This
breaks thinking models (Qwen3.5, Claude extended thinking, etc.) where
the model may use all tokens for reasoning, leaving content empty.
When reasoning_content is stripped in that case, the response appears
completely empty to the client.
Fix: only strip reasoning_content when the response also has non-empty
content, so that reasoning output is preserved when it is the only
useful output.
Co-authored-by: Agent Zero <agent@agent-zero.local>
Some upstream providers (e.g. Antigravity) return non-standard finish_reason
values like 'other' instead of the OpenAI-standard 'tool_calls' when the
model invokes tools. This causes downstream consumers (e.g. OpenClaw) to
fail to execute tool calls, breaking agentic sub-agent workflows.
Changes:
- nonStreamingHandler: post-translation guard that normalizes finish_reason
to 'tool_calls' when message.tool_calls is present
- sseToJsonHandler: accumulate tool_calls from streaming deltas in
parseSSEToOpenAIResponse; extract function_call items from Responses API
output in handleForcedSSEToJson
- openai-responses translator: use toolCallIndex to choose between
'tool_calls' and 'stop' in flush and response.completed events
Tested: 7 scenarios (non-stream text, single/multiple tool calls, stream
text/tool calls, multi-turn tool conversation, tools present but unused)
Root cause: Codex/OpenAI Responses streams multiple alternating reasoning and
message output items. The first message block often has empty output_text; the
visible answer lives in a later message. Previous code used output.find() which
always picked the first (empty) message block.
Fix: walk message items from end and use the last message whose extracted text
is non-empty; fall back to final message if all are empty.
Note: Removed debug logging code from original PR #383 to keep implementation clean.
Co-authored-by: lokinh <locnh@uniultra.xyz>
Made-with: Cursor
- Respect Accept: application/json header to return non-streaming JSON
instead of SSE, fixing AI SDK generateObject/generateText compatibility
- Strip markdown code block markers (```json...```) from Claude
non-streaming responses to prevent JSON parse errors
Cherry-picked and adapted from PR #290 by @rothnic
https://github.com/decolua/9router/pull/290
Made-with: Cursor