Include full contents of all nested repositories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 16:25:02 +01:00
parent 14ff8fd54c
commit 2401ed446f
7271 changed files with 1310112 additions and 6 deletions

View File

@@ -0,0 +1,60 @@
"""Pydantic schemas for API request/response validation."""
from app.schemas.common import HealthResponse, InstanceMetaResponse
from app.schemas.tenant import TenantCreate, TenantResponse
from app.schemas.task import (
TaskCreate,
TaskResponse,
TaskUpdate,
)
from app.schemas.agent import (
AgentRegisterRequest,
AgentRegisterResponse,
AgentHeartbeatResponse,
AgentResponse,
LocalAgentRegisterRequest,
LocalAgentRegisterResponse,
)
from app.schemas.tasks_extended import (
FileWritePayload,
EnvUpdatePayload,
DockerReloadPayload,
CompositeSubTask,
CompositePayload,
)
from app.schemas.env import (
EnvInspectRequest,
EnvUpdateRequest,
)
from app.schemas.file import FileInspectRequest
__all__ = [
# Common
"HealthResponse",
"InstanceMetaResponse",
# Tenant
"TenantCreate",
"TenantResponse",
# Task
"TaskCreate",
"TaskResponse",
"TaskUpdate",
# Task Payloads (for documentation/reference)
"FileWritePayload",
"EnvUpdatePayload",
"DockerReloadPayload",
"CompositeSubTask",
"CompositePayload",
# Agent
"AgentRegisterRequest",
"AgentRegisterResponse",
"AgentHeartbeatResponse",
"AgentResponse",
"LocalAgentRegisterRequest",
"LocalAgentRegisterResponse",
# Env Management
"EnvInspectRequest",
"EnvUpdateRequest",
# File Management
"FileInspectRequest",
]

View File

@@ -0,0 +1,111 @@
"""Agent schemas for API validation."""
import uuid
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
class AgentRegisterRequest(BaseModel):
"""Schema for agent registration request (new secure flow)."""
hostname: str = Field(..., min_length=1, max_length=255)
version: str = Field(..., min_length=1, max_length=50)
metadata: dict[str, Any] | None = None
registration_token: str = Field(
...,
min_length=1,
description="Registration token issued by the orchestrator",
)
class AgentRegisterRequestLegacy(BaseModel):
"""Schema for legacy agent registration request (deprecated).
This schema is kept for backward compatibility during migration.
New agents should use AgentRegisterRequest with registration_token.
"""
hostname: str = Field(..., min_length=1, max_length=255)
version: str = Field(..., min_length=1, max_length=50)
metadata: dict[str, Any] | None = None
tenant_id: uuid.UUID | None = Field(
default=None,
description="Tenant UUID to associate the agent with (DEPRECATED)",
)
class AgentRegisterResponse(BaseModel):
"""Schema for agent registration response."""
agent_id: uuid.UUID
agent_secret: str = Field(
...,
description="Agent secret for authentication. Store securely - shown only once.",
)
tenant_id: uuid.UUID = Field(
...,
description="Tenant this agent is associated with",
)
class LocalAgentRegisterRequest(BaseModel):
"""Schema for LOCAL_MODE agent registration request.
Unlike AgentRegisterRequest, this does NOT include registration_token
because LOCAL_MODE uses X-Local-Agent-Key header authentication.
"""
hostname: str = Field(..., min_length=1, max_length=255)
version: str = Field(..., min_length=1, max_length=50)
metadata: dict[str, Any] | None = None
class LocalAgentRegisterResponse(BaseModel):
"""Schema for LOCAL_MODE agent registration response (idempotent).
This endpoint is idempotent:
- First registration: returns agent_id, agent_secret, already_registered=False
- Subsequent calls: returns agent_id, NO secret, already_registered=True
- With rotate=True: deletes existing, returns new credentials
"""
agent_id: uuid.UUID
tenant_id: uuid.UUID
agent_secret: str | None = Field(
default=None,
description="Agent secret. Only returned on first registration or rotation.",
)
already_registered: bool = Field(
default=False,
description="True if returning existing agent (no new secret).",
)
class AgentRegisterResponseLegacy(BaseModel):
"""Schema for legacy agent registration response (deprecated)."""
agent_id: uuid.UUID
token: str
class AgentHeartbeatResponse(BaseModel):
"""Schema for agent heartbeat response."""
status: str = "ok"
class AgentResponse(BaseModel):
"""Schema for agent response."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
tenant_id: uuid.UUID | None
name: str
version: str
status: str
last_heartbeat: datetime | None
created_at: datetime
updated_at: datetime

View File

@@ -0,0 +1,39 @@
"""Common schemas used across the API."""
from typing import Generic, Optional, TypeVar
from pydantic import BaseModel
T = TypeVar("T")
class HealthResponse(BaseModel):
"""Health check response schema."""
status: str
version: str
class InstanceMetaResponse(BaseModel):
"""
Instance metadata response.
This endpoint is stable even before tenant bootstrap completes.
Used for diagnostics and instance identification.
"""
instance_id: Optional[str] = None
local_mode: bool
version: str
tenant_id: Optional[str] = None
bootstrap_status: dict
class PaginatedResponse(BaseModel, Generic[T]):
"""Generic paginated response wrapper."""
items: list[T]
total: int
page: int
page_size: int
total_pages: int

View File

@@ -0,0 +1,32 @@
"""Pydantic schemas for env management endpoints."""
import uuid
from pydantic import BaseModel, Field
class EnvInspectRequest(BaseModel):
"""Request body for env inspect endpoint."""
tenant_id: uuid.UUID = Field(..., description="UUID of the tenant")
path: str = Field(
..., min_length=1, description="Path to .env file (e.g., /opt/letsbe/env/chatwoot.env)"
)
keys: list[str] | None = Field(
None, description="Optional list of specific keys to inspect"
)
class EnvUpdateRequest(BaseModel):
"""Request body for env update endpoint."""
tenant_id: uuid.UUID = Field(..., description="UUID of the tenant")
path: str = Field(
..., min_length=1, description="Path to .env file (e.g., /opt/letsbe/env/chatwoot.env)"
)
updates: dict[str, str] | None = Field(
None, description="Key-value pairs to set or update"
)
remove_keys: list[str] | None = Field(
None, description="Keys to remove from the env file"
)

View File

@@ -0,0 +1,37 @@
"""Event schemas for API validation."""
import uuid
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
class EventCreate(BaseModel):
"""Schema for creating a new event."""
tenant_id: uuid.UUID
task_id: uuid.UUID | None = None
event_type: str = Field(
...,
min_length=1,
max_length=100,
description="Event type identifier (e.g. agent.registered, task.completed)",
)
payload: dict[str, Any] = Field(
default_factory=dict,
description="Event-specific payload data",
)
class EventResponse(BaseModel):
"""Schema for event response."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
tenant_id: uuid.UUID
task_id: uuid.UUID | None
event_type: str
payload: dict[str, Any]
created_at: datetime

View File

@@ -0,0 +1,20 @@
"""Pydantic schemas for file management endpoints."""
import uuid
from pydantic import BaseModel, Field
class FileInspectRequest(BaseModel):
"""Request body for FILE_INSPECT tasks."""
tenant_id: uuid.UUID = Field(..., description="UUID of the tenant")
path: str = Field(
..., min_length=1, description="Absolute path to the file to inspect"
)
max_bytes: int | None = Field(
4096,
ge=1,
le=1048576,
description="Max bytes to read from file (default 4096, max 1MB)",
)

View File

@@ -0,0 +1,63 @@
"""Registration token schemas for API validation."""
import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
class RegistrationTokenCreate(BaseModel):
"""Schema for creating a new registration token."""
description: str | None = Field(
default=None,
max_length=255,
description="Human-readable description for this token",
)
max_uses: int = Field(
default=1,
ge=0,
description="Maximum number of times this token can be used (0 = unlimited)",
)
expires_in_hours: int | None = Field(
default=None,
ge=1,
le=8760, # Max 1 year
description="Number of hours until this token expires (optional)",
)
class RegistrationTokenResponse(BaseModel):
"""Schema for registration token response (without plaintext token)."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
tenant_id: uuid.UUID
description: str | None
max_uses: int
use_count: int
expires_at: datetime | None
revoked: bool
created_at: datetime
created_by: str | None
class RegistrationTokenCreatedResponse(RegistrationTokenResponse):
"""Schema for registration token creation response.
This is the only time the plaintext token is returned to the client.
It must be securely stored as it cannot be retrieved again.
"""
token: str = Field(
...,
description="The plaintext registration token. Store this securely - it cannot be retrieved again.",
)
class RegistrationTokenList(BaseModel):
"""Schema for listing registration tokens."""
tokens: list[RegistrationTokenResponse]
total: int

View File

@@ -0,0 +1,70 @@
"""Task schemas for API validation."""
import uuid
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from app.models.task import TaskStatus
class TaskCreate(BaseModel):
"""
Schema for creating a new task.
Supported task types and their expected payloads:
**FILE_WRITE** - Write content to a file
payload: {"path": "/absolute/path", "content": "file content"}
**ENV_UPDATE** - Update key/value pairs in a .env file
payload: {"path": "/path/to/.env", "updates": {"KEY": "value"}}
**DOCKER_RELOAD** - Reload a Docker Compose stack
payload: {"compose_dir": "/path/to/compose/dir"}
**COMPOSITE** - Execute a sequence of sub-tasks
payload: {"sequence": [{"task": "FILE_WRITE", "payload": {...}}, ...]}
Legacy types (still supported):
- provision_server, configure_keycloak, configure_minio, etc.
Note: Payload validation is performed agent-side. The orchestrator
accepts any dict payload to allow flexibility and forward compatibility.
"""
tenant_id: uuid.UUID
type: str = Field(
...,
min_length=1,
max_length=100,
description="Task type (FILE_WRITE, ENV_UPDATE, DOCKER_RELOAD, COMPOSITE, etc.)",
)
payload: dict[str, Any] = Field(
default_factory=dict,
description="Task-specific payload (see docstring for formats)",
)
class TaskUpdate(BaseModel):
"""Schema for updating a task (status and result only)."""
status: TaskStatus | None = None
result: dict[str, Any] | None = None
class TaskResponse(BaseModel):
"""Schema for task response."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
tenant_id: uuid.UUID
agent_id: uuid.UUID | None
type: str
payload: dict[str, Any]
status: str
result: dict[str, Any] | None
created_at: datetime
updated_at: datetime

