Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/quality/file-size-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@
"open-sse/translator/response/gemini-to-openai.ts": 821,
"_rebaseline_2026_07_22_7936_namespace_roundtrip": "#7936 (@RCrushMe, Responses-Chat namespace round-trip identity seam) own growth: open-sse/translator/response/openai-responses.ts 1092->1125 (+33) and open-sse/utils/stream.ts 2814->2869 (+55) — threading the namespace-identity seam through the Responses↔Chat translation + stream paths so tool-call namespaces survive the round-trip. Cohesive translation/stream wiring at existing chokepoints, frozen at new size.",
"_rebaseline_2026_07_22_8210_openrouter_midstream_error": "PR #8210 (hartmark, fix/openrouter-midstream-error-surfacing) own growth: open-sse/translator/response/openai-responses.ts 1137->1163 (+26) measured on the merged tip (release 1137 + this PR own growth). Adds a single new branch inside openaiToOpenAIResponsesResponse() that detects an OpenRouter-style mid-stream aggregator error (HTTP 200 SSE chunk with empty choices + a top-level error object) and surfaces it as state.upstreamError instead of silently falling through to the no-op/awaitingTrailingUsage path, which previously masked the failure as a false empty-success completion and skipped combo fallback. Irreducible call-site addition at the existing chunk-dispatch chokepoint (mirrors the Gemini-to-OpenAI translator's #4177 precedent for the same class of upstream error surfacing). Note: this baseline entry does NOT cover the separate pre-existing +11 drift already on the release tip from #8081/#8162 (1125->1136, unrelated reasoning-placeholder-stripping fix merged after this PR branched) — that drift belongs to the maintainer's rebaseline, not this PR.",
"open-sse/translator/response/openai-responses.ts": 1163,
"_rebaseline_2026_07_24_responses_toolcalls_log_summary": "hartmark, fix/responses-tool-calls-log-summary own growth: open-sse/translator/response/openai-responses.ts 1163->1174 (+11). closeToolCall() now also writes the completed tool call into the shared state.toolCalls Map (already populated by the openai-to-claude / claude-to-openai / gemini-to-openai response translators) so stream.ts's completion-log summary builder (which reads state.toolCalls, not this translator's own funcCallIds/funcNames/funcArgsBuf bookkeeping) reports finish_reason \"tool_calls\" and message.tool_calls for openai->openai-responses translated streams instead of always logging \"stop\" with no tool_calls — the actual client-facing SSE events were already correct; only the persisted call-log summary was wrong. Irreducible call-site addition at the existing tool-call-close chokepoint. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts.",
"open-sse/translator/response/openai-responses.ts": 1174,
"open-sse/utils/cursorAgentProtobuf.ts": 1521,
"_rebaseline_2026_07_23_8143_empty_catch_logging": "#8143 (@chirag127) own growth: open-sse/utils/stream.ts 2869->2887 (+18) — replacing empty catch blocks in the SSE stream subsystem with console.debug logging (Rule #6 silent-swallow fix, issues #8138-#8142). Cohesive logging additions at the existing catch chokepoints, not extractable; frozen at new size. Covered by tests/unit/stream-handler-catch-logging-8143.test.ts.",
"open-sse/utils/stream.ts": 2887,
Expand Down
11 changes: 11 additions & 0 deletions open-sse/translator/response/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,17 @@ function closeToolCall(state, emit, idx, recordAsCompleted = true) {
// superseded-call eviction where a new call replaced this one at the same index).
if (recordAsCompleted) {
recordCompletedItem(state, normalizedIndex, funcItem);
// Mirror into the shared state.toolCalls map (populated by the other response
// translators) so stream.ts's completion-log summary reports finish_reason
// "tool_calls" and message.tool_calls instead of "stop" with no tool calls.
if (state.toolCalls instanceof Map) {
state.toolCalls.set(idx, {
id: callId,
index: normalizedIndex,
type: isCustomTool ? "custom_tool_call" : "function",
function: { name: funcItem.name, arguments: args },
});
}
}

state.funcItemDone[idx] = true;
Expand Down
51 changes: 51 additions & 0 deletions tests/unit/translator-resp-openai-responses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,57 @@ test("OpenAI -> Responses: emits lifecycle, reasoning, text, tool calls and comp
assert.equal(completed.data.response.usage.input_tokens_details.cached_tokens, 2);
});

// Regression guard for the OpenRouter/nemotron "reasoning_content + tool_calls in the
// final chunk" case reported via the /dashboard/logs/timeline UI: the SSE events sent to
// the client were always correct, but stream.ts's completion-log summary builder
// (open-sse/utils/stream.ts) reads the shared `state.toolCalls` Map — populated by the
// openai-to-claude / claude-to-openai / gemini-to-openai translators — to report
// finish_reason and message.tool_calls in the persisted call-log. This translator alone
// tracked tool calls in its own funcCallIds/funcNames/funcArgsBuf bookkeeping without
// ever writing to the shared Map, so every openai->openai-responses translated stream
// with a tool call was logged as finish_reason "stop" with no tool_calls, even though the
// client received the tool call correctly.
test("OpenAI -> Responses: closing a tool call also records it in the shared state.toolCalls map", () => {
const state = initState(FORMATS.OPENAI_RESPONSES);
const chunks = [
{
id: "chatcmpl-4",
model: "nvidia/nemotron",
choices: [{ index: 0, delta: { reasoning_content: "thinking" }, finish_reason: null }],
},
{
id: "chatcmpl-4",
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: 0,
id: "call-abc123",
type: "function",
function: { name: "openclaw", arguments: '{"message":"hi"}' },
},
],
},
finish_reason: "tool_calls",
},
],
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
},
];

for (const chunk of chunks) {
openaiToOpenAIResponsesResponse(chunk, state);
}

assert.equal(state.toolCalls.size, 1, "state.toolCalls should carry the completed tool call");
const recorded = [...state.toolCalls.values()][0];
assert.equal(recorded.id, "call-abc123");
assert.equal(recorded.function.name, "openclaw");
assert.equal(recorded.function.arguments, '{"message":"hi"}');
});

test("OpenAI -> Responses: flush on null closes text content and emits response.completed", () => {
const events = collectEvents([
{
Expand Down
Loading