mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-19 11:15:24 -04:00
ed18192a8e
Moves create_session, list_sessions, send_to_session and manage_session out of ai_interaction.py into src/agent_tools/session_tools.py (the do_ prefix dropped) and registers them in TOOL_HANDLERS, so dispatch flows through the registry instead of the dispatch_ai_tool elif in tool_execution.py. Same pattern as the model-interaction move. The bodies move verbatim; each fetches the runtime-set session manager via a get_session_manager() shim, and reuses _resolve_model / AI_CHAT_TIMEOUT from ai_interaction. manage_session's internal 'list' alias is repointed from the old do_list_sessions to the moved list_sessions. stream_ai_tool (dead, no callers) and do_pipeline stay put. dispatch_ai_tool loses its four now-unused branches. Tests: test_session_tools_registry covers registration, owner threading, the manage_session->list_sessions delegation, graceful no-manager handling, and registry dispatch. Verified end-to-end against a live SessionManager.
150 lines
5.9 KiB
Python
150 lines
5.9 KiB
Python
"""Tests for the session tools' move to the agent_tools registry (#3629):
|
|
create_session, list_sessions, send_to_session, manage_session.
|
|
|
|
The implementations now live in src/agent_tools/session_tools.py (moved out of
|
|
src/ai_interaction.py). These assert (1) the handlers are registered in
|
|
TOOL_HANDLERS, (2) the moved logic runs and threads owner/session from ctx
|
|
(the session manager is fetched via ai_interaction.get_session_manager), and
|
|
(3) tool_execution.py dispatches them through the registry rather than the
|
|
legacy dispatch_ai_tool elif.
|
|
"""
|
|
import asyncio
|
|
from pathlib import Path
|
|
|
|
import src.ai_interaction as ai_interaction
|
|
import src.database as database
|
|
from src.agent_tools import TOOL_HANDLERS
|
|
from src.agent_tools import session_tools as st
|
|
|
|
_SESSION_TOOLS = ("create_session", "list_sessions", "send_to_session", "manage_session")
|
|
|
|
|
|
def test_session_tools_registered():
|
|
for name in _SESSION_TOOLS:
|
|
assert name in TOOL_HANDLERS, f"{name} missing from TOOL_HANDLERS"
|
|
|
|
|
|
def test_list_sessions_handler_threads_ctx(monkeypatch):
|
|
# The handler must thread content + session_id + owner from ctx into the
|
|
# moved list_sessions implementation. Spy at the function boundary so the
|
|
# test does not depend on list_sessions' DB internals.
|
|
seen = {}
|
|
|
|
async def spy(content, session_id=None, owner=None):
|
|
seen.update(content=content, session_id=session_id, owner=owner)
|
|
return {"results": "ok"}
|
|
|
|
monkeypatch.setattr(st, "list_sessions", spy)
|
|
res = asyncio.run(st.ListSessionsTool().execute("q", {"owner": "alice", "session_id": "s1"}))
|
|
assert res == {"results": "ok"}
|
|
assert seen == {"content": "q", "session_id": "s1", "owner": "alice"}
|
|
|
|
|
|
def test_manage_session_list_delegates_to_list_sessions(monkeypatch):
|
|
# manage_session("list") must delegate to list_sessions; guards against a
|
|
# stale do_list_sessions reference surviving the move (caught live in e2e).
|
|
called = {}
|
|
|
|
async def spy(content, session_id=None, owner=None):
|
|
called["owner"] = owner
|
|
return {"results": "ok"}
|
|
|
|
monkeypatch.setattr(st, "list_sessions", spy)
|
|
# manage_session imports `Session` from src.database before the list branch;
|
|
# the src.database test double may not expose it, so provide a stand-in.
|
|
monkeypatch.setattr(database, "Session", object, raising=False)
|
|
monkeypatch.setattr(ai_interaction, "_session_manager", object()) # truthy: pass the guard
|
|
res = asyncio.run(st.ManageSessionTool().execute("list", {"owner": "carol"}))
|
|
assert called.get("owner") == "carol"
|
|
assert res == {"results": "ok"}
|
|
|
|
|
|
def test_create_session_reaches_uuid_and_creates(monkeypatch):
|
|
# Regression for the missing `import uuid` (PR review): create_session must
|
|
# get past _resolve_model and mint a session id without NameError.
|
|
monkeypatch.setattr(st, "_resolve_model", lambda spec, owner=None: ("http://x", "model-x", {}))
|
|
created = {}
|
|
|
|
class FakeMgr:
|
|
def create_session(self, **kw):
|
|
created.update(kw)
|
|
|
|
def get_session(self, sid):
|
|
return None
|
|
|
|
monkeypatch.setattr(ai_interaction, "_session_manager", FakeMgr())
|
|
res = asyncio.run(st.CreateSessionTool().execute("My Chat\nmodel-x", {"owner": "alice"}))
|
|
assert res.get("name") == "My Chat" and res.get("model") == "model-x"
|
|
assert isinstance(res.get("session_id"), str) and res["session_id"]
|
|
assert created.get("name") == "My Chat" # the uuid-minted id reached the manager
|
|
|
|
|
|
def test_manage_session_fork_reaches_uuid(monkeypatch):
|
|
# Regression for the missing `import uuid`: the fork action also mints a new
|
|
# session id and must not NameError. Mocks the DB query layer so the fork
|
|
# branch reaches the uuid call without a real sessions table.
|
|
class FakeDbSession:
|
|
id = "id"
|
|
owner = "owner"
|
|
|
|
class FakeQ:
|
|
def filter(self, *a, **k):
|
|
return self
|
|
|
|
def first(self):
|
|
return object()
|
|
|
|
class FakeDB:
|
|
def query(self, *a, **k):
|
|
return FakeQ()
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
monkeypatch.setattr(database, "Session", FakeDbSession, raising=False)
|
|
monkeypatch.setattr(database, "SessionLocal", lambda: FakeDB(), raising=False)
|
|
|
|
class Src:
|
|
name = "Orig"
|
|
endpoint_url = "http://x"
|
|
model = "m"
|
|
|
|
def get_context_messages(self):
|
|
return []
|
|
|
|
created = {}
|
|
|
|
class FakeMgr:
|
|
def get_session(self, sid):
|
|
return Src() if sid == "abc" else type("S", (), {"add_message": lambda self, m: None})()
|
|
|
|
def create_session(self, **kw):
|
|
created.update(kw)
|
|
|
|
monkeypatch.setattr(ai_interaction, "_session_manager", FakeMgr())
|
|
res = asyncio.run(st.ManageSessionTool().execute('{"action":"fork","session_id":"abc"}', {"owner": "owner"}))
|
|
assert res.get("action") == "fork"
|
|
assert isinstance(res.get("session_id"), str) and res["session_id"]
|
|
assert created.get("name") == "Fork: Orig" # uuid-minted new session was created
|
|
|
|
|
|
def test_no_session_manager_is_handled(monkeypatch):
|
|
# With no session manager set, the moved function must fail gracefully
|
|
# (proves the handler reached the impl, not an "unknown tool").
|
|
monkeypatch.setattr(ai_interaction, "_session_manager", None)
|
|
res = asyncio.run(st.ListSessionsTool().execute("", {"owner": "bob"}))
|
|
assert isinstance(res, dict)
|
|
assert "error" in res or "results" in res
|
|
|
|
|
|
def test_dispatched_via_registry_not_dispatch_ai_tool():
|
|
source = (Path(__file__).resolve().parent.parent / "src" / "tool_execution.py").read_text(encoding="utf-8")
|
|
assert 'elif tool in ("create_session", "list_sessions", "send_to_session", "manage_session"):' in source
|
|
|
|
marker = "from src.ai_interaction import dispatch_ai_tool"
|
|
idx = source.index(marker)
|
|
branch_head = source.rfind("elif tool in (", 0, idx)
|
|
legacy_tuple = source[branch_head:idx]
|
|
for name in _SESSION_TOOLS:
|
|
assert f'"{name}"' not in legacy_tuple, f"{name} still routed via dispatch_ai_tool"
|