mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
This commit is contained in:
committed by
GitHub
parent
b15f343e67
commit
583d3ebe1d
1
changelog.d/fixes/7856-copilot-reasoning-text.md
Normal file
1
changelog.d/fixes/7856-copilot-reasoning-text.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): Copilot reasoning_text now surfaces in Claude-format non-stream responses, fixing spurious 502s (#7856)
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "../services/geminiThoughtSignatureStore.ts";
|
||||
import { normalizeOpenAICompatibleFinishReasonString } from "../utils/finishReason.ts";
|
||||
import { containsTextualToolCallMarker } from "../utils/textualToolCall.ts";
|
||||
import { getAnyReasoningValue } from "../utils/reasoningFields.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
@@ -576,27 +577,15 @@ export function translateNonStreamingResponse(
|
||||
|
||||
/**
|
||||
* Resolve reasoning/thinking text off a non-streaming OpenAI-format message object.
|
||||
* Checks DeepSeek-style `reasoning_content`, then the OpenRouter/StepFun aliases
|
||||
* `reasoning` and `reasoning_details[]` (array of { text | content }), mirroring the
|
||||
* streaming translator's fallback chain in open-sse/translator/response/openai-to-claude.ts.
|
||||
* Delegates to the shared reasoning-field resolver (`open-sse/utils/reasoningFields.ts`)
|
||||
* so every reasoning alias — DeepSeek-style `reasoning_content`, the OpenRouter/StepFun
|
||||
* `reasoning` string, GitHub Copilot's `reasoning_text`, `thinking`/`thought`, and
|
||||
* `reasoning_details[]` (array of { text | content }) — is read from one place instead
|
||||
* of a divergent local copy. Mirrors the streaming translator's fallback chain in
|
||||
* open-sse/translator/response/openai-to-claude.ts.
|
||||
*/
|
||||
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 "";
|
||||
return getAnyReasoningValue(messageObj);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { translateNonStreamingResponse } from "../../open-sse/handlers/responseTranslator.ts";
|
||||
import { detectMalformedNonStream } from "../../open-sse/utils/diagnostics.ts";
|
||||
|
||||
// GitHub Copilot (github/gemini-3.x models) returns reasoning in `message.reasoning_text`,
|
||||
// with `content` left empty. Before the fix, resolveReasoningText() in responseTranslator.ts
|
||||
// only checked reasoning_content / reasoning / reasoning_details[] and missed reasoning_text,
|
||||
// so the Claude-format translation dropped the reasoning entirely and emitted a bare
|
||||
// "(empty response)" text block, which detectMalformedNonStream() then rejects (-> 502).
|
||||
const copilotStyleResponse = {
|
||||
id: "chatcmpl-copilot-7856",
|
||||
object: "chat.completion",
|
||||
created: 1783700000,
|
||||
model: "github/gemini-3-pro",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
finish_reason: "stop",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: "",
|
||||
reasoning_text: "Let me think about the user's request before answering.",
|
||||
},
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 12, completion_tokens: 8, total_tokens: 20 },
|
||||
};
|
||||
|
||||
test("#7856 /v1/messages non-stream translation surfaces Copilot reasoning_text as a thinking block", () => {
|
||||
const translated = translateNonStreamingResponse(copilotStyleResponse, "openai", "claude", null) as {
|
||||
content: Array<{ type: string; thinking?: string; text?: string }>;
|
||||
};
|
||||
|
||||
const thinkingBlock = translated.content.find((block) => block.type === "thinking");
|
||||
assert.ok(thinkingBlock, "expected a thinking block carrying the Copilot reasoning_text");
|
||||
assert.equal(thinkingBlock?.thinking, "Let me think about the user's request before answering.");
|
||||
});
|
||||
|
||||
test("#7856 /v1/messages non-stream translation of a Copilot reasoning_text turn is not flagged malformed", () => {
|
||||
const translated = translateNonStreamingResponse(copilotStyleResponse, "openai", "claude", null);
|
||||
const malformedReason = detectMalformedNonStream(translated);
|
||||
assert.equal(malformedReason, null);
|
||||
});
|
||||
Reference in New Issue
Block a user