fix(claude-to-openai): handle OpenAI-format responses in non-streaming path
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>
This commit is contained in:
@@ -76,7 +76,12 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
// missing/null (e.g. M3 with max_tokens:1 spends the budget on thinking
|
||||
// and returns `content: null`). Returning the raw body would leave the
|
||||
// OpenAI client without a `choices` array and surface as a UI test error.
|
||||
if (responseBody.content && !Array.isArray(responseBody.content)) return responseBody;
|
||||
// Early return if the response is already in OpenAI format (has choices array)
|
||||
// or if it has content as a non-array value (likely a different non-Claude format).
|
||||
// Some providers (e.g. xiaomi-tokenplan) return OpenAI-format responses even when
|
||||
// the request was translated to Claude format — the targetFormat is Claude but the
|
||||
// actual response is OpenAI-native and needs no further translation.
|
||||
if (responseBody.choices || (responseBody.content && !Array.isArray(responseBody.content))) return responseBody;
|
||||
|
||||
let textContent = "", thinkingContent = "";
|
||||
const toolCalls = [];
|
||||
@@ -193,11 +198,14 @@ export async function handleNonStreamingResponse({ providerResponse, provider, m
|
||||
translatedResponse.usage = filterUsageForFormat(addBufferToUsage(translatedResponse.usage), sourceFormat);
|
||||
}
|
||||
|
||||
// Strip reasoning_content — some clients (e.g. Firecrawl AI SDK) have JSON parsers that
|
||||
// break on this non-standard field, even though OpenAI allows it in extensions.
|
||||
// Strip reasoning_content only when content is non-empty.
|
||||
// When content is empty (e.g. thinking models that used all tokens for reasoning),
|
||||
// reasoning_content is the only useful output and must be preserved.
|
||||
if (translatedResponse?.choices) {
|
||||
for (const choice of translatedResponse.choices) {
|
||||
if (choice?.message) delete choice.message.reasoning_content;
|
||||
if (choice?.message?.reasoning_content && choice.message.content) {
|
||||
delete choice.message.reasoning_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user