This commit was merged in pull request #734.
This commit is contained in:
@@ -65,6 +65,57 @@ def produce_bark(agent_id: str, text: str, reply_to: str = None, style: str = "s
|
||||
}
|
||||
|
||||
|
||||
def produce_thought(
|
||||
agent_id: str, thought_text: str, thought_id: int, chain_id: str = None
|
||||
) -> dict:
|
||||
"""Format a thinking engine thought as a Matrix thought message.
|
||||
|
||||
Thoughts appear as subtle floating text in the 3D world, streaming from
|
||||
Timmy's thinking engine (/thinking/api). This function wraps thoughts in
|
||||
Matrix protocol format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
agent_id:
|
||||
Unique identifier for the agent (e.g. ``"timmy"``).
|
||||
thought_text:
|
||||
The thought text to display. Truncated to 500 characters.
|
||||
thought_id:
|
||||
Unique identifier for this thought (sequence number).
|
||||
chain_id:
|
||||
Optional chain identifier grouping related thoughts.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Thought message with keys ``type``, ``agent_id``, ``data`` (containing
|
||||
``text``, ``thought_id``, ``chain_id``), and ``ts``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> produce_thought("timmy", "Considering the options...", 42, "chain-123")
|
||||
{
|
||||
"type": "thought",
|
||||
"agent_id": "timmy",
|
||||
"data": {"text": "Considering the options...", "thought_id": 42, "chain_id": "chain-123"},
|
||||
"ts": 1742529600,
|
||||
}
|
||||
"""
|
||||
# Truncate text to 500 characters (thoughts can be longer than barks)
|
||||
truncated_text = thought_text[:500] if thought_text else ""
|
||||
|
||||
return {
|
||||
"type": "thought",
|
||||
"agent_id": agent_id,
|
||||
"data": {
|
||||
"text": truncated_text,
|
||||
"thought_id": thought_id,
|
||||
"chain_id": chain_id,
|
||||
},
|
||||
"ts": int(time.time()),
|
||||
}
|
||||
|
||||
|
||||
def serialize_presence(presence: dict) -> dict:
|
||||
"""Transform an ADR-023 presence dict into the world-state API shape.
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from infrastructure.presence import produce_agent_state, produce_bark, serialize_presence
|
||||
from infrastructure.presence import (
|
||||
produce_agent_state,
|
||||
produce_bark,
|
||||
produce_thought,
|
||||
serialize_presence,
|
||||
)
|
||||
|
||||
|
||||
class TestSerializePresence:
|
||||
@@ -269,3 +274,86 @@ class TestProduceBark:
|
||||
assert result["data"]["text"] == "Running test suite..."
|
||||
assert result["data"]["reply_to"] == "parent-msg-456"
|
||||
assert result["data"]["style"] == "thought"
|
||||
|
||||
|
||||
class TestProduceThought:
|
||||
"""Tests for produce_thought() — Matrix thought message producer."""
|
||||
|
||||
@patch("infrastructure.presence.time")
|
||||
def test_full_message_structure(self, mock_time):
|
||||
"""Returns dict with type, agent_id, data, and ts keys."""
|
||||
mock_time.time.return_value = 1742529600
|
||||
result = produce_thought("timmy", "Considering the options...", 42)
|
||||
|
||||
assert result["type"] == "thought"
|
||||
assert result["agent_id"] == "timmy"
|
||||
assert result["ts"] == 1742529600
|
||||
assert isinstance(result["data"], dict)
|
||||
|
||||
def test_data_fields(self):
|
||||
"""data dict contains text, thought_id, and chain_id."""
|
||||
result = produce_thought("timmy", "Considering...", 42, chain_id="chain-123")
|
||||
data = result["data"]
|
||||
|
||||
assert data["text"] == "Considering..."
|
||||
assert data["thought_id"] == 42
|
||||
assert data["chain_id"] == "chain-123"
|
||||
|
||||
def test_default_chain_id_is_none(self):
|
||||
"""When chain_id is not provided, defaults to None."""
|
||||
result = produce_thought("timmy", "Thinking...", 1)
|
||||
assert result["data"]["chain_id"] is None
|
||||
|
||||
def test_text_truncated_to_500_chars(self):
|
||||
"""Text longer than 500 chars is truncated."""
|
||||
long_text = "A" * 600
|
||||
result = produce_thought("timmy", long_text, 1)
|
||||
assert len(result["data"]["text"]) == 500
|
||||
assert result["data"]["text"] == "A" * 500
|
||||
|
||||
def test_text_exactly_500_chars_not_truncated(self):
|
||||
"""Text exactly 500 chars is not truncated."""
|
||||
text = "B" * 500
|
||||
result = produce_thought("timmy", text, 1)
|
||||
assert result["data"]["text"] == text
|
||||
|
||||
def test_text_shorter_than_500_not_padded(self):
|
||||
"""Text shorter than 500 chars is not padded."""
|
||||
result = produce_thought("timmy", "Short thought", 1)
|
||||
assert result["data"]["text"] == "Short thought"
|
||||
|
||||
def test_empty_text_handled(self):
|
||||
"""Empty text is handled gracefully."""
|
||||
result = produce_thought("timmy", "", 1)
|
||||
assert result["data"]["text"] == ""
|
||||
|
||||
def test_ts_is_unix_timestamp(self):
|
||||
"""ts should be an integer Unix timestamp."""
|
||||
result = produce_thought("timmy", "Hello!", 1)
|
||||
assert isinstance(result["ts"], int)
|
||||
assert result["ts"] > 0
|
||||
|
||||
def test_agent_id_passed_through(self):
|
||||
"""agent_id appears in the top-level message."""
|
||||
result = produce_thought("spark", "Hello!", 1)
|
||||
assert result["agent_id"] == "spark"
|
||||
|
||||
def test_thought_id_passed_through(self):
|
||||
"""thought_id appears in the data."""
|
||||
result = produce_thought("timmy", "Hello!", 999)
|
||||
assert result["data"]["thought_id"] == 999
|
||||
|
||||
def test_with_all_parameters(self):
|
||||
"""Full parameter set produces expected output."""
|
||||
result = produce_thought(
|
||||
agent_id="timmy",
|
||||
thought_text="Analyzing the situation...",
|
||||
thought_id=42,
|
||||
chain_id="chain-abc",
|
||||
)
|
||||
|
||||
assert result["type"] == "thought"
|
||||
assert result["agent_id"] == "timmy"
|
||||
assert result["data"]["text"] == "Analyzing the situation..."
|
||||
assert result["data"]["thought_id"] == 42
|
||||
assert result["data"]["chain_id"] == "chain-abc"
|
||||
|
||||
Reference in New Issue
Block a user