"""Pydantic request/response models for the platform SDK REST API.""" from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, Field class TaskConstraintsModel(BaseModel): driver_type: str | None = None capability_tags: list[str] = Field(default_factory=list) target_host_id: str | None = Field(default=None, min_length=1) target_device_id: str | None = Field(default=None, min_length=1) class TaskSubmissionRequest(BaseModel): goal: str | None = None workflow_definition_id: str | None = None constraints: TaskConstraintsModel = Field(default_factory=TaskConstraintsModel) class TaskSubmissionResponse(BaseModel): task_id: str class TaskStatusResponse(BaseModel): id: str status: str goal: str | None = None workflow_definition_id: str | None = None assigned_device_id: str | None = None assigned_host_id: str | None = None attempt_count: int = 0 lease_expires_at: datetime | None = None failure_reason: str | None = None target_host_id: str | None = None target_device_id: str | None = None class TaskListItem(BaseModel): id: str status: str goal: str | None = None workflow_definition_id: str | None = None assigned_device_id: str | None = None assigned_host_id: str | None = None attempt_count: int = 0 failure_reason: str | None = None target_host_id: str | None = None target_device_id: str | None = None created_at: datetime class TaskListResponse(BaseModel): items: list[TaskListItem] total: int limit: int offset: int class TaskAttemptResponse(BaseModel): task_id: str attempt: int lease_id: str host_id: str device_id: str status: str lease_expires_at: datetime created_at: datetime completed_at: datetime | None = None failure_reason: str | None = None terminal_result: dict[str, Any] | None = None class DeviceResponse(BaseModel): device_id: str host_id: str driver_type: str status: str capability_tags: list[str] = Field(default_factory=list) class HostResponse(BaseModel): host_id: str address: str | None = None last_seen_at: str planner_transport: Literal["direct", "cloud"] = "direct" class PluginRegistrationRequest(BaseModel): name: str version: str entry_point_kind: Literal["driver", "tool", "skill"] target: str class PluginResponse(BaseModel): name: str version: str entry_point_kind: str target: str wired: bool class LoginRequest(BaseModel): username: str = Field(min_length=1, max_length=64) password: str = Field(min_length=1, max_length=256) class PasswordChangeRequest(BaseModel): current_password: str = Field(min_length=1, max_length=256) new_password: str = Field(min_length=1, max_length=256) class UserResponse(BaseModel): id: str username: str display_name: str role: Literal["viewer", "operator", "admin"] enabled: bool must_change_password: bool scopes: list[str] created_at: datetime updated_at: datetime last_login_at: datetime | None = None class UserListResponse(BaseModel): items: list[UserResponse] limit: int offset: int class UserCreateRequest(BaseModel): username: str = Field(min_length=1, max_length=64) display_name: str = Field(min_length=1, max_length=120) role: Literal["viewer", "operator", "admin"] password: str = Field(min_length=1, max_length=256) class UserUpdateRequest(BaseModel): display_name: str | None = Field(default=None, min_length=1, max_length=120) role: Literal["viewer", "operator", "admin"] | None = None enabled: bool | None = None class PasswordResetRequest(BaseModel): password: str = Field(min_length=1, max_length=256) class ErrorResponse(BaseModel): detail: str class DeviceTargetModel(BaseModel): host_id: str = Field(min_length=1) device_id: str = Field(min_length=1) class UserSubmissionPolicyRequest(BaseModel): submission_enabled: bool = True allowed_host_ids: list[str] | None = None allowed_device_targets: list[DeviceTargetModel] | None = None expected_revision: int | None = Field(default=None, ge=0) class UserSubmissionPolicyResponse(BaseModel): user_id: str revision: int submission_enabled: bool allowed_host_ids: list[str] | None = None allowed_device_targets: list[DeviceTargetModel] | None = None updated_at: datetime class HostGovernancePolicyRequest(BaseModel): self_submission_enabled: bool = True max_active_tasks: int | None = Field(default=None, ge=1) daily_token_budget: int | None = Field(default=None, ge=1) expected_revision: int | None = Field(default=None, ge=0) class HostGovernancePolicyResponse(BaseModel): host_id: str revision: int self_submission_enabled: bool max_active_tasks: int | None = None daily_token_budget: int | None = None updated_at: datetime class HostTokenUsageSummaryResponse(BaseModel): host_id: str usage_day: str daily_token_budget: int | None = None used_tokens: int reserved_tokens: int remaining_tokens: int | None = None class TokenUsageEventResponse(BaseModel): id: str host_id: str usage_day: str task_id: str | None = None attempt: int | None = None provider: str model: str input_tokens: int | None = None output_tokens: int | None = None total_tokens: int occurred_at: datetime