- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
298 lines
10 KiB
Python
298 lines
10 KiB
Python
"""
|
|
Agent Repository — Database operations for agents
|
|
Phase 6: CRUD + DB Persistence
|
|
"""
|
|
import uuid
|
|
from typing import List, Optional
|
|
from datetime import datetime
|
|
import asyncpg
|
|
from models import AgentCreate, AgentUpdate, AgentRead, AgentBlueprint
|
|
|
|
class AgentRepository:
|
|
def __init__(self, db_pool: asyncpg.Pool):
|
|
self.db = db_pool
|
|
|
|
# ========================================================================
|
|
# Blueprints
|
|
# ========================================================================
|
|
|
|
async def get_blueprint_by_code(self, code: str) -> Optional[AgentBlueprint]:
|
|
"""Get blueprint by code"""
|
|
row = await self.db.fetchrow(
|
|
"""
|
|
SELECT id, code, name, description, default_model,
|
|
default_tools, default_system_prompt, created_at
|
|
FROM agent_blueprints
|
|
WHERE code = $1
|
|
""",
|
|
code
|
|
)
|
|
|
|
if not row:
|
|
return None
|
|
|
|
return AgentBlueprint(
|
|
id=str(row['id']),
|
|
code=row['code'],
|
|
name=row['name'],
|
|
description=row['description'],
|
|
default_model=row['default_model'],
|
|
default_tools=row['default_tools'] or [],
|
|
default_system_prompt=row['default_system_prompt'],
|
|
created_at=row['created_at']
|
|
)
|
|
|
|
async def list_blueprints(self) -> List[AgentBlueprint]:
|
|
"""List all blueprints"""
|
|
rows = await self.db.fetch(
|
|
"""
|
|
SELECT id, code, name, description, default_model,
|
|
default_tools, default_system_prompt, created_at
|
|
FROM agent_blueprints
|
|
ORDER BY name
|
|
"""
|
|
)
|
|
|
|
return [
|
|
AgentBlueprint(
|
|
id=str(row['id']),
|
|
code=row['code'],
|
|
name=row['name'],
|
|
description=row['description'],
|
|
default_model=row['default_model'],
|
|
default_tools=row['default_tools'] or [],
|
|
default_system_prompt=row['default_system_prompt'],
|
|
created_at=row['created_at']
|
|
)
|
|
for row in rows
|
|
]
|
|
|
|
# ========================================================================
|
|
# Agents — CRUD
|
|
# ========================================================================
|
|
|
|
async def create_agent(
|
|
self,
|
|
data: AgentCreate,
|
|
actor_id: Optional[str] = None
|
|
) -> AgentRead:
|
|
"""Create new agent"""
|
|
# Get blueprint
|
|
blueprint = await self.get_blueprint_by_code(data.blueprint_code)
|
|
if not blueprint:
|
|
raise ValueError(f"Blueprint '{data.blueprint_code}' not found")
|
|
|
|
# Generate IDs
|
|
agent_id = uuid.uuid4()
|
|
external_id = f"agent:{agent_id.hex[:12]}"
|
|
|
|
# Determine model (use provided or blueprint default)
|
|
model = data.model or blueprint.default_model
|
|
|
|
# Determine system prompt
|
|
system_prompt = data.system_prompt or blueprint.default_system_prompt
|
|
|
|
# Insert
|
|
row = await self.db.fetchrow(
|
|
"""
|
|
INSERT INTO agents (
|
|
id, external_id, name, kind, microdao_id, owner_user_id,
|
|
blueprint_id, model, tools_enabled, system_prompt,
|
|
avatar_url, description, is_active
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
RETURNING id, external_id, name, kind, microdao_id, owner_user_id,
|
|
blueprint_id, model, tools_enabled, system_prompt,
|
|
avatar_url, description, is_active, created_at, updated_at
|
|
""",
|
|
agent_id,
|
|
external_id,
|
|
data.name,
|
|
data.kind.value,
|
|
uuid.UUID(data.microdao_id) if data.microdao_id else None,
|
|
uuid.UUID(data.owner_user_id or actor_id) if (data.owner_user_id or actor_id) else None,
|
|
uuid.UUID(blueprint.id),
|
|
model,
|
|
data.tools_enabled or [],
|
|
system_prompt,
|
|
data.avatar_url,
|
|
data.description,
|
|
True
|
|
)
|
|
|
|
return self._row_to_agent_read(row)
|
|
|
|
async def update_agent(
|
|
self,
|
|
agent_id: str,
|
|
data: AgentUpdate
|
|
) -> AgentRead:
|
|
"""Update agent"""
|
|
# Build dynamic update query
|
|
updates = []
|
|
values = []
|
|
param_idx = 1
|
|
|
|
if data.name is not None:
|
|
updates.append(f"name = ${param_idx}")
|
|
values.append(data.name)
|
|
param_idx += 1
|
|
|
|
if data.description is not None:
|
|
updates.append(f"description = ${param_idx}")
|
|
values.append(data.description)
|
|
param_idx += 1
|
|
|
|
if data.model is not None:
|
|
updates.append(f"model = ${param_idx}")
|
|
values.append(data.model)
|
|
param_idx += 1
|
|
|
|
if data.tools_enabled is not None:
|
|
updates.append(f"tools_enabled = ${param_idx}")
|
|
values.append(data.tools_enabled)
|
|
param_idx += 1
|
|
|
|
if data.system_prompt is not None:
|
|
updates.append(f"system_prompt = ${param_idx}")
|
|
values.append(data.system_prompt)
|
|
param_idx += 1
|
|
|
|
if data.avatar_url is not None:
|
|
updates.append(f"avatar_url = ${param_idx}")
|
|
values.append(data.avatar_url)
|
|
param_idx += 1
|
|
|
|
if data.is_active is not None:
|
|
updates.append(f"is_active = ${param_idx}")
|
|
values.append(data.is_active)
|
|
param_idx += 1
|
|
|
|
if not updates:
|
|
# No changes
|
|
return await self.get_agent_by_external_id(agent_id)
|
|
|
|
# Always update updated_at
|
|
updates.append(f"updated_at = NOW()")
|
|
|
|
# Add agent_id to values
|
|
values.append(agent_id)
|
|
|
|
query = f"""
|
|
UPDATE agents
|
|
SET {', '.join(updates)}
|
|
WHERE external_id = ${param_idx}
|
|
RETURNING id, external_id, name, kind, microdao_id, owner_user_id,
|
|
blueprint_id, model, tools_enabled, system_prompt,
|
|
avatar_url, description, is_active, created_at, updated_at
|
|
"""
|
|
|
|
row = await self.db.fetchrow(query, *values)
|
|
|
|
if not row:
|
|
raise ValueError(f"Agent '{agent_id}' not found")
|
|
|
|
return self._row_to_agent_read(row)
|
|
|
|
async def delete_agent(self, agent_id: str) -> None:
|
|
"""Soft delete agent (set is_active = false)"""
|
|
result = await self.db.execute(
|
|
"""
|
|
UPDATE agents
|
|
SET is_active = false, updated_at = NOW()
|
|
WHERE external_id = $1
|
|
""",
|
|
agent_id
|
|
)
|
|
|
|
if result == "UPDATE 0":
|
|
raise ValueError(f"Agent '{agent_id}' not found")
|
|
|
|
async def get_agent_by_external_id(self, external_id: str) -> Optional[AgentRead]:
|
|
"""Get agent by external_id"""
|
|
row = await self.db.fetchrow(
|
|
"""
|
|
SELECT id, external_id, name, kind, microdao_id, owner_user_id,
|
|
blueprint_id, model, tools_enabled, system_prompt,
|
|
avatar_url, description, is_active, created_at, updated_at
|
|
FROM agents
|
|
WHERE external_id = $1
|
|
""",
|
|
external_id
|
|
)
|
|
|
|
if not row:
|
|
return None
|
|
|
|
return self._row_to_agent_read(row)
|
|
|
|
async def list_agents(
|
|
self,
|
|
microdao_id: Optional[str] = None,
|
|
owner_user_id: Optional[str] = None,
|
|
kind: Optional[str] = None,
|
|
is_active: bool = True
|
|
) -> List[AgentRead]:
|
|
"""List agents with filters"""
|
|
query = """
|
|
SELECT id, external_id, name, kind, microdao_id, owner_user_id,
|
|
blueprint_id, model, tools_enabled, system_prompt,
|
|
avatar_url, description, is_active, created_at, updated_at
|
|
FROM agents
|
|
WHERE 1=1
|
|
"""
|
|
|
|
values = []
|
|
param_idx = 1
|
|
|
|
if microdao_id:
|
|
query += f" AND microdao_id = ${param_idx}"
|
|
values.append(uuid.UUID(microdao_id))
|
|
param_idx += 1
|
|
|
|
if owner_user_id:
|
|
query += f" AND owner_user_id = ${param_idx}"
|
|
values.append(uuid.UUID(owner_user_id))
|
|
param_idx += 1
|
|
|
|
if kind:
|
|
query += f" AND kind = ${param_idx}"
|
|
values.append(kind)
|
|
param_idx += 1
|
|
|
|
query += f" AND is_active = ${param_idx}"
|
|
values.append(is_active)
|
|
|
|
query += " ORDER BY created_at DESC"
|
|
|
|
rows = await self.db.fetch(query, *values)
|
|
|
|
return [self._row_to_agent_read(row) for row in rows]
|
|
|
|
# ========================================================================
|
|
# Helpers
|
|
# ========================================================================
|
|
|
|
def _row_to_agent_read(self, row) -> AgentRead:
|
|
"""Convert DB row to AgentRead"""
|
|
from models import AgentKind
|
|
|
|
return AgentRead(
|
|
id=str(row['id']),
|
|
external_id=row['external_id'],
|
|
name=row['name'],
|
|
kind=AgentKind(row['kind']),
|
|
description=row['description'],
|
|
microdao_id=str(row['microdao_id']) if row['microdao_id'] else None,
|
|
owner_user_id=str(row['owner_user_id']) if row['owner_user_id'] else None,
|
|
blueprint_id=str(row['blueprint_id']) if row['blueprint_id'] else None,
|
|
model=row['model'],
|
|
tools_enabled=row['tools_enabled'] or [],
|
|
system_prompt=row['system_prompt'],
|
|
avatar_url=row['avatar_url'],
|
|
is_active=row['is_active'],
|
|
created_at=row['created_at'],
|
|
updated_at=row['updated_at']
|
|
)
|
|
|