mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-04 05:34:04 +00:00
* fix(responses): drop orphaned assistant msg_* id when reasoning is dropped (#88019) When an Azure/OpenAI Responses session falls back to a non-Responses model and later resumes a Responses model, sanitizeSessionHistory drops the replayable reasoning (rs_*) item via downgradeOpenAIReasoningBlocks. The paired assistant text block still carried its textSignature (the msg_* id), so the transport replayed an assistant message item referencing msg_* with no accompanying rs_* reasoning item. Azure Responses then rejected the next turn with: 400 Item 'msg_...' provided without its required 'reasoning' item: 'rs_...' permanently poisoning the session. Fix: - downgradeOpenAIReasoningBlocks now strips the textSignature from a turn's text blocks whenever it drops a replayable reasoning item, so the msg_* id and its rs_* reasoning are removed together. The transport then falls back to a synthetic, unpaired id that Azure accepts. - Because the synthetic fallback id is derived from the per-message msgIndex, multiple id-less text blocks in one assistant turn (e.g. commentary + final_answer) would collide on the same id. Make the fallback unique per text block in both Responses conversion sites (openai-transport-stream.ts and the shared llm provider openai-responses-shared.ts). Tests: - sanitize-session-history: model-switch path drops the paired msg_* id. - embedded-agent-helpers: downgrade strips paired text signature(s). - reasoning-replay: multiple id-less text blocks get distinct item ids. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(responses): preserve phase metadata and guard malformed blocks (#88019) Address PR review feedback on the orphaned msg_* replay fix: - Preserve Responses phase metadata: dropping the paired msg_* id when its rs_* reasoning is removed previously stripped the entire textSignature, which also discarded the phase (commentary/final_answer). Phased text now keeps a phase-only signature ({v:1,phase}) so commentary is not replayed as user-visible output. Both parseTextSignature copies (shared provider and embedded transport) now accept id-less phase-only signatures and fall back to a synthetic id while preserving the phase. - Guard malformed content blocks: the post-drop map no longer dereferences contentBlock.type unconditionally, so a corrupted transcript with a null/primitive block can still sanitize through a model switch. Tests: - sanitize-session-history: phase metadata is preserved while the paired id is dropped on a model switch. - reasoning-replay: id-less phase-only signatures get distinct synthetic ids and retain their phase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>