Fix assistant-first provider history

Omit orphaned leading assistant messages from model history so Claude-compatible providers receive a user-first conversation. Add regression coverage for the initial UI greeting path.

Alessandro committed Aug 12, 2026 at 06:33 UTC 517a3cd90bb968314b1d0da336c286bf608e9b02
3 files changed +17
helpers/history.py
+2
@@ -724,6 +724,8 @@ def output_langchain(messages: list[OutputMessage]):
724 result.append(HumanMessage(content)) # type: ignore
725 # ensure message type alternation
726 result = group_messages_abab(result)
727 + while result and isinstance(result[0], AIMessage):
728 + result.pop(0)
729 return result
730
731
helpers/history.py.dox.md
+1
@@ -80,6 +80,7 @@
80 - Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
81 - `clear_responses_provider_state(agent)` removes the active provider continuation IDs after local history rewrites while preserving stored response ID lists for later cleanup.
82 - `Message.from_dict()` normalizes legacy AI Responses metadata through `LLMResult.metadata()` so loaded chats shed transient payloads while unrelated metadata and non-AI tool-result inputs remain intact.
83 +- `output_langchain()` removes leading assistant messages after grouping so provider histories always begin with a user turn; the WebUI greeting remains persisted and displayed but is not sent as an orphaned assistant message.
84 - Observed side-effect areas: filesystem writes, filesystem deletion, model calls, plugin state, settings/state persistence, secret handling.
85 - Imported dependency areas include: `abc`, `asyncio`, `collections`, `collections.abc`, `enum`, `helpers`, `json`, `langchain_core.messages`, `math`, `plugins._model_config.helpers.model_config`, `typing`, `uuid`.
86
tests/test_history.py new
+14
@@ -0,0 +1,14 @@
1 +from helpers.history import output_langchain
2 +from langchain_core.messages import AIMessage, HumanMessage
3 +
4 +
5 +def test_output_langchain_omits_leading_assistant_messages():
6 + messages = output_langchain(
7 + [
8 + {"ai": True, "content": "Welcome"},
9 + {"ai": False, "content": "Hello"},
10 + {"ai": True, "content": "Hi"},
11 + ]
12 + )
13 +
14 + assert messages == [HumanMessage("Hello"), AIMessage("Hi")]