"""Config profile request/response schemas.""" from typing import Any from pydantic import BaseModel, Field, field_validator MAX_MOUNT_PATH_LENGTH = 1024 class ConfigProfileCreate(BaseModel): name: str = Field(description="Profile name (unique per user)") description: str | None = Field(default=None, description="Optional description") @field_validator("name") @classmethod def validate_name(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("Profile name cannot be empty") if len(v) > 255: raise ValueError("Profile name must be 255 characters or less") return v class ConfigProfileUpdate(BaseModel): name: str | None = Field(default=None, description="Profile name") description: str | None = Field(default=None, description="Optional description") @field_validator("name") @classmethod def validate_name(cls, v: str | None) -> str | None: if v is None: return v v = v.strip() if not v: raise ValueError("Profile name cannot be empty") if len(v) > 255: raise ValueError("Profile name must be 255 characters or less") return v class ConfigProfileResponse(BaseModel): id: str user_id: str name: str description: str | None created_at: str updated_at: str class ConfigProfileDetailResponse(ConfigProfileResponse): includes: list[dict[str, Any]] mounts: list[dict[str, Any]] class ConfigIncludeCreate(BaseModel): included_profile_id: str = Field(description="UUID of the profile to include") order_index: int = Field(default=0, description="Order index for include resolution") class ConfigIncludeUpdate(BaseModel): order_index: int = Field(description="Order index for include resolution") class ConfigIncludeResponse(BaseModel): id: str profile_id: str included_profile_id: str included_profile_name: str | None order_index: int created_at: str updated_at: str class ConfigMountCreate(BaseModel): target_path: str = Field(description="Absolute target path in container") mode: str = Field(default="rw", description="Mount mode (rw or ro)") files: dict[str, str] | None = Field( default=None, description="Files as {path: content}" ) order_index: int = Field(default=0, description="Order index for mount resolution") @field_validator("target_path") @classmethod def validate_target_path(cls, v: str) -> str: if not v.startswith("/"): raise ValueError("Target path must be absolute (start with /)") if ".." in v: raise ValueError("Target path cannot contain parent directory references (..)") if len(v) > MAX_MOUNT_PATH_LENGTH: raise ValueError(f"Target path must be {MAX_MOUNT_PATH_LENGTH} characters or less") return v class ConfigMountUpdate(BaseModel): target_path: str | None = Field(default=None, description="Absolute target path in container") mode: str | None = Field(default=None, description="Mount mode (rw or ro)") files: dict[str, str] | None = Field( default=None, description="Files as {path: content}" ) order_index: int | None = Field(default=None, description="Order index for mount resolution") @field_validator("target_path") @classmethod def validate_target_path(cls, v: str | None) -> str | None: if v is None: return v if not v.startswith("/"): raise ValueError("Target path must be absolute (start with /)") if ".." in v: raise ValueError("Target path cannot contain parent directory references (..)") if len(v) > MAX_MOUNT_PATH_LENGTH: raise ValueError(f"Target path must be {MAX_MOUNT_PATH_LENGTH} characters or less") return v class ConfigMountResponse(BaseModel): id: str profile_id: str target_path: str mode: str files: dict[str, str] | None order_index: int created_at: str updated_at: str class DefaultProfilesUpdate(BaseModel): default_profiles: dict[str, str] = Field( description="Mapping of tool_type_id to profile_id" )