Skip to content

fix(api): recognize OpenRouter reasoning/reasoning_details in non-streaming OpenAI-to-Claude conversion (#6623) - #6887

Merged
diegosouzapw merged 2 commits into
release/v3.8.47from
fix/6623-mimo-502-messages
Jul 12, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.47from
fix/6623-mimo-502-messages

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #6623

Root cause

oc/mimo-v2.5-free (opencode zen gateway, OpenRouter-backed) is a reasoning model whose thinking text arrives under OpenRouter-native message.reasoning / message.reasoning_details[] fields, not DeepSeek's message.reasoning_content. The non-streaming OpenAI-to-Claude converter (convertOpenAINonStreamingToClaude in open-sse/handlers/responseTranslator.ts) only recognized reasoning_content, so on a truncated (finish_reason: length, content: null) reasoning-only turn it dropped the reasoning entirely and emitted an internal (empty response) placeholder. detectMalformedNonStream (open-sse/utils/diagnostics.ts, added for #5108) correctly flags that placeholder as no real output, which chatCore.ts turns into an HTTP 502.

/v1/chat/completions never hits this because source format equals target format there, so translation is skipped and the raw body passes through untouched — which is why it appeared to work on that route.

The streaming per-chunk translator (open-sse/translator/response/openai-to-claude.ts) already had this reasoning / reasoning_details[] fallback (landed via PR #2951 for StepFun/OpenRouter compatibility) — it was never ported to the non-streaming converter. This fix adds a resolveReasoningText() helper mirroring that same fallback chain (reasoning_content → reasoning → reasoning_details[].text|content) to responseTranslator.ts.

Regression test

tests/unit/issue-6623-opencode-mimo-reasoning-details-nonstream.test.ts — reproduces the exact OpenRouter-style payload captured live from opencode.ai/zen/v1/chat/completions (no auth required) and asserts the /v1/messages (openai→claude) translation is no longer flagged empty_choices by detectMalformedNonStream.

  • RED before fix: AssertionError: 'empty_choices' !== null
  • GREEN after fix: all 3 assertions pass

Gates run (all green)

  • node scripts/check/check-file-size.mjs — OK
  • node scripts/check/check-complexity.mjs — OK (2050 ≤ baseline 2054)
  • node scripts/check/check-cognitive-complexity.mjs — OK (885 = baseline 885)
  • node scripts/check/check-changelog-integrity.mjs — OK
  • npm run typecheck:core — clean, exit 0
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json open-sse/handlers/responseTranslator.ts tests/unit/issue-6623-opencode-mimo-reasoning-details-nonstream.test.ts — exit 0
  • Existing tests touching this area (plan3-p0.test.ts, t19-codex-responses-empty-content.test.ts, diagnostics.test.ts) — 71/71 pass, no regressions

Note: unrelated to #1635 (different failure — Codex Responses API missing input/previous_response_id), which the Kilo-bot duplicate flag incorrectly linked.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for extracting OpenRouter reasoning and reasoning details during non-streaming OpenAI-to-Claude translation, along with corresponding unit tests. The review feedback correctly identifies a robustness issue in the reasoning extraction helper where non-string truthy values could prematurely halt the fallback chain, and suggests a cleaner implementation using safe string conversions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +569 to +586
function resolveReasoningText(messageObj: JsonRecord): string {
if (messageObj.reasoning_content) {
return toString(messageObj.reasoning_content);
}
if (typeof messageObj.reasoning === "string" && messageObj.reasoning) {
return messageObj.reasoning;
}
if (Array.isArray(messageObj.reasoning_details)) {
const parts: string[] = [];
for (const detail of messageObj.reasoning_details) {
const detailObj = toRecord(detail);
const text = detailObj.text ?? detailObj.content;
if (typeof text === "string" && text) parts.push(text);
}
return parts.join("");
}
return "";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of resolveReasoningText has a potential robustness issue. If messageObj.reasoning_content is a truthy non-string value (such as a number or an object), if (messageObj.reasoning_content) will evaluate to true, but toString will return "". This causes the function to return "" immediately, preventing the fallback to reasoning or reasoning_details from being evaluated.

We can make this more robust and consistent by using the existing toString helper to safely extract and validate the string values before checking their truthiness. This also simplifies the type checks and null/undefined guards for reasoning and the individual details.

function resolveReasoningText(messageObj: JsonRecord): string {
  const reasoningContent = toString(messageObj.reasoning_content);
  if (reasoningContent) {
    return reasoningContent;
  }
  const reasoning = toString(messageObj.reasoning);
  if (reasoning) {
    return reasoning;
  }
  if (Array.isArray(messageObj.reasoning_details)) {
    const parts: string[] = [];
    for (const detail of messageObj.reasoning_details) {
      const detailObj = toRecord(detail);
      const text = toString(detailObj.text ?? detailObj.content);
      if (text) parts.push(text);
    }
    return parts.join("");
  }
  return "";
}

@diegosouzapw
diegosouzapw merged commit e3e474d into release/v3.8.47 Jul 12, 2026
3 checks passed
@diegosouzapw
diegosouzapw deleted the fix/6623-mimo-502-messages branch July 19, 2026 21:00
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant