refactor(backend): update API routes and service layer

- Update agent router/service/repository with new endpoints
- Update auth routes with phone-based authentication
- Update users service with new phone lookup
- Update schedule_items with new schemas
- Update message schemas with visibility support
- Update settings with new automation scheduler config
- Update CLI with new commands
- Update tests to match new API contracts
This commit is contained in:
qzl
2026-03-19 18:42:59 +08:00
parent 641d847008
commit f0af44d840
36 changed files with 1083 additions and 1853 deletions
+106 -7
View File
@@ -20,6 +20,7 @@ class _FakeRepository:
def __init__(self) -> None:
self.committed = False
self.persisted_user_messages: list[dict[str, object]] = []
self.created_session_calls = 0
async def get_session_owner(self, *, session_id: str) -> str:
if session_id == "00000000-0000-0000-0000-000000000001":
@@ -30,6 +31,7 @@ class _FakeRepository:
self, *, user_id: str, session_id: str | None = None
) -> str:
del user_id
self.created_session_calls += 1
return session_id or "00000000-0000-0000-0000-000000000999"
async def commit(self) -> None:
@@ -39,9 +41,13 @@ class _FakeRepository:
return None
async def get_history_day(
self, *, session_id: str, before: date | None
self,
*,
session_id: str,
before: date | None,
visibility_mask: int | None = None,
) -> dict[str, object] | None:
del session_id, before
del session_id, before, visibility_mask
return None
async def get_latest_session_id_for_user(self, *, user_id: str) -> str | None:
@@ -54,15 +60,42 @@ class _FakeRepository:
session_id: str,
content: str,
metadata: AgentChatMessageMetadata | None,
visibility_mask: int,
) -> None:
self.persisted_user_messages.append(
{
"session_id": session_id,
"content": content,
"metadata": metadata,
"visibility_mask": visibility_mask,
}
)
async def get_system_agent_config(
self, *, agent_type: str
) -> dict[str, object] | None:
normalized = agent_type.strip().lower()
mapping = {
"router": 16,
"worker": 17,
"memory": 18,
}
bit = mapping.get(normalized)
if bit is None:
return None
return {
"agent_type": normalized,
"status": "active",
"config": {
"temperature": 0.7,
"max_tokens": None,
"timeout_seconds": 30,
"visibility_consumer_bit": bit,
"context_messages": {"mode": "number", "count": 20},
"enabled_tools": [],
},
}
class _FakeQueue:
def __init__(self) -> None:
@@ -122,11 +155,11 @@ class _FakeAttachmentStorage:
def _user() -> CurrentUser:
return CurrentUser(
id=UUID("00000000-0000-0000-0000-000000000001"),
email="user@example.com",
phone="+8613812345678",
)
def _build_run_input(*, urls: list[str]) -> RunAgentInput:
def _build_run_input(*, urls: list[str], agent_type: str = "worker") -> RunAgentInput:
content: list[dict[str, str]] = [{"type": "text", "text": "hello"}]
for url in urls:
content.append({"type": "binary", "mimeType": "image/png", "url": url})
@@ -144,7 +177,7 @@ def _build_run_input(*, urls: list[str]) -> RunAgentInput:
],
"tools": [],
"context": [],
"forwardedProps": {},
"forwardedProps": {"agent_type": agent_type},
}
)
@@ -222,6 +255,68 @@ async def test_enqueue_run_persists_attachment_and_queue_without_user_token(
assert run_input["runId"] == "run-1"
@pytest.mark.asyncio
async def test_enqueue_run_rejects_unknown_agent_type(monkeypatch) -> None:
monkeypatch.setattr(
agent_service_module.config.storage, "bucket", "agent-test-bucket"
)
service = AgentService(
repository=_FakeRepository(),
queue=_FakeQueue(),
stream=_FakeStream(),
attachment_storage=_FakeAttachmentStorage(),
)
base_url = str(config.supabase.url).rstrip("/")
safe_path = quote(
"agent-inputs/00000000-0000-0000-0000-000000000001/"
"00000000-0000-0000-0000-000000000001/uploads/a.png"
)
run_input = _build_run_input(
urls=[
f"{base_url}/storage/v1/object/sign/agent-test-bucket/{safe_path}?token=1"
],
agent_type="planner",
)
with pytest.raises(HTTPException) as exc_info:
await service.enqueue_run(run_input=run_input, current_user=_user())
assert exc_info.value.status_code == 422
@pytest.mark.asyncio
async def test_enqueue_run_rejects_memory_mode_for_api(monkeypatch) -> None:
monkeypatch.setattr(
agent_service_module.config.storage, "bucket", "agent-test-bucket"
)
repository = _FakeRepository()
service = AgentService(
repository=repository,
queue=_FakeQueue(),
stream=_FakeStream(),
attachment_storage=_FakeAttachmentStorage(),
)
base_url = str(config.supabase.url).rstrip("/")
safe_path = quote(
"agent-inputs/00000000-0000-0000-0000-000000000001/"
"00000000-0000-0000-0000-000000000001/uploads/a.png"
)
run_input = _build_run_input(
urls=[
f"{base_url}/storage/v1/object/sign/agent-test-bucket/{safe_path}?token=1"
],
agent_type="memory",
)
with pytest.raises(HTTPException) as exc_info:
await service.enqueue_run(run_input=run_input, current_user=_user())
assert exc_info.value.status_code == 422
assert exc_info.value.detail == "memory mode is automation-only"
assert repository.created_session_calls == 0
assert repository.persisted_user_messages == []
@pytest.mark.asyncio
async def test_create_attachment_signed_url_returns_url(monkeypatch) -> None:
monkeypatch.setattr(
@@ -317,9 +412,13 @@ async def test_enqueue_run_rejects_too_many_attachments(monkeypatch) -> None:
async def test_get_history_snapshot_filters_out_tool_messages() -> None:
class _HistoryRepository(_FakeRepository):
async def get_history_day(
self, *, session_id: str, before: date | None
self,
*,
session_id: str,
before: date | None,
visibility_mask: int | None = None,
) -> dict[str, object] | None:
del session_id, before
del session_id, before, visibility_mask
return {
"day": "2026-03-17",
"hasMore": False,