555e282cc4
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
from collections import defaultdict
|
|
from datetime import datetime
|
|
from typing import Any, Literal, Optional
|
|
|
|
from auth import require_admin, verify_auth
|
|
from errors import upstream_error
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from schemas import MessageResponse
|
|
from server_state import get_memory_instance
|
|
|
|
router = APIRouter(prefix="/entities", tags=["entities"])
|
|
|
|
SCAN_LIMIT = 10_000
|
|
|
|
EntityType = Literal["user", "agent", "run"]
|
|
TYPE_TO_FIELD: dict[EntityType, str] = {"user": "user_id", "agent": "agent_id", "run": "run_id"}
|
|
|
|
|
|
class Entity(BaseModel):
|
|
id: str
|
|
type: EntityType
|
|
total_memories: int
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
|
|
def _iter_payloads() -> list[dict[str, Any]]:
|
|
results = get_memory_instance().vector_store.list(top_k=SCAN_LIMIT)
|
|
rows = results[0] if results and isinstance(results, list) and isinstance(results[0], list) else results or []
|
|
return [getattr(row, "payload", None) or {} for row in rows]
|
|
|
|
|
|
def _parse_timestamp(value: Any) -> Optional[datetime]:
|
|
if not value:
|
|
return None
|
|
try:
|
|
return datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
@router.get("", response_model=list[Entity])
|
|
def list_entities(_auth=Depends(verify_auth)):
|
|
buckets: dict[tuple[EntityType, str], dict[str, Any]] = defaultdict(
|
|
lambda: {"total_memories": 0, "created_at": None, "updated_at": None}
|
|
)
|
|
|
|
for payload in _iter_payloads():
|
|
created = _parse_timestamp(payload.get("created_at"))
|
|
updated = _parse_timestamp(payload.get("updated_at")) or created
|
|
|
|
for entity_type, field in TYPE_TO_FIELD.items():
|
|
value = payload.get(field)
|
|
if not value:
|
|
continue
|
|
bucket = buckets[(entity_type, str(value))]
|
|
bucket["total_memories"] += 1
|
|
if created and (bucket["created_at"] is None or created < bucket["created_at"]):
|
|
bucket["created_at"] = created
|
|
if updated and (bucket["updated_at"] is None or updated > bucket["updated_at"]):
|
|
bucket["updated_at"] = updated
|
|
|
|
return [
|
|
Entity(id=entity_id, type=entity_type, **data)
|
|
for (entity_type, entity_id), data in sorted(buckets.items(), key=lambda item: (item[0][0], item[0][1]))
|
|
]
|
|
|
|
|
|
@router.delete("/{entity_type}/{entity_id}", response_model=MessageResponse)
|
|
def delete_entity(entity_type: EntityType, entity_id: str, _auth=Depends(require_admin)):
|
|
try:
|
|
get_memory_instance().delete_all(**{TYPE_TO_FIELD[entity_type]: entity_id})
|
|
except Exception:
|
|
raise upstream_error()
|
|
return MessageResponse(message="Entity deleted")
|