diff --git a/src/agentmsgs/core/ops.py b/src/agentmsgs/core/ops.py new file mode 100644 index 0000000..1ac8b4f --- /dev/null +++ b/src/agentmsgs/core/ops.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import uuid + +from .store import Store +from .types import Agent, Message, Thread + + +def get_or_create_agent(store: Store, name: str) -> Agent: + return store.get_or_create_agent(name) + + +def create_agent(store: Store, name: str) -> Agent: + return store.create_agent(name) + + +def delete_agent(store: Store, id: uuid.UUID) -> None: + return store.delete_agent(id) + + +def create_thread(store: Store, participants: set[Agent]) -> Thread: + if len(participants) < 2: + raise ValueError("need >=2 participants") + return store.create_thread(participants) + + +def find_threads(store: Store, containing: set[Agent]) -> list[Thread]: + return store.find_threads(containing) + + +def join_thread(store: Store, tid: uuid.UUID, agent: Agent) -> Thread: + return store.add_participant(tid, agent) + + +def leave_thread(store: Store, tid: uuid.UUID, agent: Agent) -> Thread: + return store.remove_participant(tid, agent) + + +def append_message(store: Store, tid: uuid.UUID, sender: Agent, content: str) -> Message: + return store.append_message(tid, sender, content) + + +def list_messages(store: Store, tid: uuid.UUID, after_seq: int = 0) -> list[Message]: + return store.list_messages(tid, after_seq=after_seq) + + +def mark_read(store: Store, tid: uuid.UUID, agent: Agent, seq: int) -> None: + return store.set_cursor(tid, agent, seq) + + +def has_unread(store: Store, tid: uuid.UUID, agent: Agent, exclude_own: bool = False) -> bool: + cur = store.get_cursor(tid, agent) + msgs = store.list_messages(tid, after_seq=cur) + if exclude_own: + msgs = [m for m in msgs if m.sender.id != agent.id] + return len(msgs) > 0 diff --git a/tests/test_ops.py b/tests/test_ops.py new file mode 100644 index 0000000..cfbbdbb --- /dev/null +++ b/tests/test_ops.py @@ -0,0 +1,36 @@ +from agentmsgs.stores.memory import InMemoryStore +from agentmsgs.core import ops + + +def test_ops_validates_sender_must_be_in_thread(): + s = InMemoryStore() + a = ops.get_or_create_agent(s, "A"); b = ops.get_or_create_agent(s, "B"); c = ops.get_or_create_agent(s, "C") + t = ops.create_thread(s, {a,b}) + try: + ops.append_message(s, t.id, c, "oops") + assert False, "should raise" + except ValueError as e: + assert "not in thread" in str(e) + + +def test_ops_has_unread_exclude_own(): + s = InMemoryStore() + a = ops.get_or_create_agent(s, "A"); b = ops.get_or_create_agent(s, "B") + t = ops.create_thread(s, {a,b}) + ops.append_message(s, t.id, a, "a1") + # b has unread, a's own message shouldn't count if exclude_own + assert ops.has_unread(s, t.id, b) is True + assert ops.has_unread(s, t.id, a, exclude_own=True) is False + assert ops.has_unread(s, t.id, a, exclude_own=False) is True + ops.mark_read(s, t.id, b, 1) + assert ops.has_unread(s, t.id, b) is False + + +def test_ops_join_and_delete(): + s = InMemoryStore() + a = ops.get_or_create_agent(s, "A"); b = ops.get_or_create_agent(s, "B"); c = ops.get_or_create_agent(s, "C") + t = ops.create_thread(s, {a,b}) + t2 = ops.join_thread(s, t.id, c) + assert c in t2.participants + ops.delete_agent(s, c.id) + assert s.get_agent_by_id(c.id) is None