refactor(open-sse): add reasoningDelta helper, dedup thinking deltas (B4)

Centralize the reasoning_content delta shape (optional assistant role) used by
claude/gemini/kiro/codex/commandcode response translators. Keeps the cross-format
convention consistent for future translators. Output byte-for-byte identical;
golden + gate clean. Ollama left as-is (mutates existing delta object).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
decolua
2026-06-13 18:21:46 +07:00
parent 997860aa1f
commit 6597b81e5e
7 changed files with 18 additions and 12 deletions

View File

@@ -0,0 +1,6 @@
// Build OpenAI delta carrying reasoning_content (optional leading assistant role)
export function reasoningDelta(text, withRole = false) {
return withRole
? { role: "assistant", reasoning_content: text }
: { reasoning_content: text };
}

View File

@@ -2,6 +2,7 @@ import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
import { buildUsage } from "../helpers/usageHelper.js";
import { reasoningDelta } from "../helpers/reasoningHelper.js";
// Create OpenAI chunk helper
function createChunk(state, delta, finishReason = null) {
@@ -67,7 +68,7 @@ export function claudeToOpenAIResponse(chunk, state) {
if (delta?.type === "text_delta" && delta.text) {
results.push(createChunk(state, { content: delta.text }));
} else if (delta?.type === "thinking_delta" && delta.thinking) {
results.push(createChunk(state, { reasoning_content: delta.thinking }));
results.push(createChunk(state, reasoningDelta(delta.thinking)));
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
const toolCall = state.toolCalls.get(chunk.index);
if (toolCall) {

View File

@@ -18,6 +18,7 @@
import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
import { reasoningDelta } from "../helpers/reasoningHelper.js";
function ensureState(state, model) {
if (!state.responseId) {
@@ -96,9 +97,7 @@ export function convertCommandCodeToOpenAI(chunk, state) {
const text = event.text || "";
if (!text) break;
// Map reasoning to OpenAI "reasoning_content" field (used by deepseek-reasoner-style clients).
const delta = state.chunkIndex === 0
? { role: "assistant", reasoning_content: text }
: { reasoning_content: text };
const delta = reasoningDelta(text, state.chunkIndex === 0);
state.chunkIndex++;
out.push(makeChunk(state, delta));
break;

View File

@@ -2,6 +2,7 @@ import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
import { buildUsage } from "../helpers/usageHelper.js";
import { reasoningDelta } from "../helpers/reasoningHelper.js";
// Build chunk meta for current gemini state
function chunkMeta(state) {
@@ -42,7 +43,7 @@ export function geminiToOpenAIResponse(chunk, state) {
if (hasTextContent) {
results.push(buildChunk(
chunkMeta(state),
isThought ? { reasoning_content: part.text } : { content: part.text },
isThought ? reasoningDelta(part.text) : { content: part.text },
null
));
}
@@ -78,7 +79,7 @@ export function geminiToOpenAIResponse(chunk, state) {
if (part.text !== undefined && part.text !== "") {
results.push(buildChunk(
chunkMeta(state),
isThought ? { reasoning_content: part.text } : { content: part.text },
isThought ? reasoningDelta(part.text) : { content: part.text },
null
));
}

View File

@@ -6,6 +6,7 @@ import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
import { fallbackToolCallId } from "../helpers/toolCallHelper.js";
import { reasoningDelta } from "../helpers/reasoningHelper.js";
// Build chunk meta for current kiro state
function chunkMeta(state) {
@@ -94,10 +95,7 @@ export function convertKiroToOpenAI(chunk, state) {
: (reasoning.text || reasoning.content || data.content || "");
if (!content) return null;
const openaiChunk = buildChunk(chunkMeta(state), {
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
reasoning_content: content
}, null);
const openaiChunk = buildChunk(chunkMeta(state), reasoningDelta(content, state.chunkIndex === 0), null);
state.chunkIndex++;
return openaiChunk;

View File

@@ -7,6 +7,7 @@ import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
import { buildUsage } from "../helpers/usageHelper.js";
import { fallbackToolCallId } from "../helpers/toolCallHelper.js";
import { reasoningDelta } from "../helpers/reasoningHelper.js";
/**
* Translate OpenAI chunk to Responses API events
@@ -519,7 +520,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
if (!delta) return null;
return buildChunk(
{ id: state.chatId, created: state.created, model: state.model || "unknown" },
{ reasoning_content: delta }
reasoningDelta(delta)
);
}

File diff suppressed because one or more lines are too long