mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
fix(stream): normalize delta.reasoning to reasoning_content in SSE streaming
NVIDIA kimi-k2.5 (and potentially other providers) send reasoning tokens as `delta.reasoning` in SSE streaming chunks instead of the standard OpenAI `delta.reasoning_content` field. This caused reasoning content to be silently dropped during stream passthrough — clients received only the final answer with no reasoning separation. The non-streaming sanitizer (responseSanitizer.ts) already handled this alias, but the streaming pipeline did not. Fix applied in 4 locations: - stream.ts passthrough: normalize + force re-serialize sanitized chunk - stream.ts translate: accumulate reasoning from delta.reasoning - sseParser.ts: collect delta.reasoning in parseSSEToOpenAIResponse - streamPayloadCollector.ts: collect delta.reasoning in buildOpenAISummary
This commit is contained in:
@@ -52,6 +52,10 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
||||
reasoningParts.push(delta.reasoning_content);
|
||||
}
|
||||
// Normalize `reasoning` alias (NVIDIA kimi-k2.5 etc.)
|
||||
if (typeof delta.reasoning === "string" && delta.reasoning.length > 0 && !delta.reasoning_content) {
|
||||
reasoningParts.push(delta.reasoning);
|
||||
}
|
||||
|
||||
// T18: Accumulate tool calls correctly across streamed chunks
|
||||
if (delta.tool_calls) {
|
||||
|
||||
@@ -314,6 +314,12 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
|
||||
const delta = parsed.choices?.[0]?.delta;
|
||||
|
||||
// Normalize `reasoning` alias → `reasoning_content` (NVIDIA kimi-k2.5 etc.)
|
||||
if (delta?.reasoning && typeof delta.reasoning === "string" && !delta.reasoning_content) {
|
||||
delta.reasoning_content = delta.reasoning;
|
||||
delete delta.reasoning;
|
||||
}
|
||||
|
||||
// Extract <think> tags from streaming content
|
||||
if (delta?.content && typeof delta.content === "string") {
|
||||
const { content, thinking } = extractThinkingFromContent(delta.content);
|
||||
@@ -323,6 +329,14 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// If reasoning was normalized (reasoning → reasoning_content) or <think>
|
||||
// tags were extracted, force re-serialization so the client sees
|
||||
// the standard `reasoning_content` field instead of the raw provider line.
|
||||
if (delta?.reasoning_content && !injectedUsage) {
|
||||
output = `data: ${JSON.stringify(parsed)}\n`;
|
||||
injectedUsage = true;
|
||||
}
|
||||
|
||||
// T18: Track if we saw tool calls & accumulate for call log
|
||||
if (delta?.tool_calls && delta.tool_calls.length > 0) {
|
||||
passthroughHasToolCalls = true;
|
||||
@@ -483,6 +497,16 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
if (state?.accumulatedContent !== undefined) state.accumulatedContent += r;
|
||||
}
|
||||
}
|
||||
// Normalize `reasoning` alias → `reasoning_content` (NVIDIA kimi-k2.5 etc.)
|
||||
if (parsed.choices?.[0]?.delta?.reasoning && !parsed.choices?.[0]?.delta?.reasoning_content) {
|
||||
const r = parsed.choices[0].delta.reasoning;
|
||||
if (typeof r === "string") {
|
||||
parsed.choices[0].delta.reasoning_content = r;
|
||||
delete parsed.choices[0].delta.reasoning;
|
||||
totalContentLength += r.length;
|
||||
if (state?.accumulatedContent !== undefined) state.accumulatedContent += r;
|
||||
}
|
||||
}
|
||||
|
||||
// Gemini format - may have multiple parts
|
||||
if (parsed.candidates?.[0]?.content?.parts) {
|
||||
|
||||
@@ -157,6 +157,10 @@ function buildOpenAISummary(events: StructuredSSEEvent[], fallbackModel?: string
|
||||
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
||||
reasoningParts.push(delta.reasoning_content);
|
||||
}
|
||||
// Normalize `reasoning` alias (NVIDIA kimi-k2.5 etc.)
|
||||
if (typeof delta.reasoning === "string" && delta.reasoning.length > 0 && !delta.reasoning_content) {
|
||||
reasoningParts.push(delta.reasoning);
|
||||
}
|
||||
|
||||
if (Array.isArray(delta.tool_calls)) {
|
||||
for (const item of delta.tool_calls) {
|
||||
|
||||
Reference in New Issue
Block a user