fix(webui): preserve automation source on streamed replies
This commit is contained in:
@@ -1216,6 +1216,7 @@ class WebSocketChannel(BaseChannel):
|
||||
body,
|
||||
metadata=meta,
|
||||
phase="answer",
|
||||
include_source=True,
|
||||
)
|
||||
raw = json.dumps(body, ensure_ascii=False)
|
||||
if not conns:
|
||||
|
||||
@@ -51,6 +51,7 @@ from nanobot.webui.http_utils import (
|
||||
)
|
||||
from nanobot.webui.metadata import (
|
||||
WEBSOCKET_TURN_OWNER_METADATA_KEY,
|
||||
WEBUI_MESSAGE_SOURCE_METADATA_KEY,
|
||||
WEBUI_SYSTEM_COMMAND_TURN_PREFIX,
|
||||
WEBUI_TURN_METADATA_KEY,
|
||||
)
|
||||
@@ -1350,6 +1351,35 @@ async def test_send_delta_emits_delta_and_stream_end() -> None:
|
||||
assert "text" not in second
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_delta_preserves_webui_source_metadata() -> None:
|
||||
bus = MagicMock()
|
||||
channel = WebSocketChannel({"enabled": True, "allowFrom": ["*"], "streaming": True}, bus, gateway=_basic_handler(bus))
|
||||
mock_ws = AsyncMock()
|
||||
channel._attach(mock_ws, "chat-source-stream")
|
||||
source = {"kind": "cron", "label": "Repo check"}
|
||||
metadata = {WEBUI_MESSAGE_SOURCE_METADATA_KEY: source}
|
||||
|
||||
await channel.send_delta("chat-source-stream", "done", metadata=metadata, stream_id="sid")
|
||||
await channel.send_delta(
|
||||
"chat-source-stream",
|
||||
"",
|
||||
metadata=metadata,
|
||||
stream_id="sid",
|
||||
stream_end=True,
|
||||
)
|
||||
|
||||
first = json.loads(mock_ws.send.call_args_list[0][0][0])
|
||||
second = json.loads(mock_ws.send.call_args_list[1][0][0])
|
||||
assert first["event"] == "delta"
|
||||
assert first["source"] == source
|
||||
assert second["event"] == "stream_end"
|
||||
assert second["source"] == source
|
||||
lines = read_transcript_lines("websocket:chat-source-stream")
|
||||
assert lines[-2]["source"] == source
|
||||
assert lines[-1]["source"] == source
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_delta_marks_resuming_stream_end() -> None:
|
||||
bus = MagicMock()
|
||||
|
||||
@@ -2026,6 +2026,7 @@ def replay_transcript_to_ui_messages(
|
||||
continue
|
||||
close_activity_for_answer()
|
||||
turn_fields = _turn_fields(rec, "answer")
|
||||
source_fields = _source_fields(rec)
|
||||
adopted = find_active_placeholder(messages, turn_fields) if buffer_message_id is None else None
|
||||
if buffer_message_id is None:
|
||||
if adopted:
|
||||
@@ -2038,7 +2039,8 @@ def replay_transcript_to_ui_messages(
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"isStreaming": True,
|
||||
**_turn_fields(rec, "answer"),
|
||||
**turn_fields,
|
||||
**source_fields,
|
||||
"createdAt": _created_at_ms(rec, idx),
|
||||
},
|
||||
)
|
||||
@@ -2050,7 +2052,8 @@ def replay_transcript_to_ui_messages(
|
||||
**m,
|
||||
"content": combined,
|
||||
"isStreaming": True,
|
||||
**_turn_fields(rec, "answer"),
|
||||
**turn_fields,
|
||||
**source_fields,
|
||||
}
|
||||
break
|
||||
continue
|
||||
@@ -2062,6 +2065,8 @@ def replay_transcript_to_ui_messages(
|
||||
continue
|
||||
merge_next = rec.get("resuming") is True and rec.get("merge_next") is True
|
||||
final_text = rec.get("text")
|
||||
turn_fields = _turn_fields(rec, "answer")
|
||||
source_fields = _source_fields(rec)
|
||||
if isinstance(final_text, str):
|
||||
if buffer_message_id is None:
|
||||
buffer_message_id = _new_id("buf", idx)
|
||||
@@ -2071,7 +2076,8 @@ def replay_transcript_to_ui_messages(
|
||||
"role": "assistant",
|
||||
"content": final_text,
|
||||
"isStreaming": True,
|
||||
**_turn_fields(rec, "answer"),
|
||||
**turn_fields,
|
||||
**source_fields,
|
||||
"createdAt": _created_at_ms(rec, idx),
|
||||
},
|
||||
)
|
||||
@@ -2082,11 +2088,21 @@ def replay_transcript_to_ui_messages(
|
||||
**m,
|
||||
"content": final_text,
|
||||
"isStreaming": True,
|
||||
**_turn_fields(rec, "answer"),
|
||||
**turn_fields,
|
||||
**source_fields,
|
||||
}
|
||||
break
|
||||
if merge_next:
|
||||
buffer_parts = [final_text]
|
||||
elif source_fields and buffer_message_id is not None:
|
||||
for i, m in enumerate(messages):
|
||||
if m.get("id") == buffer_message_id:
|
||||
messages[i] = {
|
||||
**m,
|
||||
**turn_fields,
|
||||
**source_fields,
|
||||
}
|
||||
break
|
||||
if not merge_next:
|
||||
buffer_message_id = None
|
||||
buffer_parts = []
|
||||
|
||||
Reference in New Issue
Block a user