feat: add ops (validation + has_unread/join/delete)
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user