fix(openviking): preserve structured sync attribution
This commit is contained in:
parent
c7b7f92ec1
commit
d7cd0bc086
7 changed files with 210 additions and 23 deletions
25
tests/agent/test_message_content.py
Normal file
25
tests/agent/test_message_content.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from agent.message_content import flatten_message_text
|
||||
|
||||
|
||||
def test_flatten_message_text_accepts_chat_and_responses_text_parts():
|
||||
content = [
|
||||
{"type": "text", "text": "chat text"},
|
||||
{"type": "input_text", "text": "user text"},
|
||||
{"type": "output_text", "text": "assistant text"},
|
||||
{"type": "summary_text", "text": "summary text"},
|
||||
]
|
||||
|
||||
assert flatten_message_text(content) == "chat text\nuser text\nassistant text\nsummary text"
|
||||
|
||||
|
||||
def test_flatten_message_text_accepts_object_parts():
|
||||
content = [
|
||||
SimpleNamespace(type="output_text", text="object text"),
|
||||
{"content": "legacy content"},
|
||||
]
|
||||
|
||||
assert flatten_message_text(content) == "object text\nlegacy content"
|
||||
|
|
@ -330,7 +330,7 @@ class TestOpenVikingTurnConversion:
|
|||
|
||||
batch = OpenVikingMemoryProvider._messages_to_openviking_batch(turn)
|
||||
|
||||
assert [message["role"] for message in batch] == ["user", "assistant", "user", "assistant"]
|
||||
assert [message["role"] for message in batch] == ["user", "assistant", "assistant", "assistant"]
|
||||
assert batch[0]["parts"] == [
|
||||
{"type": "text", "text": "Please inspect the repository for assemble hooks."}
|
||||
]
|
||||
|
|
@ -378,6 +378,7 @@ class TestOpenVikingTurnConversion:
|
|||
|
||||
batch = OpenVikingMemoryProvider._messages_to_openviking_batch(turn)
|
||||
|
||||
assert batch[1]["role"] == "assistant"
|
||||
assert batch[1]["parts"] == [
|
||||
{
|
||||
"type": "tool",
|
||||
|
|
@ -453,7 +454,7 @@ class TestOpenVikingTurnConversion:
|
|||
|
||||
batch = OpenVikingMemoryProvider._messages_to_openviking_batch(turn)
|
||||
|
||||
assert [message["role"] for message in batch] == ["user", "user", "assistant"]
|
||||
assert [message["role"] for message in batch] == ["user", "assistant", "assistant"]
|
||||
assert batch[1]["parts"] == [
|
||||
{
|
||||
"type": "tool",
|
||||
|
|
@ -523,7 +524,7 @@ class TestOpenVikingTurnConversion:
|
|||
|
||||
batch = OpenVikingMemoryProvider._messages_to_openviking_batch(turn)
|
||||
|
||||
assert [message["role"] for message in batch] == ["user", "user", "assistant"]
|
||||
assert [message["role"] for message in batch] == ["user", "assistant", "assistant"]
|
||||
assert batch[1]["parts"] == [
|
||||
{
|
||||
"type": "tool",
|
||||
|
|
@ -538,6 +539,35 @@ class TestOpenVikingTurnConversion:
|
|||
assert recall_tool_name not in batch_text
|
||||
assert "Old OpenViking memory content" not in batch_text
|
||||
|
||||
def test_messages_to_openviking_batch_preserves_responses_text_parts(self):
|
||||
turn = [
|
||||
{"role": "user", "content": [{"type": "input_text", "text": "hello"}]},
|
||||
{"role": "assistant", "content": [{"type": "output_text", "text": "answer"}]},
|
||||
]
|
||||
|
||||
batch = OpenVikingMemoryProvider._messages_to_openviking_batch(turn)
|
||||
|
||||
assert batch == [
|
||||
{"role": "user", "parts": [{"type": "text", "text": "hello"}]},
|
||||
{"role": "assistant", "parts": [{"type": "text", "text": "answer"}]},
|
||||
]
|
||||
|
||||
def test_messages_to_openviking_batch_adds_assistant_peer_id_when_requested(self):
|
||||
turn = [
|
||||
{"role": "user", "content": "hello"},
|
||||
{"role": "assistant", "content": "answer"},
|
||||
]
|
||||
|
||||
batch = OpenVikingMemoryProvider._messages_to_openviking_batch(
|
||||
turn,
|
||||
assistant_peer_id="hermes",
|
||||
)
|
||||
|
||||
assert batch == [
|
||||
{"role": "user", "parts": [{"type": "text", "text": "hello"}]},
|
||||
{"role": "assistant", "parts": [{"type": "text", "text": "answer"}], "peer_id": "hermes"},
|
||||
]
|
||||
|
||||
|
||||
class TestOpenVikingRead:
|
||||
def test_overview_read_normalizes_uri_and_unwraps_result(self):
|
||||
|
|
|
|||
|
|
@ -2195,6 +2195,78 @@ def test_sync_turn_retries_batch_write_with_fresh_client():
|
|||
)]
|
||||
|
||||
|
||||
def test_sync_turn_structured_messages_include_assistant_peer_id():
|
||||
provider = OpenVikingMemoryProvider()
|
||||
provider._client = MagicMock()
|
||||
provider._endpoint = "http://test"
|
||||
provider._api_key = ""
|
||||
provider._account = "acct"
|
||||
provider._user = "usr"
|
||||
provider._agent = "hermes"
|
||||
provider._session_id = "sid-structured"
|
||||
|
||||
captured = []
|
||||
|
||||
class StubClient:
|
||||
def __init__(self, *a, **kw):
|
||||
pass
|
||||
|
||||
def post(self, path, payload=None, **kwargs):
|
||||
captured.append((path, payload))
|
||||
return {}
|
||||
|
||||
import plugins.memory.openviking as _mod
|
||||
|
||||
real_client_cls = _mod._VikingClient
|
||||
_mod._VikingClient = StubClient
|
||||
messages = [
|
||||
{"role": "user", "content": [{"type": "input_text", "text": "u"}]},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Looking.",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call-1",
|
||||
"type": "function",
|
||||
"function": {"name": "shell_command", "arguments": json.dumps({"cmd": "pwd"})},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call-1", "name": "shell_command", "content": "ok"},
|
||||
{"role": "assistant", "content": [{"type": "output_text", "text": "a"}]},
|
||||
]
|
||||
try:
|
||||
provider.sync_turn("u", "a", messages=messages)
|
||||
assert provider._drain_writers("sid-structured", timeout=2.0)
|
||||
finally:
|
||||
_mod._VikingClient = real_client_cls
|
||||
|
||||
assert captured == [(
|
||||
"/api/v1/sessions/sid-structured/messages/batch",
|
||||
{
|
||||
"messages": [
|
||||
{"role": "user", "parts": [{"type": "text", "text": "u"}]},
|
||||
{"role": "assistant", "parts": [{"type": "text", "text": "Looking."}], "peer_id": "hermes"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"parts": [
|
||||
{
|
||||
"type": "tool",
|
||||
"tool_id": "call-1",
|
||||
"tool_name": "shell_command",
|
||||
"tool_input": {"cmd": "pwd"},
|
||||
"tool_output": "ok",
|
||||
"tool_status": "completed",
|
||||
}
|
||||
],
|
||||
"peer_id": "hermes",
|
||||
},
|
||||
{"role": "assistant", "parts": [{"type": "text", "text": "a"}], "peer_id": "hermes"},
|
||||
]
|
||||
},
|
||||
)]
|
||||
|
||||
|
||||
def test_sync_turn_noop_when_session_id_blank():
|
||||
provider = OpenVikingMemoryProvider()
|
||||
provider._client = MagicMock()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Verifies that:
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -148,6 +148,17 @@ class TestRunConversationCodexPath:
|
|||
and m.get("content") == "echo: hello"]
|
||||
assert final, f"expected final assistant message in {msgs}"
|
||||
|
||||
def test_projected_messages_are_synced_to_external_memory(self, fake_session):
|
||||
agent = _make_codex_agent()
|
||||
agent._memory_manager = MagicMock()
|
||||
agent._memory_manager.build_system_prompt.return_value = ""
|
||||
|
||||
with patch.object(agent, "_spawn_background_review", return_value=None):
|
||||
result = agent.run_conversation("hello")
|
||||
|
||||
agent._memory_manager.sync_all.assert_called_once()
|
||||
assert agent._memory_manager.sync_all.call_args.kwargs["messages"] == result["messages"]
|
||||
|
||||
def test_nudge_counters_tick(self, fake_session):
|
||||
"""The skill nudge counter must accumulate tool_iterations across
|
||||
turns. The memory nudge counter is gated on memory being configured
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue