"""Abstract persistence API for custom agents.""" from __future__ import annotations from abc import ABC, abstractmethod from typing import Any class AgentStore(ABC): @abstractmethod async def list_visible(self, user_id: str) -> list[dict[str, Any]]: raise NotImplementedError @abstractmethod async def get_visible(self, agent_id: str, user_id: str) -> dict[str, Any] | None: raise NotImplementedError @abstractmethod async def get_any(self, agent_id: str) -> dict[str, Any] | None: raise NotImplementedError @abstractmethod async def get_owned(self, agent_id: str, user_id: str) -> dict[str, Any] | None: raise NotImplementedError @abstractmethod async def create(self, data: dict[str, Any]) -> dict[str, Any]: raise NotImplementedError @abstractmethod async def ensure_builtin(self, data: dict[str, Any]) -> dict[str, Any]: raise NotImplementedError @abstractmethod async def update(self, agent_id: str, user_id: str, data: dict[str, Any]) -> dict[str, Any] | None: raise NotImplementedError @abstractmethod async def update_builtin(self, agent_id: str, data: dict[str, Any]) -> dict[str, Any] | None: """Update a built-in agent (user_id IS NULL). Caller is responsible for authorization — this method does NOT check any user identity and returns ``None`` if the target is not a built-in. """ raise NotImplementedError @abstractmethod async def delete(self, agent_id: str, user_id: str) -> bool: raise NotImplementedError @abstractmethod async def delete_builtin(self, agent_id: str) -> bool: """Delete a built-in agent (user_id IS NULL). Caller is responsible for authorization — this method does NOT check any user identity and returns ``False`` if the target is not a built-in. """ raise NotImplementedError @abstractmethod async def count_owned(self, user_id: str) -> int: """Return the number of agents owned by ``user_id`` (user_id IS NOT NULL).""" raise NotImplementedError @abstractmethod async def set_featured(self, agent_id: str, order: int | None) -> dict[str, Any] | None: """Set or clear the homepage-selector featured rank for an agent. Returns the updated row, or ``None`` if the agent does not exist. Caller is responsible for authorization (admin) and for verifying that ``published`` is true when ``order`` is not ``None``. """ raise NotImplementedError @abstractmethod async def list_featured(self, user_id: str) -> list[dict[str, Any]]: """Return featured + still-visible-to-``user_id`` agents, ordered by ``featured_order`` asc.""" raise NotImplementedError @abstractmethod async def list_by_ids(self, agent_ids: list[str], user_id: str) -> list[dict[str, Any]]: """Return rows for ``agent_ids`` visible to ``user_id``, preserving the input order.""" raise NotImplementedError @abstractmethod async def list_private(self) -> list[dict[str, Any]]: """Return every agent flagged ``is_private=True``. Caller is responsible for authorization — typically the caller has already verified the admin-configured private-square password. """ raise NotImplementedError @abstractmethod async def list_all(self) -> list[dict[str, Any]]: """Return every agent regardless of ownership/visibility. Caller is responsible for authorization — typically the caller has already verified ``system_role == "admin"``. """ raise NotImplementedError @abstractmethod async def update_any(self, agent_id: str, data: dict[str, Any]) -> dict[str, Any] | None: """Update any agent row by id, regardless of ownership. Caller is responsible for authorization (admin only). Returns ``None`` if the agent does not exist. """ raise NotImplementedError @abstractmethod async def delete_any(self, agent_id: str) -> bool: """Delete any agent row by id, regardless of ownership. Caller is responsible for authorization (admin only). Returns ``False`` if the agent does not exist. """ raise NotImplementedError @abstractmethod async def list_paginated( self, *, user_id: str, scope: str | None, search: str | None, tag_ids: list[str] | None, page: int, page_size: int, admin: bool = False, sort: str = "time_desc", owner: str | None = None, square_id: str | None = None, square_is_default: bool = False, ) -> tuple[list[dict[str, Any]], int]: """Return one page of agents plus the total matching count. ``scope``: - ``"mine"`` — owned by ``user_id`` OR favorited by ``user_id``. - ``"square"`` — built-in or published, and not private-square. - ``"private"``— flagged ``is_private=True`` (caller gates access). - ``None`` — the default listing visibility (``list_visible``). ``admin=True`` bypasses the visibility/scope filter entirely (every agent), but still honors ``search`` / ``tag_ids``. Caller authorizes. ``search`` matches the raw name OR its desensitized display form. ``tag_ids`` keeps agents carrying ANY of the tags (OR). ``owner`` keeps only agents published by that user (``user_id`` equality) — built-in agents (no owner) never match. All filters, the COUNT, and LIMIT/OFFSET all execute in SQL. Returns ``(rows, total)`` where ``rows`` is at most ``page_size`` items for the 1-based ``page``. """ raise NotImplementedError # --- Listing cache (skills / tool_groups / model) --- @abstractmethod async def replace_skills(self, agent_id: str, skills: list[str] | None) -> None: """Replace the cached skill list for ``agent_id``. ``None`` and ``[]`` both clear the cache to "no skills" (mirrors the tri-state semantics of ``AgentConfig.skills``). """ raise NotImplementedError @abstractmethod async def set_extras(self, agent_id: str, *, tool_groups: list[str] | None, model: str | None) -> None: """Upsert the cached ``tool_groups`` / ``model`` for ``agent_id``.""" raise NotImplementedError @abstractmethod async def get_extras_for(self, agent_ids: list[str]) -> dict[str, dict[str, Any]]: """Batch-fetch cached listing extras for ``agent_ids``. Returns ``{agent_id: {"skills": list[str] | None, "tool_groups": list[str] | None, "model": str | None}}``. An agent that was never mirrored is absent from the mapping (caller treats it as unknown). """ raise NotImplementedError @abstractmethod async def delete_extras_for_agent(self, agent_id: str) -> None: """Drop the cached skills + extras rows for ``agent_id`` (on delete).""" raise NotImplementedError # --- Favorites ("收藏") --- @abstractmethod async def add_favorite(self, user_id: str, agent_id: str) -> None: """Bookmark ``agent_id`` for ``user_id``. Idempotent.""" raise NotImplementedError @abstractmethod async def remove_favorite(self, user_id: str, agent_id: str) -> bool: """Remove a bookmark. Returns ``False`` when none existed.""" raise NotImplementedError @abstractmethod async def list_favorite_ids(self, user_id: str) -> set[str]: """Return the set of agent ids ``user_id`` has favorited.""" raise NotImplementedError @abstractmethod async def delete_favorites_for_agent(self, agent_id: str) -> None: """Drop every user's favorite of ``agent_id`` (called on agent delete).""" raise NotImplementedError # --- Per-user pins ("置顶") --- @abstractmethod async def add_pin(self, user_id: str, agent_id: str) -> None: """Pin ``agent_id`` for ``user_id``. Idempotent.""" raise NotImplementedError @abstractmethod async def remove_pin(self, user_id: str, agent_id: str) -> bool: """Remove a per-user pin. Returns ``False`` when none existed.""" raise NotImplementedError @abstractmethod async def list_pin_ids(self, user_id: str) -> set[str]: """Return the set of agent ids pinned by ``user_id``.""" raise NotImplementedError @abstractmethod async def delete_pins_for_agent(self, agent_id: str) -> None: """Drop every user's pin of ``agent_id`` (called on agent delete).""" raise NotImplementedError