446 lines
13 KiB
Python
446 lines
13 KiB
Python
"""Pydantic schemas — API request/response contracts.
|
|
|
|
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
|
|
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 PlanAction(BaseModel):
|
|
type: Literal[
|
|
"create_record",
|
|
"update_record",
|
|
"delete_record",
|
|
"index_document",
|
|
"send_notification",
|
|
]
|
|
table: str | None = None
|
|
data: dict[str, Any] | None = None
|
|
|
|
|
|
class ChatRequest(BaseModel):
|
|
message: str
|
|
context: ChatContext = Field(default_factory=ChatContext)
|
|
execution_mode: Literal["direct", "plan"] = "direct"
|
|
|
|
|
|
class ChatResponse(BaseModel):
|
|
response: str
|
|
actions: list[PlanAction] = Field(default_factory=list)
|
|
|
|
|
|
# ── Execution Plans ──────────────────────────────────────────────────
|
|
|
|
class PlanStep(BaseModel):
|
|
action: str
|
|
prompt_template: str | None = None
|
|
variables: dict[str, Any] | None = None
|
|
data_from_step: int | None = None
|
|
|
|
|
|
class ExecutionPlan(BaseModel):
|
|
agent: str
|
|
steps: list[PlanStep] = Field(default_factory=list)
|
|
|
|
|
|
# ── 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 # encrypted vector + metadata — backend never decrypts
|
|
checksum: str
|
|
|
|
|
|
class VectorUpsertRequest(BaseModel):
|
|
vectors: list[VectorItem]
|
|
|
|
|
|
class VectorSearchRequest(BaseModel):
|
|
query_blob: bytes # encrypted query — backend never decrypts
|
|
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"
|
|
agent_run = "agent_run"
|
|
agent_data = "agent_data"
|
|
agent_complete = "agent_complete"
|
|
device_hello = "device_hello"
|
|
# ── v3 frame types ─────────────────────────────────────────────────
|
|
home_request = "home_request"
|
|
floating_request = "floating_request"
|
|
stream_start = "stream_start"
|
|
stream_text = "stream_text"
|
|
stream_block = "stream_block"
|
|
stream_end = "stream_end"
|
|
floating_domain = "floating_domain"
|
|
data_request = "data_request"
|
|
data_response = "data_response"
|
|
mutation = "mutation"
|
|
|
|
|
|
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)
|
|
|
|
|
|
class WsAgentRun(BaseModel):
|
|
"""Server → Client: trigger an agent run on the connected device."""
|
|
|
|
type: Literal[WsFrameType.agent_run] = WsFrameType.agent_run
|
|
run_id: str
|
|
agent_id: str
|
|
config: dict[str, Any]
|
|
|
|
|
|
class WsAgentData(BaseModel):
|
|
"""Client → Server: files read by the local agent."""
|
|
|
|
type: Literal[WsFrameType.agent_data] = WsFrameType.agent_data
|
|
run_id: str
|
|
files: list[dict[str, Any]]
|
|
|
|
|
|
class WsAgentComplete(BaseModel):
|
|
"""Client → Server: Electron signals it has finished reading files."""
|
|
|
|
type: Literal[WsFrameType.agent_complete] = WsFrameType.agent_complete
|
|
run_id: str
|
|
files_read: int
|
|
errors: list[str] = Field(default_factory=list)
|
|
|
|
|
|
# ── WebSocket v3 Frame Models ─────────────────────────────────────────
|
|
|
|
class WsFloatingScope(BaseModel):
|
|
"""Scope for a floating request — narrows the agent to a specific entity."""
|
|
|
|
type: Literal["task", "project", "note", "checkpoint"]
|
|
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 WsStreamBlock(BaseModel):
|
|
"""Server → Client: structured block (chart, table, entity, timeline)."""
|
|
|
|
type: Literal[WsFrameType.stream_block] = WsFrameType.stream_block
|
|
request_id: str
|
|
block_type: Literal["chart", "entity_ref", "table", "timeline"]
|
|
data: dict[str, Any]
|
|
|
|
|
|
class WsStreamEnd(BaseModel):
|
|
"""Server → Client: signals end of a streaming response."""
|
|
|
|
type: Literal[WsFrameType.stream_end] = WsFrameType.stream_end
|
|
request_id: str
|
|
mutations: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
|
|
|
class WsFloatingDomain(BaseModel):
|
|
"""Server → Client: domain determined for a floating request."""
|
|
|
|
type: Literal[WsFrameType.floating_domain] = WsFrameType.floating_domain
|
|
request_id: str
|
|
domain: Literal["tasks", "checkpoints", "notes", "projects"]
|
|
|
|
|
|
# ── Agent Catalog ─────────────────────────────────────────────────────
|
|
|
|
class AgentCatalogItem(BaseModel):
|
|
type: str
|
|
name: str
|
|
description: str
|
|
config_schema: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
# ── Local Agent Config ────────────────────────────────────────────────
|
|
|
|
class LocalAgentConfigCreate(BaseModel):
|
|
name: str
|
|
device_id: str
|
|
directory_paths: list[str]
|
|
data_types: list[str]
|
|
prompt_template: str
|
|
file_extensions: list[str]
|
|
schedule_cron: str
|
|
|
|
|
|
class LocalAgentConfigUpdate(BaseModel):
|
|
name: str | None = None
|
|
device_id: str | None = None
|
|
directory_paths: list[str] | None = None
|
|
data_types: list[str] | None = None
|
|
prompt_template: str | None = None
|
|
file_extensions: list[str] | None = None
|
|
schedule_cron: str | None = None
|
|
enabled: bool | None = None
|
|
|
|
|
|
class LocalAgentConfigResponse(BaseModel):
|
|
id: str
|
|
name: str
|
|
device_id: str
|
|
directory_paths: list[str]
|
|
data_types: list[str]
|
|
prompt_template: str
|
|
file_extensions: list[str]
|
|
schedule_cron: str
|
|
enabled: bool
|
|
last_run_at: int | None
|
|
created_at: int
|
|
updated_at: int
|
|
|
|
|
|
# ── Cloud Agent Config ────────────────────────────────────────────────
|
|
|
|
class CloudAgentConfigCreate(BaseModel):
|
|
provider: Literal["gmail", "teams", "outlook"]
|
|
name: str
|
|
data_types: list[str]
|
|
prompt_template: str
|
|
oauth_token_encrypted: str
|
|
schedule_cron: str
|
|
filter_config: dict[str, Any] | None = None
|
|
|
|
|
|
class CloudAgentConfigUpdate(BaseModel):
|
|
provider: Literal["gmail", "teams", "outlook"] | None = None
|
|
name: str | None = None
|
|
data_types: list[str] | None = None
|
|
prompt_template: str | None = None
|
|
oauth_token_encrypted: str | None = None
|
|
schedule_cron: str | None = None
|
|
filter_config: dict[str, Any] | None = None
|
|
enabled: bool | None = None
|
|
|
|
|
|
class CloudAgentConfigResponse(BaseModel):
|
|
"""oauth_token_encrypted is intentionally excluded — never returned to clients."""
|
|
|
|
id: str
|
|
provider: Literal["gmail", "teams", "outlook"]
|
|
name: str
|
|
data_types: list[str]
|
|
prompt_template: str
|
|
schedule_cron: str
|
|
filter_config: dict[str, Any] | None
|
|
enabled: bool
|
|
last_run_at: int | None
|
|
created_at: int
|
|
updated_at: int
|
|
|
|
|
|
# ── 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
|
|
|
|
|
|
# ── Chatbot Journey ───────────────────────────────────────────────────
|
|
|
|
class JourneyStartRequest(BaseModel):
|
|
agent_type: Literal["local", "cloud"]
|
|
agent_id: str | None = None
|
|
|
|
|
|
class JourneyMessageRequest(BaseModel):
|
|
session_id: str
|
|
message: str
|
|
|
|
|
|
class JourneyResponse(BaseModel):
|
|
session_id: str
|
|
message: str
|
|
done: bool
|
|
prompt_template: str | None = None
|