View File

@@ -0,0 +1,73 @@
"""Extended task payload schemas for SysAdmin Agent automation.
These schemas define the expected payload structure for each task type.
Validation is performed agent-side; the orchestrator accepts any dict payload.
"""
from typing import Any
from pydantic import BaseModel, Field
class FileWritePayload(BaseModel):
"""
Payload for FILE_WRITE task type.
Instructs the agent to write content to a file at the specified path.
"""
path: str = Field(..., description="Absolute path to the target file")
content: str = Field(..., description="Content to write to the file")
class EnvUpdatePayload(BaseModel):
"""
Payload for ENV_UPDATE task type.
Instructs the agent to update key/value pairs in an .env file.
Existing keys are updated; new keys are appended.
"""
path: str = Field(..., description="Absolute path to the .env file")
updates: dict[str, str] = Field(
..., description="Key-value pairs to update or add"
)
class DockerReloadPayload(BaseModel):
"""
Payload for DOCKER_RELOAD task type.
Instructs the agent to reload a Docker Compose stack.
Equivalent to: docker compose down && docker compose up -d
"""
compose_dir: str = Field(
..., description="Directory containing docker-compose.yml"
)
class CompositeSubTask(BaseModel):
"""
A single sub-task within a COMPOSITE task.
Represents one step in a multi-step automation sequence.
"""
task: str = Field(..., description="Task type (e.g., FILE_WRITE, ENV_UPDATE)")
payload: dict[str, Any] = Field(
default_factory=dict, description="Payload for this sub-task"
)
class CompositePayload(BaseModel):
"""
Payload for COMPOSITE task type.
Instructs the agent to execute a sequence of sub-tasks in order.
If any sub-task fails, the sequence stops and the composite task fails.
"""
sequence: list[CompositeSubTask] = Field(
..., description="Ordered list of sub-tasks to execute"
)

View File

@@ -0,0 +1,45 @@
"""Tenant schemas for API validation."""
import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from pydantic import BaseModel, ConfigDict, Field
if TYPE_CHECKING:
from app.models.tenant import Tenant
class TenantCreate(BaseModel):
"""Schema for creating a new tenant."""
name: str = Field(..., min_length=1, max_length=255)
domain: str | None = Field(None, max_length=255)
class TenantResponse(BaseModel):
"""Schema for tenant response."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
name: str
domain: str | None
has_dashboard_token: bool = Field(
default=False,
description="Whether a dashboard token has been configured",
)
created_at: datetime
updated_at: datetime
@classmethod
def from_orm_with_token_check(cls, tenant: "Tenant") -> "TenantResponse":
"""Create response with dashboard token check."""
return cls(
id=tenant.id,
name=tenant.name,
domain=tenant.domain,
has_dashboard_token=tenant.dashboard_token_hash is not None,
created_at=tenant.created_at,
updated_at=tenant.updated_at,
)