fix(chat): persist reasoning for Responses stream and WS final snapshot

- Parse reasoning_summary_text (and related) SSE events in OpenAI Responses
  streaming; forward deltas to on_token and return reasoning_content on
  LLMResponse so thinking-mode rows get event_payload in the store.
- When emitting chat/final from turn_runner, include event_type/event_payload
  and merge reasoning_content from earlier assistant rows in the same
  turn_uuid so the collapsed reasoning fold matches what streamed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-12 20:18:15 +08:00
parent 46ebfeb54f
commit a3365b77fa
3 changed files with 164 additions and 11 deletions

View file

@ -25,12 +25,46 @@ def test_openai_responses_stream_parses_text_deltas_and_final_response_tool_call
},
]
buf: list[str] = []
text, tool_calls, final_resp = parse_openai_responses_stream_events(events, on_token=buf.append)
text, tool_calls, final_resp, reasoning_text = parse_openai_responses_stream_events(events, on_token=buf.append)
assert text == "Hello"
assert "".join(buf) == "Hello"
assert reasoning_text == ""
assert final_resp and final_resp.get("id") == "resp_1"
assert len(tool_calls) == 1
assert tool_calls[0].id == "call_1"
assert tool_calls[0].name == "query_route"
assert tool_calls[0].arguments.get("destination") == "1.1.1.1"
def test_openai_responses_stream_emits_reasoning_summary_deltas() -> None:
events = [
{"type": "response.reasoning_summary_text.delta", "delta": "step "},
{"type": "response.reasoning_summary_text.delta", "delta": "one"},
{"type": "response.output_text.delta", "delta": "Hi"},
{"type": "response.completed", "response": {"id": "resp_2", "output_text": "Hi"}},
]
buf: list[str] = []
text, tool_calls, final_resp, reasoning_text = parse_openai_responses_stream_events(events, on_token=buf.append)
assert text == "Hi"
assert reasoning_text == "step one"
assert "".join(buf) == "step oneHi"
assert final_resp and final_resp.get("id") == "resp_2"
assert tool_calls == []
def test_openai_responses_stream_reasoning_fallback_from_completed_output() -> None:
events = [
{"type": "response.output_text.delta", "delta": "Z"},
{
"type": "response.completed",
"response": {
"id": "resp_3",
"output_text": "Z",
"output": [{"type": "reasoning", "summary": "silent plan"}],
},
},
]
text, _, _, reasoning_text = parse_openai_responses_stream_events(events)
assert text == "Z"
assert reasoning_text == "silent plan"