"""Pydantic schemas — API request/response contracts. Shared across all services. Mirrors the TypeScript types from the Electron app (src/shared/api-types.ts). """ from __future__ import annotations from enum import Enum from typing import Any, Literal from pydantic import BaseModel, Field # ── Billing ────────────────────────────────────────────────────────── BillingTier = Literal["free", "pro", "power", "team"] # ── Auth ───────────────────────────────────────────────────────────── class AuthTokens(BaseModel): access_token: str refresh_token: str expires_at: int class UserProfile(BaseModel): id: str email: str name: str | None = None surname: str | None = None tier: BillingTier # ── Chat ───────────────────────────────────────────────────────────── class ChatContext(BaseModel): user_profile: dict[str, Any] = Field(default_factory=dict) relevant_documents: list[str] = Field(default_factory=list) recent_tasks: list[dict[str, Any]] = Field(default_factory=list) conversation_history: list[dict[str, Any]] = Field(default_factory=list) class ChatRequest(BaseModel): message: str context: ChatContext = Field(default_factory=ChatContext) class ChatResponse(BaseModel): response: str # ── Backup ─────────────────────────────────────────────────────────── class BackupMetadata(BaseModel): version: int timestamp: int checksum: str chunk_count: int # ── Cloud Storage (E2E encrypted blobs) ────────────────────────────── class StorageRecord(BaseModel): id: str user_id: str table: str blob: bytes checksum: str created_at: int updated_at: int class StorageRecordCreate(BaseModel): table: str blob: bytes checksum: str class StorageRecordUpdate(BaseModel): blob: bytes checksum: str # ── Cloud Vector Store (E2E encrypted vectors) ──────────────────────── class VectorItem(BaseModel): id: str blob: bytes checksum: str class VectorUpsertRequest(BaseModel): vectors: list[VectorItem] class VectorSearchRequest(BaseModel): query_blob: bytes top_k: int = 10 class VectorSearchResult(BaseModel): id: str score: float blob: bytes class VectorSearchResponse(BaseModel): results: list[VectorSearchResult] # ── Plugin Marketplace ──────────────────────────────────────────────── class PluginManifest(BaseModel): id: str name: str description: str version: str author: str permissions: list[str] category: str price_cents: int = 0 class PluginListResponse(BaseModel): plugins: list[PluginManifest] total: int page: int class PluginInstallRequest(BaseModel): plugin_id: str # ── WebSocket Frame Protocol ────────────────────────────────────────── class WsFrameType(str, Enum): # ── v2 frame types (kept for backward compat) ────────────────────── chat_request = "chat_request" text_chunk = "text_chunk" tool_call = "tool_call" tool_result = "tool_result" final = "final" ping = "ping" device_hello = "device_hello" # ── v3 frame types ───────────────────────────────────────────────── home_request = "home_request" floating_request = "floating_request" stream_start = "stream_start" stream_text = "stream_text" stream_end = "stream_end" floating_domain = "floating_domain" data_request = "data_request" data_response = "data_response" mutation = "mutation" # ── v4 journey frame types ──────────────────────────────────────── journey_start = "journey_start" journey_message = "journey_message" journey_reply = "journey_reply" class WsToolCall(BaseModel): """Server → Client: requests a CRUD/vector operation on the local DB.""" type: Literal[WsFrameType.tool_call] = WsFrameType.tool_call id: str action: str table: str | None = None data: dict[str, Any] | None = None filters: dict[str, Any] | None = None vector: list[float] | None = None limit: int | None = None class WsToolResult(BaseModel): """Client → Server: result of a CRUD/vector operation.""" type: Literal[WsFrameType.tool_result] = WsFrameType.tool_result id: str row: dict[str, Any] | None = None rows: list[dict[str, Any]] | None = None results: list[dict[str, Any]] | None = None deleted: bool | None = None ok: bool | None = None error: str | None = None class WsTextChunk(BaseModel): """Server → Client: incremental LLM response text.""" type: Literal[WsFrameType.text_chunk] = WsFrameType.text_chunk text: str class WsFinal(BaseModel): """Server → Client: signals end of response with the complete text.""" type: Literal[WsFrameType.final] = WsFrameType.final response: str # ── WebSocket Agent Frame Protocol ──────────────────────────────────── class WsDeviceHello(BaseModel): """Client → Server: device identification on WS connect.""" type: Literal[WsFrameType.device_hello] = WsFrameType.device_hello device_id: str agent_ids: list[str] = Field(default_factory=list) # ── WebSocket v3 Frame Models ───────────────────────────────────────── class WsFloatingScope(BaseModel): """Scope for a floating request.""" type: Literal["task", "project", "note", "timeline"] id: str | None = None class WsHomeRequest(BaseModel): """Client → Server: Home chat message.""" type: Literal[WsFrameType.home_request] = WsFrameType.home_request message: str conversation_history: list[dict[str, Any]] = Field(default_factory=list) class WsFloatingRequest(BaseModel): """Client → Server: Floating chat message scoped to an entity.""" type: Literal[WsFrameType.floating_request] = WsFrameType.floating_request message: str scope: WsFloatingScope class WsStreamStart(BaseModel): """Server → Client: signals start of a streaming response.""" type: Literal[WsFrameType.stream_start] = WsFrameType.stream_start request_id: str class WsStreamText(BaseModel): """Server → Client: streamed text token.""" type: Literal[WsFrameType.stream_text] = WsFrameType.stream_text request_id: str chunk: str class WsStreamEnd(BaseModel): """Server → Client: signals end of a streaming response.""" type: Literal[WsFrameType.stream_end] = WsFrameType.stream_end request_id: str class WsDomain(BaseModel): """Structured floating domain payload for UI routing decisions.""" type: Literal["task", "timeline", "project", "node"] id: str | None = None section: Literal["task", "timeline", "note"] | None = None class WsFloatingDomain(BaseModel): """Server → Client: domain determined for a floating request.""" type: Literal[WsFrameType.floating_domain] = WsFrameType.floating_domain request_id: str domain: WsDomain # ── Agent Catalog ───────────────────────────────────────────────────── class AgentCatalogItem(BaseModel): type: str name: str description: str class AgentCreationCheckRequest(BaseModel): active_agents: int = Field(ge=0, default=0) class AgentCreationCheckResponse(BaseModel): allowed: bool tier: BillingTier active_agents: int limit: int class AgentTriggerRequest(BaseModel): directory: str = Field(min_length=1) device_id: str = Field(default="") agent_id: str | None = None what_to_extract: list[str] = Field(min_length=1) actions_by_type: dict[str, list[str]] | None = None batch_interval: str = Field(min_length=1) custom_agent_prompt: str = Field(min_length=1) active_agents: int = Field(ge=0, default=0) # ── Agent Run Log ───────────────────────────────────────────────────── class AgentRunLogResponse(BaseModel): id: str agent_id: str agent_type: Literal["local", "cloud"] status: Literal["running", "success", "error", "partial"] items_processed: int items_created: int errors: list[str] started_at: int completed_at: int | None