"""Pydantic contracts for the flows API.""" from datetime import datetime from uuid import UUID from pydantic import BaseModel, Field class FlowDefinitionCreate(BaseModel): name: str = Field(min_length=1, max_length=200) description: str = Field(default="", max_length=2000) continuation_flow_id: UUID | None = None class FlowDefinitionRead(BaseModel): id: UUID name: str description: str status: str # backend/flows filename this definition syncs from (None if API-created). source_path: str | None = None continuation_flow_id: UUID | None created_at: datetime class FlowVersionCreate(BaseModel): langflow_json: dict author: str = Field(default="", max_length=200) class FlowVersionRead(BaseModel): id: UUID flow_id: UUID version_no: int langflow_json: dict lfx_flow_id: str | None deployed_at: datetime | None author: str created_at: datetime input_schema: dict | None = None class InputSchemaRead(BaseModel): """Form schema of the latest deployed version (RJSF renders it).""" flow_id: UUID version_no: int json_schema: dict ui_schema: dict = Field(default_factory=dict) # Set when the flow has no CustomInput node: render a free-text box. fallback: str | None = None class RunRequest(BaseModel): """Start a flow run. `input` is the domain payload handed to the flow. Tenant, callback URL and continuation come from the server (session / settings / task), never from the caller — they must not be client-controllable (SSRF / cross-flow chaining). """ input: dict = Field(default_factory=dict) version_no: int | None = None session_id: str | None = None class RunRead(BaseModel): id: UUID version_id: UUID tenant_id: str status: str input: dict | None output: dict | None session_id: str | None created_at: datetime finished_at: datetime | None class NamedRunRead(BaseModel): """A run with its flow name (GET /api/runs aggregate, kills N+1).""" flow_name: str run: RunRead class ApprovalCreate(BaseModel): """Callback contract: a flow reports a terminal approval state.""" run_id: UUID title: str = Field(default="", max_length=500) payload: dict = Field(default_factory=dict) continuation_flow_id: UUID | None = None class ApprovalRead(BaseModel): id: UUID run_id: UUID version_id: UUID tenant_id: str title: str payload: dict continuation_flow_id: UUID | None approver: str | None status: str decision_note: str created_at: datetime decided_at: datetime | None class FlowImportRequest(BaseModel): """Import a flow from the Langflow authoring server by its Langflow id.""" langflow_flow_id: str = Field(min_length=1) name: str | None = Field(default=None, max_length=200) author: str = Field(default="", max_length=200) deploy: bool = True class FlowImportRead(BaseModel): definition: FlowDefinitionRead version: FlowVersionRead class DecisionRequest(BaseModel): """Decide a task. Identity comes from the session, not the body.""" decision: str = Field(pattern="^(approve|reject)$") note: str = Field(default="", max_length=2000) class DecisionRead(BaseModel): task: ApprovalRead continuation_run: RunRead | None class SyncReport(BaseModel): """Result of the manual full pass: Langflow -> files -> DB + LFX. Lists hold filename stems; errors maps filename (or Langflow id on pull failures) to reason. """ added: list[str] = Field(default_factory=list) updated: list[str] = Field(default_factory=list) deleted: list[str] = Field(default_factory=list) skipped: list[str] = Field(default_factory=list) pulled: list[str] = Field(default_factory=list) errors: dict[str, str] = Field(default_factory=dict)