Files
microdao-daarion/services/city-service/models_city.py

766 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Pydantic Models для City Backend
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
from uuid import UUID
# =============================================================================
# City Rooms
# =============================================================================
class CityRoomBase(BaseModel):
slug: str
name: str
description: Optional[str] = None
class CityRoomCreate(CityRoomBase):
pass
class CityRoomRead(CityRoomBase):
id: str
is_default: bool
created_at: datetime
created_by: Optional[str] = None
members_online: int = 0
last_event: Optional[str] = None
# Branding
logo_url: Optional[str] = None
banner_url: Optional[str] = None
# Context
microdao_id: Optional[str] = None
microdao_name: Optional[str] = None
microdao_slug: Optional[str] = None
microdao_logo_url: Optional[str] = None
# Matrix integration
matrix_room_id: Optional[str] = None
matrix_room_alias: Optional[str] = None
# =============================================================================
# City Room Messages
# =============================================================================
class CityRoomMessageBase(BaseModel):
body: str = Field(..., min_length=1, max_length=10000)
class CityRoomMessageCreate(CityRoomMessageBase):
pass
class CityRoomMessageRead(CityRoomMessageBase):
id: str
room_id: str
author_user_id: Optional[str] = None
author_agent_id: Optional[str] = None
username: Optional[str] = "Anonymous" # Для frontend
created_at: datetime
# =============================================================================
# City Room Detail (з повідомленнями)
# =============================================================================
class CityRoomDetail(CityRoomRead):
messages: List[CityRoomMessageRead] = []
online_members: List[str] = [] # user_ids
# =============================================================================
# City Feed Events
# =============================================================================
class CityFeedEventRead(BaseModel):
id: str
kind: str # 'room_message', 'agent_reply', 'system', 'dao_event'
room_id: Optional[str] = None
user_id: Optional[str] = None
agent_id: Optional[str] = None
payload: dict
created_at: datetime
# =============================================================================
# Presence
# =============================================================================
class PresenceUpdate(BaseModel):
user_id: str
status: str # 'online', 'offline', 'away'
last_seen: Optional[datetime] = None
class PresenceBulkUpdate(BaseModel):
users: List[PresenceUpdate]
# =============================================================================
# WebSocket Messages
# =============================================================================
class WSRoomMessage(BaseModel):
event: str # 'room.message', 'room.join', 'room.leave'
room_id: Optional[str] = None
user_id: Optional[str] = None
message: Optional[CityRoomMessageRead] = None
class WSPresenceMessage(BaseModel):
event: str # 'presence.heartbeat', 'presence.update'
user_id: str
status: Optional[str] = None
# =============================================================================
# City Map (2D Map)
# =============================================================================
class CityMapRoom(BaseModel):
"""Room representation on 2D city map"""
id: str
slug: str
name: str
description: Optional[str] = None
room_type: str = "public"
zone: str = "central"
icon: Optional[str] = None
color: Optional[str] = None
# Map coordinates
x: int = 0
y: int = 0
w: int = 1
h: int = 1
# Matrix integration
matrix_room_id: Optional[str] = None
class CityMapConfig(BaseModel):
"""Global city map configuration"""
grid_width: int = 6
grid_height: int = 3
cell_size: int = 100
background_url: Optional[str] = None
class CityMapResponse(BaseModel):
"""Full city map response"""
config: CityMapConfig
rooms: List[CityMapRoom]
# =============================================================================
# Branding & Assets
# =============================================================================
class BrandingUpdatePayload(BaseModel):
logo_url: Optional[str] = None
banner_url: Optional[str] = None
class AssetUploadResponse(BaseModel):
original_url: str
processed_url: str
thumb_url: Optional[str] = None
# =============================================================================
# Agents (for Agent Presence)
# =============================================================================
class AgentRead(BaseModel):
"""Agent representation"""
id: str
display_name: str
kind: str = "assistant" # assistant, civic, oracle, builder
avatar_url: Optional[str] = None
color: str = "cyan"
status: str = "offline" # online, offline, busy
current_room_id: Optional[str] = None
capabilities: List[str] = []
class AgentPresence(BaseModel):
"""Agent presence in a room"""
agent_id: str
display_name: str
kind: str
status: str
room_id: Optional[str] = None
color: Optional[str] = None
node_id: Optional[str] = None
district: Optional[str] = None
model: Optional[str] = None
role: Optional[str] = None
avatar_url: Optional[str] = None
# =============================================================================
# Citizens
# =============================================================================
class CityPresenceRoomView(BaseModel):
room_id: Optional[str] = None
slug: Optional[str] = None
name: Optional[str] = None
class CityPresenceView(BaseModel):
primary_room_slug: Optional[str] = None
rooms: List[CityPresenceRoomView] = []
class HomeNodeView(BaseModel):
"""Home node information for agent/citizen"""
id: Optional[str] = None
name: Optional[str] = None
hostname: Optional[str] = None
roles: List[str] = []
environment: Optional[str] = None
class NodeAgentSummary(BaseModel):
"""Summary of a node agent (Guardian or Steward)"""
id: str
name: Optional[str] = None
kind: Optional[str] = None
slug: Optional[str] = None
class NodeMicrodaoSummary(BaseModel):
"""Summary of a MicroDAO hosted on a node (via orchestrator)"""
id: str
slug: str
name: str
rooms_count: int = 0
class NodeMetrics(BaseModel):
"""Node metrics for Node Directory cards"""
cpu_model: Optional[str] = None
cpu_cores: int = 0
cpu_usage: float = 0.0
gpu_model: Optional[str] = None
gpu_vram_total: int = 0
gpu_vram_used: int = 0
ram_total: int = 0
ram_used: int = 0
disk_total: int = 0
disk_used: int = 0
agent_count_router: int = 0
agent_count_system: int = 0
dagi_router_url: Optional[str] = None
class NodeProfile(BaseModel):
"""Node profile for Node Directory"""
node_id: str
name: str
hostname: Optional[str] = None
roles: List[str] = []
environment: str = "unknown"
status: str = "offline"
gpu_info: Optional[str] = None
agents_total: int = 0
agents_online: int = 0
last_heartbeat: Optional[str] = None
guardian_agent_id: Optional[str] = None
steward_agent_id: Optional[str] = None
guardian_agent: Optional[NodeAgentSummary] = None
steward_agent: Optional[NodeAgentSummary] = None
microdaos: List[NodeMicrodaoSummary] = []
metrics: Optional[NodeMetrics] = None
class ModelBindings(BaseModel):
"""Agent model bindings for AI capabilities"""
primary_model: Optional[str] = None # e.g., "qwen3:8b"
supported_kinds: List[str] = [] # e.g., ["text", "vision", "audio"]
class UsageStats(BaseModel):
"""Agent usage statistics"""
tokens_total_24h: Optional[int] = None
calls_total_24h: Optional[int] = None
last_active: Optional[str] = None
class MicrodaoBadge(BaseModel):
"""MicroDAO badge for agent display"""
id: str
name: str
slug: Optional[str] = None
role: Optional[str] = None # orchestrator, member, etc.
is_public: bool = True
is_platform: bool = False
logo_url: Optional[str] = None
banner_url: Optional[str] = None
class AgentCrewInfo(BaseModel):
"""Information about agent's CrewAI team"""
has_crew_team: bool
crew_team_key: Optional[str] = None
matrix_room_id: Optional[str] = None
class AgentSummary(BaseModel):
"""Unified Agent summary for Agent Console and Citizens"""
id: str
slug: Optional[str] = None
display_name: str
title: Optional[str] = None # public_title
tagline: Optional[str] = None # public_tagline
kind: str = "assistant"
avatar_url: Optional[str] = None
status: str = "offline"
# Node info
node_id: Optional[str] = None
node_label: Optional[str] = None # "НОДА1" / "НОДА2"
home_node: Optional[HomeNodeView] = None
# Governance & DAIS (A1, A2)
gov_level: Optional[str] = None # personal, core_team, orchestrator, district_lead, city_governance
dais_identity_id: Optional[str] = None # DAIS identity reference
# Visibility & roles
visibility_scope: str = "city" # global, microdao, private
is_listed_in_directory: bool = True
is_system: bool = False
is_public: bool = False
is_orchestrator: bool = False # Can create/manage microDAOs
# MicroDAO (A3)
primary_microdao_id: Optional[str] = None
primary_microdao_name: Optional[str] = None
primary_microdao_slug: Optional[str] = None
home_microdao_id: Optional[str] = None # Owner microDAO
home_microdao_name: Optional[str] = None
home_microdao_slug: Optional[str] = None
district: Optional[str] = None
microdaos: List[MicrodaoBadge] = []
microdao_memberships: List[Dict[str, Any]] = [] # backward compatibility
# Skills
public_skills: List[str] = []
# CrewAI
crew_info: Optional[AgentCrewInfo] = None
# Future: model bindings and usage stats
model_bindings: Optional[ModelBindings] = None
usage_stats: Optional[UsageStats] = None
class PublicCitizenSummary(BaseModel):
slug: Optional[str] = None # Can be None if public_slug not set
display_name: str
public_title: Optional[str] = None
public_tagline: Optional[str] = None
avatar_url: Optional[str] = None
kind: Optional[str] = None
district: Optional[str] = None
primary_room_slug: Optional[str] = None
public_skills: List[str] = []
online_status: Optional[str] = "unknown"
status: Optional[str] = None # backward compatibility
# Home node info
home_node: Optional[HomeNodeView] = None
node_id: Optional[str] = None
# TASK 037A: Alignment
home_microdao_slug: Optional[str] = None
home_microdao_name: Optional[str] = None
primary_city_room: Optional["CityRoomSummary"] = None
class PublicCitizenProfile(BaseModel):
slug: str
display_name: str
kind: Optional[str] = None
public_title: Optional[str] = None
public_tagline: Optional[str] = None
district: Optional[str] = None
avatar_url: Optional[str] = None
status: Optional[str] = None
node_id: Optional[str] = None
public_skills: List[str] = []
city_presence: Optional[CityPresenceView] = None
dais_public: Dict[str, Any]
interaction: Dict[str, Any]
metrics_public: Dict[str, Any]
admin_panel_url: Optional[str] = None
microdao: Optional[Dict[str, Any]] = None
# Home node info
home_node: Optional[HomeNodeView] = None
class CitizenInteractionInfo(BaseModel):
slug: str
display_name: str
primary_room_slug: Optional[str] = None
primary_room_id: Optional[str] = None
primary_room_name: Optional[str] = None
matrix_user_id: Optional[str] = None
district: Optional[str] = None
microdao_slug: Optional[str] = None
microdao_name: Optional[str] = None
class CitizenAskRequest(BaseModel):
question: str
context: Optional[str] = None
class CitizenAskResponse(BaseModel):
answer: str
agent_display_name: str
agent_id: str
# =============================================================================
# MicroDAO
# =============================================================================
class MicrodaoCitizenView(BaseModel):
slug: str
display_name: str
public_title: Optional[str] = None
public_tagline: Optional[str] = None
avatar_url: Optional[str] = None
district: Optional[str] = None
primary_room_slug: Optional[str] = None
class MicrodaoSummary(BaseModel):
"""MicroDAO summary for list view"""
id: str
slug: str
name: str
description: Optional[str] = None
district: Optional[str] = None
# Visibility & type
is_public: bool = True
is_platform: bool = False # Is a platform/district
is_active: bool = True
# Pinning & ordering
is_pinned: bool = False # Pinned to top of list
pinned_weight: int = 0 # Order within pinned items (1-N)
# Orchestrator
orchestrator_agent_id: Optional[str] = None
orchestrator_agent_name: Optional[str] = None
# Hierarchy
parent_microdao_id: Optional[str] = None
parent_microdao_slug: Optional[str] = None
# Stats
logo_url: Optional[str] = None
banner_url: Optional[str] = None
member_count: int = 0 # alias for agents_count
agents_count: int = 0 # backward compatibility
room_count: int = 0 # alias for rooms_count
rooms_count: int = 0 # backward compatibility
channels_count: int = 0
class MicrodaoChannelView(BaseModel):
"""Channel/integration view for MicroDAO"""
kind: str # 'matrix' | 'telegram' | 'city_room' | 'crew'
ref_id: str
display_name: Optional[str] = None
is_primary: bool
class MicrodaoAgentView(BaseModel):
"""Agent view within MicroDAO"""
agent_id: str
display_name: str
role: Optional[str] = None
is_core: bool
class CityRoomSummary(BaseModel):
"""Summary of a city room for chat embedding and multi-room support"""
id: str
slug: str
name: str
matrix_room_id: Optional[str] = None
microdao_id: Optional[str] = None
microdao_slug: Optional[str] = None
room_role: Optional[str] = None # 'primary', 'lobby', 'team', 'research', 'security', 'governance', 'orchestrator_team'
is_public: bool = True
sort_order: int = 100
logo_url: Optional[str] = None
banner_url: Optional[str] = None
class MicrodaoRoomsList(BaseModel):
"""List of rooms belonging to a MicroDAO"""
microdao_id: str
microdao_slug: str
rooms: List[CityRoomSummary] = []
class MicrodaoRoomUpdate(BaseModel):
"""Update request for MicroDAO room settings"""
room_role: Optional[str] = None
is_public: Optional[bool] = None
sort_order: Optional[int] = None
set_primary: Optional[bool] = None # if true, mark as primary
class AttachExistingRoomRequest(BaseModel):
"""Request to attach an existing city room to a MicroDAO"""
room_id: str
room_role: Optional[str] = None
is_public: bool = True
sort_order: int = 100
class CreateMicrodaoRoomRequest(BaseModel):
"""Request to create a new room for a MicroDAO"""
name: str
description: Optional[str] = None
room_role: str = "general" # primary, lobby, team, research, governance, etc.
is_public: bool = True
zone_key: Optional[str] = None
class MicrodaoDetail(BaseModel):
"""Full MicroDAO detail view"""
id: str
slug: str
name: str
description: Optional[str] = None
district: Optional[str] = None
# Visibility & type
is_public: bool = True
is_platform: bool = False
is_active: bool = True
# Orchestrator
orchestrator_agent_id: Optional[str] = None
orchestrator_display_name: Optional[str] = None
# Hierarchy
parent_microdao_id: Optional[str] = None
parent_microdao_slug: Optional[str] = None
child_microdaos: List["MicrodaoSummary"] = []
# Content
logo_url: Optional[str] = None
banner_url: Optional[str] = None
agents: List[MicrodaoAgentView] = []
channels: List[MicrodaoChannelView] = []
# Multi-room support
rooms: List[CityRoomSummary] = []
public_citizens: List[MicrodaoCitizenView] = []
# Primary city room for chat
primary_city_room: Optional[CityRoomSummary] = None
class AgentMicrodaoMembership(BaseModel):
microdao_id: str
microdao_slug: str
microdao_name: str
logo_url: Optional[str] = None
role: Optional[str] = None
is_core: bool = False
class MicrodaoOption(BaseModel):
id: str
slug: str
name: str
district: Optional[str] = None
is_active: bool = True
# =============================================================================
# Agent Management (Create/Delete)
# =============================================================================
class CreateAgentRequest(BaseModel):
"""Request to create a new agent"""
slug: str
display_name: str
kind: str = "assistant" # assistant, orchestrator, specialist, civic
role: Optional[str] = None
model: Optional[str] = None
node_id: Optional[str] = None
home_node_id: Optional[str] = None
home_microdao_id: Optional[str] = None
district: Optional[str] = None
primary_room_slug: Optional[str] = None
avatar_url: Optional[str] = None
color_hint: Optional[str] = None
is_public: bool = False
is_orchestrator: bool = False
priority: str = "medium"
class CreateAgentResponse(BaseModel):
"""Response after creating an agent"""
id: str
slug: str
display_name: str
kind: str
node_id: Optional[str] = None
home_microdao_id: Optional[str] = None
district: Optional[str] = None
created_at: datetime
class DeleteAgentResponse(BaseModel):
"""Response after deleting an agent"""
ok: bool
message: str
agent_id: str
# =============================================================================
# Visibility Updates (Task 029)
# =============================================================================
class AgentVisibilityUpdate(BaseModel):
"""Update agent visibility settings"""
is_public: bool
visibility_scope: Optional[str] = None # 'global' | 'microdao' | 'private'
class MicrodaoVisibilityUpdate(BaseModel):
"""Update MicroDAO visibility settings"""
is_public: bool
is_platform: Optional[bool] = None # Upgrade to platform/district
class MicrodaoCreateRequest(BaseModel):
"""Request to create MicroDAO from agent (orchestrator flow)"""
name: str
slug: str
description: Optional[str] = None
make_platform: bool = False # If true -> is_platform = true
is_public: bool = True
parent_microdao_id: Optional[str] = None
class SwapperModel(BaseModel):
"""Model info from Swapper service"""
name: str
loaded: bool
type: Optional[str] = None
vram_gb: Optional[float] = None
class NodeSwapperDetail(BaseModel):
"""Detailed Swapper info for Node Cabinet"""
node_id: str
healthy: bool
models_loaded: int
models_total: int
models: List[SwapperModel] = []
# =============================================================================
# DAGI Router
# =============================================================================
class DagiRouterHealth(BaseModel):
"""DAGI Router health status"""
node_id: str
status: str # "up", "down", "degraded"
version: Optional[str] = None
agent_count: int = 0
latency_ms: Optional[float] = None
class DagiRouterAgent(BaseModel):
"""Agent info from DAGI Router"""
id: str
name: Optional[str] = None
kind: Optional[str] = None
runtime: Optional[str] = None # e.g. "NODE1-router", "NODE2-router"
node_id: str
last_seen_at: Optional[datetime] = None
status: str = "active" # "active", "phantom", "stale"
has_db_record: bool = False
class DagiRouterAgentsResponse(BaseModel):
"""Response for DAGI Router agents endpoint"""
node_id: str
total: int = 0
active: int = 0
phantom: int = 0
stale: int = 0
agents: List[DagiRouterAgent] = []
class DagiRouterSummary(BaseModel):
"""Summary of DAGI Router status for a node"""
node_id: str
status: str # "up", "down", "degraded"
version: Optional[str] = None
latency_ms: Optional[float] = None
router_agent_count: int = 0
db_agent_count: int = 0
active: int = 0
phantom: int = 0
stale: int = 0
last_audit_at: Optional[datetime] = None
# =============================================================================
# MicroDAO Activity & Dashboard
# =============================================================================
class MicrodaoActivity(BaseModel):
"""Activity feed item for MicroDAO"""
id: UUID
microdao_slug: str
kind: str # 'post', 'event', 'update'
title: Optional[str] = None
body: str
author_agent_id: Optional[UUID] = None
author_name: Optional[str] = None
created_at: datetime
class CreateMicrodaoActivity(BaseModel):
"""Request to create a new activity item"""
kind: str = Field(pattern="^(post|event|update)$")
title: Optional[str] = None
body: str
author_agent_id: Optional[UUID] = None
author_name: Optional[str] = None
class MicrodaoStats(BaseModel):
"""Statistics for MicroDAO dashboard"""
rooms_count: int
citizens_count: int
agents_count: int
last_update_at: Optional[datetime] = None
class MicrodaoDashboard(BaseModel):
"""Complete dashboard data for a MicroDAO"""
microdao: MicrodaoSummary
stats: MicrodaoStats
recent_activity: List[MicrodaoActivity]
rooms: List[CityRoomSummary]
citizens: List[PublicCitizenSummary]