"""Pydantic request/response schemas.""" from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, EmailStr from app.models.enums import ApprovalStatus, DeviceType, Role, TaskStatus # ---- Auth / users ----------------------------------------------------- class Token(BaseModel): access_token: str token_type: str = "bearer" role: Role email: str class UserCreate(BaseModel): email: EmailStr password: str full_name: str | None = None role: Role = Role.readonly class UserUpdate(BaseModel): full_name: str | None = None role: Role | None = None is_active: bool | None = None password: str | None = None class UserOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int email: str full_name: str | None role: Role is_active: bool created_at: datetime # ---- Inventory (vessels + onboard devices) ------------------------------ class CatalogEntryOut(BaseModel): key: str label: str description: str device_type: str mcp_server: str | None default_port: int default_username: str | None has_default_secret: bool = False secret_kind: str = "password" # password | api_key env_defaults_configured: bool = False asterisk_container: str | None = None # GeneseasX docker container name (e.g. voip) category: str class VesselDeviceSelection(BaseModel): """One catalog slot when creating/updating a vessel.""" catalog_key: str enabled: bool = True port: int | None = None # falls back to catalog default_port address: str | None = None # falls back to vessel public_ip username: str | None = None secret: str | None = None notes: str | None = None class VesselCreate(BaseModel): name: str site: str | None = None public_ip: str | None = None notes: str | None = None devices: list[VesselDeviceSelection] = [] class VesselUpdate(BaseModel): name: str | None = None site: str | None = None public_ip: str | None = None notes: str | None = None devices: list[VesselDeviceSelection] | None = None class DeviceOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str catalog_key: str | None device_type: DeviceType address: str port: int | None mcp_server: str | None username: str | None has_secret: bool = False notes: str | None vessel_id: int | None created_at: datetime class VesselOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str site: str | None public_ip: str | None notes: str | None created_at: datetime class VesselDetail(VesselOut): devices: list[DeviceOut] = [] class DeviceUpdate(BaseModel): port: int | None = None address: str | None = None username: str | None = None secret: str | None = None notes: str | None = None # ---- Tasks ------------------------------------------------------------ class TaskCreate(BaseModel): title: str issue: str device_id: int | None = None vessel_id: int | None = None details: dict[str, Any] | None = None class TaskPreviewIn(BaseModel): title: str = "" issue: str vessel_id: int class TaskPreviewDevice(BaseModel): catalog_key: str name: str label: str checks: list[str] = [] class TaskPreviewOut(BaseModel): vessel_id: int vessel_name: str | None = None devices: list[TaskPreviewDevice] rule_id: str | None = None rule_name: str | None = None severity: str | None = None hints: list[str] = [] warning: str | None = None class TaskContinue(BaseModel): follow_up: str class TaskEscalateLlm(BaseModel): tier: Literal["economy", "premium"] class TaskEventOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int kind: str message: str | None payload: dict[str, Any] | None created_at: datetime class TaskOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int title: str issue: str details: dict[str, Any] | None status: TaskStatus device_id: int | None vessel_id: int | None created_by_id: int | None report: dict[str, Any] | None error: str | None memory_id: str | None obsidian_path: str | None vessel_name: str | None = None created_at: datetime updated_at: datetime class TaskOverviewOut(BaseModel): id: int title: str issue: str status: TaskStatus vessel_id: int | None = None vessel_name: str | None = None summary: str | None = None key_finding: str | None = None devices_checked: list[str] = [] resolved: bool | None = None memory_id: str | None = None obsidian_path: str | None = None obsidian_push_error: str | None = None run_cost_usd: float | None = None created_at: datetime updated_at: datetime class TaskDetail(TaskOut): events: list[TaskEventOut] = [] class ApprovalOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int task_id: int tool_name: str tool_args: dict[str, Any] | None risk: str | None status: ApprovalStatus created_at: datetime class ApprovalDecision(BaseModel): approve: bool # ---- MCP -------------------------------------------------------------- class McpToolOut(BaseModel): name: str description: str | None = None class McpServerOut(BaseModel): name: str status: str # loaded | error | cloning | stopped tools: list[McpToolOut] = [] error: str | None = None source: str | None = None # local | git class McpCommandResult(BaseModel): ok: bool action: str | None = None server: str | None = None error: str | None = None class McpDevelopRequest(BaseModel): issue: str tool: str | None = None task_id: int | None = None push: bool | None = None class McpDevelopResult(BaseModel): ok: bool task_id: int | None = None summary: str | None = None error: str | None = None tools: list[str] | None = None push: dict | None = None # ---- Balance ---------------------------------------------------------- class BalanceOut(BaseModel): model_config = ConfigDict(from_attributes=True) provider: str total_credits: float | None total_usage: float | None remaining: float | None currency: str error: str | None created_at: datetime # ---- LLM routing ------------------------------------------------------ class LlmRoutingConfigIn(BaseModel): local_model_id: str | None = None economy_gemini_id: str | None = None economy_deepseek_id: str | None = None economy_openrouter_id: str | None = None premium_gemini_id: str | None = None premium_deepseek_id: str | None = None premium_openrouter_id: str | None = None cloud_provider_order: list[str] | None = None auto_escalate: bool | None = None max_tier: str | None = None class ProviderModelsOut(BaseModel): providers: dict[str, Any] routing_order: str class LlmRoutingConfigOut(BaseModel): config: dict[str, Any] catalog: dict[str, Any] available_backends: dict[str, bool] strategy: str # ---- Troubleshooting rules -------------------------------------------- class TroubleshootingRulesUpdate(BaseModel): yaml: str class TroubleshootingRulesOut(BaseModel): yaml: str parsed: dict[str, Any] path_hint: str # ---- Agent skills ----------------------------------------------------- class SkillSummaryOut(BaseModel): id: str name: str description: str priority: int rule_ids: list[str] match: dict[str, Any] path_hint: str class SkillsListOut(BaseModel): skills: list[SkillSummaryOut] path_hint: str class SkillOut(BaseModel): id: str name: str description: str priority: int rule_ids: list[str] match: dict[str, Any] markdown: str path_hint: str class SkillUpdate(BaseModel): markdown: str