"""Type definitions for the ToothFairyAI SDK."""

from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Literal, Optional, TypedDict, Union


def _get_workspace_id(data: Dict[str, Any], default: str = "") -> str:
    """Get workspace_id from data, handling both camelCase and lowercase keys.

    Args:
        data: Dictionary containing workspace ID.
        default: Default value if not found.

    Returns:
        Workspace ID string.
    """
    return (
        data.get("workspaceID")
        or data.get("workspaceid")
        or data.get("workspace_id")
        or default
    )


# Type aliases.
EntityType = Literal["intent", "ner", "topic"]
EntityAIType = Literal["ner", "intent", "topic"]
DocumentType = Literal["readComprehensionUrl", "readComprehensionPdf", "readComprehensionFile"]
MessageRole = Literal["user", "assistant", "system"]
AgentMode = Literal["retriever", "coder", "chatter", "planner", "computer", "voice", "accuracy"]

# Enum types from GraphQL schema
AgentType = Literal["Chatbot", "BusinessAnalyst", "ContentCreator", "Default"]
ReasoningMode = Literal["auto", "dynamic", "always", "never"]
ReasoningEffort = Literal["none", "low", "medium", "high"]
PlanType = Literal["base", "pro", "business", "enterprise"]
Department = Literal[
    "HUMAN_RESOURCES",
    "FINANCE_AND_ACCOUNTING",
    "SALES",
    "MARKETING",
    "OPERATIONS",
    "INFORMATION_TECHNOLOGY",
    "CUSTOMER_SERVICE",
    "RESEARCH_AND_DEVELOPMENT",
    "LEGAL_AND_COMPLIANCE",
    "SUPPLY_CHAIN_MANAGEMENT",
    "PROJECT_MANAGEMENT",
    "GENERAL_PURPOSE",
]
QaUrlStatus = Literal["ready", "notReady", "dismissed", "unknown", "swapping"]
QaUrlType = Literal["serverless", "provisioned", "elastic"]
SearchStrategy = Literal["parallel", "targeted_first", "vector_first"]
InternetSearchMode = Literal["search", "news", "images", "videos", "maps", "places", "shopping"]
FunctionScope = Literal["customerAuthentication", "customer", "caseAuthentication", "case"]
AgentFunctionChatAction = Literal[
    "suggestion", "nextQuestion", "humanSupport", "redirection", "script"
]
ChatMode = Literal[
    "speed",
    "accuracy",
    "custom",
    "retriever",
    "coder",
    "mathematician",
    "chatter",
    "planner",
    "computer",
    "voice",
]
FunctionRequestType = Literal[
    "get", "post", "put", "delete", "patch", "custom", "graphql_query", "graphql_mutation"
]
AuthorisationType = Literal[
    "bearer",
    "apikey",
    "oauth",
    "password",
    "env",
    "generic",
    "none",
    "username_and_password",
    "basic",
]
Visibility = Literal["private", "public", "shared"]
WorkspaceAIType = Literal["hierarchical", "flat"]
BaseModelType = Literal["productivity", "sentiment", "none"]
TrainingStatus = Literal[
    "dispatched",
    "inProgress",
    "completed",
    "inError",
    "cancelled",
    "cancellationRequested",
    "training",
    "swapping",
    "swapModelFailed",
    "swapModelRequested",
    "datasetReady",
    "modelReady",
    "fineTuningRequested",
]
BatchJobStatus = Literal[
    "dispatched", "inProgress", "completed", "inError", "cancelled", "cancellationRequested"
]
RequestStatus = Literal[
    "success",
    "failed",
    "timeout",
    "fulfilled",
    "pending",
    "inProgress",
    "cancelled",
    "awaitingUserInput",
]
RequestType = Literal[
    "translator",
    "answerer",
    "parser",
    "tokenizer",
    "embedder",
    "detector",
    "questioner",
    "generator",
    "chatter",
    "planner",
    "analyser",
]
ModelType = Literal[
    "extraSmall", "small", "medium", "large", "extraLarge", "local", "custom", "external"
]
OutputType = Literal[
    "images",
    "videos",
    "modelling",
    "text",
    "code_interpreter",
    "canvas",
    "internet_search",
    "deep_thinking",
    "browser",
    "audio",
    "voice",
    "rag",
]
SiteStatus = Literal[
    "noDomain",
    "noAnswer",
    "active",
    "syncing",
    "inactive",
    "invalidToken",
    "failed",
    "pending",
    "inProgress",
    "validationTokenCreationError",
    "readyForValidation",
    "tokenRequested",
    "noSitemap",
]
FolderStatus = Literal["active", "inactive"]
ChatVisibility = Literal["public", "private", "widget", "job", "incognito"]
HumanFeedback = Literal["positive", "negative", "neutral"]
HumanFeedbackType = Literal[
    "multilanguage",
    "documentation",
    "reasoning",
    "images",
    "charting",
    "api",
    "code",
    "math",
    "moderation",
]
MessageType = Literal["system", "handoff", "notification"]
CommunicationChannel = Literal["email", "sms", "whatsapp", "linkedin", "sip"]
CommunicationProvider = Literal[
    "sendgrid",
    "ses",
    "mailgun",
    "postmark",
    "mailjet",
    "twilio",
    "whatsapp",
    "sms_magic",
    "sms_magic_sf",
    "linkedin",
]
HostingType = Literal[
    "azure_openai",
    "aws",
    "local",
    "google",
    "anthropic",
    "together",
    "tf",
    "replicate",
    "azure",
    "openai",
    "groq",
    "ollama",
]
CronJobStatus = Literal["ACTIVE", "PAUSED", "DISABLED", "RUNNING", "COMPLETED", "FAILED", "PENDING"]
CronJobFrequency = Literal["MINUTES", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY", "CUSTOM"]
UserType = Literal["Root", "SuperAdmin", "ContentAdmin", "BillingAdmin", "AIEngineer", "User"]
SubscriptionStatus = Literal["active", "inactive"]
LogType = Literal["import", "export", "entityDeletion", "documentImport"]
LogStatus = Literal["dispatched", "inProgress", "completed", "inError"]
TrainingLogType = Literal[
    "nlpTraining", "readComprehensionTraining", "generativeTraining", "conversationalTraining"
]
BatchLogType = Literal["nlp", "rc", "gn", "bm", "abm"]
Priority = Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "NONE"]
VoucherStatus = Literal["ACTIVE", "INACTIVE"]
VoucherType = Literal["CREDIT", "FREE_UPGRADE"]
ChunkingStrategy = Literal["keywords", "summary"]
ExtractionStrategy = Literal["safe", "brute"]
GenerationMode = Literal["speed", "accuracy", "custom"]
GenerationDomains = Literal[
    "editorial",
    "customerService",
    "sales",
    "marketing",
    "hr",
    "finance",
    "legal",
    "medical",
    "science",
    "technology",
    "default",
]
GenerationStyles = Literal[
    "default",
    "short",
    "long",
    "funny",
    "informal",
    "formal",
    "interview",
    "academic",
    "business",
    "creative",
    "scientific",
    "poetry",
    "news",
]
GenerationScopes = Literal[
    "generation",
    "tweet",
    "post",
    "title",
    "email",
    "paraphrase",
    "paraphraseLiteral",
    "summary",
    "simplify",
    "custom",
]


# Configuration types
@dataclass
class ToothFairyClientConfig:
    """Configuration for the ToothFairyClient."""

    api_key: str
    workspace_id: str
    base_url: str = "https://api.toothfairyai.com"
    ai_url: str = "https://ai.toothfairyai.com"
    ai_stream_url: str = "https://ais.toothfairyai.com"
    timeout: int = 120  # seconds


# Chat types
@dataclass
class ChannelSettings:
    """Channel settings for a chat."""

    channel: Optional[str] = None
    provider: Optional[str] = None


@dataclass
class Chat:
    """Represents a chat conversation."""

    id: str
    name: str = ""
    primary_role: str = ""
    external_participant_id: str = ""
    channel_settings: Optional[ChannelSettings] = None
    customer_id: str = ""
    customer_info: Dict[str, Any] = field(default_factory=dict)
    is_ai_replying: bool = False
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Chat":
        """Create a Chat instance from a dictionary."""
        channel_settings = None
        if data.get("channelSettings"):
            channel_settings = ChannelSettings(
                channel=data["channelSettings"].get("channel"),
                provider=data["channelSettings"].get("provider"),
            )
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            primary_role=data.get("primaryRole", ""),
            external_participant_id=data.get("externalParticipantId", ""),
            channel_settings=channel_settings,
            customer_id=data.get("customerId", ""),
            customer_info=data.get("customerInfo", {}),
            is_ai_replying=data.get("isAIReplying", False),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


@dataclass
class ChatCreateData:
    """Data for creating a chat."""

    name: str = ""
    customer_id: str = ""
    customer_info: Dict[str, Any] = field(default_factory=dict)
    primary_role: str = "user"
    external_participant_id: str = ""
    channel_settings: Optional[Dict[str, str]] = None


@dataclass
class Message:
    """Represents a chat message."""

    id: str
    chat_id: str
    text: str
    role: MessageRole
    user_id: str = ""
    images: List[str] = field(default_factory=list)
    audios: List[str] = field(default_factory=list)
    videos: List[str] = field(default_factory=list)
    files: List[str] = field(default_factory=list)
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Message":
        """Create a Message instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            chat_id=data.get("chatID", ""),
            text=data.get("text", ""),
            role=data.get("role", "user"),
            user_id=data.get("userID", ""),
            images=data.get("images", []),
            audios=data.get("audios", []),
            videos=data.get("videos", []),
            files=data.get("files", []),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


@dataclass
class MessageCreateData:
    """Data for creating a message."""

    chat_id: str
    text: str
    role: MessageRole = "user"
    user_id: str = ""
    images: List[str] = field(default_factory=list)
    audios: List[str] = field(default_factory=list)
    videos: List[str] = field(default_factory=list)
    files: List[str] = field(default_factory=list)


class Attachments(TypedDict, total=False):
    """Attachments for agent messages."""

    images: List[str]
    audios: List[str]
    videos: List[str]
    files: List[str]


@dataclass
class AgentMessage:
    """Message to send to an agent."""

    role: MessageRole
    content: str


@dataclass
class AgentRequest:
    """Request to send to an agent."""

    messages: List[AgentMessage]
    agentid: str
    chatid: Optional[str] = None
    raw_stream: bool = False
    phone_number: Optional[str] = None
    customer_id: Optional[str] = None
    provider_id: Optional[str] = None
    customer_info: Optional[Dict[str, Any]] = None


@dataclass
class AgentResponse:
    """Response from an agent."""

    chat_id: str
    message_id: str
    agent_response: Any

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "AgentResponse":
        """Create an AgentResponse instance from a dictionary."""
        return cls(
            chat_id=data.get("chatId", ""),
            message_id=data.get("messageId", ""),
            agent_response=data.get("agentResponse"),
        )


# Document types
@dataclass
class Document:
    """Represents a document."""

    id: str
    workspace_id: str = ""
    user_id: str = ""
    doc_type: DocumentType = "readComprehensionFile"
    title: str = ""
    topics: List[str] = field(default_factory=list)
    folder_id: str = ""
    external_path: str = ""
    source: str = ""
    status: str = ""
    scope: Optional[str] = None
    rawtext: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Document":
        """Create a Document instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            user_id=data.get("userid", ""),
            doc_type=data.get("type", "readComprehensionFile"),
            title=data.get("title", ""),
            topics=data.get("topics", []),
            folder_id=data.get("folderid", ""),
            external_path=data.get("external_path", ""),
            source=data.get("source", ""),
            status=data.get("status", ""),
            scope=data.get("scope"),
            rawtext=data.get("rawtext"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


@dataclass
class DocumentCreateData:
    """Data for creating a document."""

    user_id: str
    title: str
    doc_type: DocumentType = "readComprehensionFile"
    topics: List[str] = field(default_factory=list)
    folder_id: str = "mrcRoot"
    external_path: str = ""
    source: str = ""
    status: str = "published"
    scope: Optional[str] = None


@dataclass
class DocumentUpdateData:
    """Data for updating a document."""

    title: Optional[str] = None
    topics: Optional[List[str]] = None
    folder_id: Optional[str] = None
    status: Optional[str] = None
    scope: Optional[str] = None


@dataclass
class FileUploadOptions:
    """Options for file upload."""

    folder_id: str = "mrcRoot"
    on_progress: Optional[Callable[[int, int, int], None]] = None


@dataclass
class Base64FileUploadOptions:
    """Options for base64 file upload."""

    filename: str
    content_type: str
    folder_id: str = "mrcRoot"
    on_progress: Optional[Callable[[int, int, int], None]] = None


@dataclass
class FileUploadResult:
    """Result of a file upload."""

    success: bool
    original_filename: str
    sanitized_filename: str
    filename: str
    import_type: str
    content_type: str
    size: int
    size_in_mb: float

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "FileUploadResult":
        """Create a FileUploadResult instance from a dictionary."""
        return cls(
            success=data.get("success", False),
            original_filename=data.get("originalFilename", ""),
            sanitized_filename=data.get("sanitizedFilename", ""),
            filename=data.get("filename", ""),
            import_type=data.get("importType", ""),
            content_type=data.get("contentType", ""),
            size=data.get("size", 0),
            size_in_mb=data.get("sizeInMB", 0.0),
        )


@dataclass
class FileDownloadOptions:
    """Options for file download."""

    context: str = "documents"
    on_progress: Optional[Callable[[int, int, int], None]] = None


@dataclass
class FileDownloadResult:
    """Result of a file download."""

    success: bool
    filename: str
    output_path: str
    size: int

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "FileDownloadResult":
        """Create a FileDownloadResult instance from a dictionary."""
        return cls(
            success=data.get("success", False),
            filename=data.get("filename", ""),
            output_path=data.get("outputPath", ""),
            size=data.get("size", 0),
        )


# Entity types
@dataclass
class Entity:
    """Represents an entity (topic, intent, or NER)."""

    id: str
    workspace_id: str = ""
    created_by: str = ""
    label: str = ""
    entity_type: EntityType = "topic"
    description: Optional[str] = None
    emoji: Optional[str] = None
    parent_entity: Optional[str] = None
    background_color: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Entity":
        """Create an Entity instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            created_by=data.get("createdBy", ""),
            label=data.get("label", ""),
            entity_type=data.get("type", "topic"),
            description=data.get("description"),
            emoji=data.get("emoji"),
            parent_entity=data.get("parentEntity"),
            background_color=data.get("backgroundColor"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


@dataclass
class EntityCreateOptions:
    """Options for creating an entity."""

    description: Optional[str] = None
    emoji: Optional[str] = None
    parent_entity: Optional[str] = None
    background_color: Optional[str] = None


@dataclass
class EntityUpdateData:
    """Data for updating an entity."""

    label: Optional[str] = None
    description: Optional[str] = None
    emoji: Optional[str] = None
    parent_entity: Optional[str] = None
    background_color: Optional[str] = None


# Folder types
@dataclass
class Folder:
    """Represents a folder."""

    id: str
    workspace_id: str = ""
    created_by: str = ""
    name: str = ""
    description: Optional[str] = None
    emoji: Optional[str] = None
    status: Optional[str] = None
    parent: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Folder":
        """Create a Folder instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            created_by=data.get("createdBy", ""),
            name=data.get("name", ""),
            description=data.get("description"),
            emoji=data.get("emoji"),
            status=data.get("status"),
            parent=data.get("parent"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


@dataclass
class FolderCreateOptions:
    """Options for creating a folder."""

    description: Optional[str] = None
    emoji: Optional[str] = None
    status: str = "active"
    parent: Optional[str] = None


@dataclass
class FolderUpdateData:
    """Data for updating a folder."""

    name: Optional[str] = None
    description: Optional[str] = None
    emoji: Optional[str] = None
    status: Optional[str] = None
    parent: Optional[str] = None


@dataclass
class FolderTreeNode:
    """A folder in a tree structure."""

    id: str
    workspace_id: str
    created_by: str
    name: str
    description: Optional[str]
    emoji: Optional[str]
    status: Optional[str]
    parent: Optional[str]
    created_at: Optional[str]
    updated_at: Optional[str]
    children: List["FolderTreeNode"] = field(default_factory=list)

    @classmethod
    def from_folder(
        cls, folder: Folder, children: Optional[List["FolderTreeNode"]] = None
    ) -> "FolderTreeNode":
        """Create a FolderTreeNode from a Folder."""
        return cls(
            id=folder.id,
            workspace_id=folder.workspace_id,
            created_by=folder.created_by,
            name=folder.name,
            description=folder.description,
            emoji=folder.emoji,
            status=folder.status,
            parent=folder.parent,
            created_at=folder.created_at,
            updated_at=folder.updated_at,
            children=children or [],
        )


# Prompt types (maps to CustomPrompt in GraphQL)
@dataclass
class Prompt:
    """Represents a custom prompt template."""

    id: str
    workspace_id: str = ""
    prompt_type: str = ""
    label: str = ""
    prompt_length: float = 0
    interpolation_string: str = ""
    scope: Optional[str] = None
    style: Optional[str] = None
    domain: Optional[str] = None
    prompt_placeholder: Optional[str] = None
    available_to_agents: List[str] = field(default_factory=list)
    created_by: Optional[str] = None
    updated_by: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Prompt":
        """Create a Prompt instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            prompt_type=data.get("type", ""),
            label=data.get("label", ""),
            prompt_length=data.get("promptLength", 0),
            interpolation_string=data.get("interpolationString", ""),
            scope=data.get("scope"),
            style=data.get("style"),
            domain=data.get("domain"),
            prompt_placeholder=data.get("promptPlaceholder"),
            available_to_agents=data.get("availableToAgents", []),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


# Alias for clarity
CustomPrompt = Prompt


@dataclass
class PromptCreateData:
    """Data for creating a prompt."""

    label: str
    prompt_type: str
    interpolation_string: str
    scope: Optional[str] = None
    style: Optional[str] = None
    domain: Optional[str] = None
    prompt_placeholder: Optional[str] = None
    available_to_agents: List[str] = field(default_factory=list)


@dataclass
class PromptUpdateData:
    """Data for updating a prompt."""

    label: Optional[str] = None
    prompt_type: Optional[str] = None
    interpolation_string: Optional[str] = None
    scope: Optional[str] = None
    style: Optional[str] = None
    domain: Optional[str] = None
    prompt_placeholder: Optional[str] = None
    available_to_agents: Optional[List[str]] = None


# Response types
@dataclass
class ListResponse:
    """Generic list response."""

    items: List[Any]
    total: Optional[int] = None
    limit: Optional[int] = None
    offset: Optional[int] = None


# Streaming types
class StreamingOptions(TypedDict, total=False):
    """Options for streaming requests."""

    chat_id: str
    phone_number: str
    customer_id: str
    provider_id: str
    customer_info: Dict[str, Any]
    attachments: Attachments
    show_progress: bool
    raw_stream: bool


@dataclass
class StreamEventData:
    """Data from a stream event."""

    text: str = ""
    message_type: str = ""
    message_id: str = ""
    chat_id: str = ""

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "StreamEventData":
        """Create a StreamEventData instance from a dictionary."""
        return cls(
            text=data.get("text", ""),
            message_type=data.get("type", ""),
            message_id=data.get("message_id", ""),
            chat_id=data.get("chatId", ""),
        )


@dataclass
class StreamStatusEvent:
    """Status event from a stream."""

    status: str
    processing_status: Optional[str] = None
    metadata_parsed: Optional[Dict[str, Any]] = None


@dataclass
class StreamingResult:
    """Result of a streaming request."""

    chat_id: str
    message_id: str


# Content type mapping
CONTENT_TYPE_MAP: Dict[str, str] = {
    ".pdf": "application/pdf",
    ".doc": "application/msword",
    ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    ".xls": "application/vnd.ms-excel",
    ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ".ppt": "application/vnd.ms-powerpoint",
    ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    ".txt": "text/plain",
    ".csv": "text/csv",
    ".json": "application/json",
    ".xml": "application/xml",
    ".html": "text/html",
    ".htm": "text/html",
    ".md": "text/markdown",
    ".rtf": "application/rtf",
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".png": "image/png",
    ".gif": "image/gif",
    ".bmp": "image/bmp",
    ".webp": "image/webp",
    ".svg": "image/svg+xml",
    ".mp3": "audio/mpeg",
    ".wav": "audio/wav",
    ".ogg": "audio/ogg",
    ".mp4": "video/mp4",
    ".webm": "video/webm",
    ".avi": "video/x-msvideo",
    ".mov": "video/quicktime",
    ".zip": "application/zip",
    ".rar": "application/vnd.rar",
    ".7z": "application/x-7z-compressed",
    ".tar": "application/x-tar",
    ".gz": "application/gzip",
    ".py": "text/x-python",
    ".js": "application/javascript",
    ".ts": "application/typescript",
    ".java": "text/x-java-source",
    ".c": "text/x-c",
    ".cpp": "text/x-c++",
    ".h": "text/x-c",
    ".hpp": "text/x-c++",
    ".cs": "text/x-csharp",
    ".go": "text/x-go",
    ".rs": "text/x-rust",
    ".rb": "text/x-ruby",
    ".php": "text/x-php",
    ".swift": "text/x-swift",
    ".kt": "text/x-kotlin",
    ".scala": "text/x-scala",
}


def get_content_type(filename: str) -> str:
    """Get content type from filename extension."""
    import os

    ext = os.path.splitext(filename)[1].lower()
    return CONTENT_TYPE_MAP.get(ext, "application/octet-stream")


# Max file size constant
MAX_FILE_SIZE_MB = 15
MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024


@dataclass
class Agent:
    """Represents an AI agent."""

    id: str
    workspace_id: str = ""
    type: str = ""
    label: str = ""
    description: Optional[str] = None
    interpolation_string: Optional[str] = None
    emoji: Optional[str] = None
    color: Optional[str] = None
    dark_color: Optional[str] = None
    icon: Optional[str] = None
    large_logo: Optional[str] = None
    ico_url: Optional[str] = None
    logo_background: Optional[str] = None
    dark_logo_background: Optional[str] = None
    forced_theme: Optional[str] = None
    message_on_launch: Optional[str] = None
    messages_on_launch: List[str] = field(default_factory=list)
    rotate_messages_on_launch: bool = False
    rotate_messages_on_launch_interval: Optional[int] = None
    placeholder_message_on_loading: Optional[str] = None
    dynamic_placeholder_message_on_loading: bool = False
    custom_greeting_instructions: Optional[str] = None
    custom_conclusion_instructions: Optional[str] = None
    custom_charting_instructions: Optional[str] = None
    custom_summarisation_instructions: Optional[str] = None
    custom_images_instructions: Optional[str] = None
    custom_tooling_instructions: Optional[str] = None
    placeholder_input_message: Optional[str] = None
    open_by_default: bool = False
    agentic_coding_model: Optional[str] = None
    disclaimer: Optional[str] = None
    show_documents_references: bool = False
    is_default: bool = False
    has_memory: bool = True
    has_images: bool = False
    prompt_top_keywords: int = 5
    key_words_for_knowledge_base: bool = True
    has_topics_context: bool = True
    has_functions: bool = False
    advanced_language_detection: bool = False
    allowed_topics: Dict[str, Any] = field(default_factory=dict)
    static_docs: Dict[str, Any] = field(default_factory=dict)
    default_answer: Optional[str] = None
    no_knowledge_default_answer: Optional[str] = None
    quick_questions: Optional[str] = None
    recency_importance: Optional[float] = None
    max_recency_increase: Optional[float] = None
    recency_top_k_increase_factor: Optional[int] = None
    stop_sequence: Optional[str] = None
    examples: List[Any] = field(default_factory=list)
    enhancement_passage: Optional[str] = None
    inhibition_passage: Optional[str] = None
    pertinence_passage: Optional[str] = None
    goals: Optional[str] = None
    qa_url: Optional[str] = None
    top_k: int = 10
    mode: Optional[ChatMode] = None
    show_code_in_execution: bool = False
    hide_reasoning: bool = False
    charting: bool = False
    summarisation: bool = True
    compressor: bool = False
    doc_top_k: int = 5
    min_retrieval_score: Optional[float] = None
    time_significance: Optional[float] = None
    has_moderation: bool = False
    custom_moderations: Dict[str, Any] = field(default_factory=dict)
    moderation_message: Optional[str] = None
    language_detection_confidence: Optional[float] = None
    max_history: int = 10
    max_tokens: int = 1000
    topic_enhancer: bool = False
    max_topics: int = 3
    blender: bool = False
    temperature: float = 0.7
    version: int = 1
    subject: Optional[str] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None
    agent_functions: List[str] = field(default_factory=list)
    is_global: bool = False
    prompt_buffering: bool = False
    function_context: Dict[str, Any] = field(default_factory=dict)
    plain_text_output: bool = False
    chain_of_thoughts: bool = False
    tree_of_thoughts: bool = False
    extended_output: bool = False
    agent_type: Optional[AgentType] = None
    agents_pool: List[str] = field(default_factory=list)
    mcps: List[str] = field(default_factory=list)
    max_planning_steps: Optional[int] = None
    max_browser_steps: Optional[int] = None
    virtual_desktop_instructions: Optional[str] = None
    planning_instructions: Optional[str] = None
    planner_execution_attempts: Optional[int] = None
    planner_requires_approval_for_plan_execution: bool = False
    planner_email_on_approval_request: bool = False
    planner_email_on_failed_plan: bool = False
    planner_email_on_completed_plan: bool = False
    planner_auto_agent_spawn: bool = False
    planner_retains_spawned_agents: bool = False
    planner_prompts_agents_programmatically: bool = False
    planner_deep_thinking: bool = False
    allow_replanning: bool = False
    planner_proceeds_when_improvements_required: bool = False
    planner_approver_emails: List[str] = field(default_factory=list)
    review_instructions: Optional[str] = None
    show_references_to_internal_docs: bool = False
    show_citations: bool = False
    force_json_output: bool = False
    agentic_rag: bool = False
    prevent_widget_usage: bool = False
    allow_feedback: bool = False
    generate_extended_report: bool = False
    show_time_for_response: bool = False
    show_detected_language: bool = False
    show_routed_model: bool = False
    is_multi_language_enabled: bool = False
    has_math: bool = False
    has_code: bool = False
    has_computer: bool = False
    has_ner: bool = False
    restricted_access: bool = False
    restricted_access_users: List[str] = field(default_factory=list)
    splash_title: Optional[str] = None
    show_splash_when_empty: bool = False
    llm_endpoint: Optional[str] = None
    llm_url: Optional[str] = None
    llm_url_status: Optional[QaUrlStatus] = None
    llm_url_type: Optional[QaUrlType] = None
    llm_base_model: Optional[str] = None
    llm_provider: Optional[str] = None
    llm_top_k: Optional[float] = None
    llm_seed: Optional[float] = None
    adverserial_provider: Optional[str] = None
    adverserial_model: Optional[str] = None
    function_calling_provider: Optional[str] = None
    function_calling_model: Optional[str] = None
    vision_provider: Optional[str] = None
    vision_model: Optional[str] = None
    video_generation_provider: Optional[str] = None
    video_generation_model: Optional[str] = None
    show_agent_name: bool = False
    allow_images_upload: bool = False
    allow_videos_upload: bool = False
    allow_audios_upload: bool = False
    allow_docs_upload: bool = False
    allow_images_generation: bool = False
    base_image_generation_model: Optional[str] = None
    max_images_generated: Optional[int] = None
    advanced_image_generation: bool = False
    allow_videos_generation: bool = False
    allow_audios_generation: bool = False
    allow_3d_model_generation: bool = False
    allow_introspection: bool = False
    voice_file_key: Optional[str] = None
    allow_internet_search: bool = False
    banned_domains: List[str] = field(default_factory=list)
    max_urls_for_internet_search: Optional[int] = None
    allow_deep_internet_search: bool = False
    internet_search_modes: List[InternetSearchMode] = field(default_factory=list)
    internet_search_location: Optional[str] = None
    auto_voice_mode: bool = False
    voice_sensitivity: Optional[str] = None
    keywords_weight_in_retrieval: Optional[float] = None
    custom_code_execution_environments: List[str] = field(default_factory=list)
    phone_number_as_sender: Optional[str] = None
    whatsapp_number_as_sender: Optional[str] = None
    email_as_sender: Optional[str] = None
    communication_services: List[str] = field(default_factory=list)
    channel_callback_delay: Optional[int] = None
    ephemeral_agent: bool = False
    parent_agent_id: Optional[str] = None
    parent_chat_id: Optional[str] = None
    minimum_subscription_type: Optional[PlanType] = None
    cloned_agent_id: Optional[str] = None
    cloned_agent_id_icon: Optional[str] = None
    has_content_creation: bool = False
    has_entity_extraction: bool = False
    department: Optional[Department] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
    allow_email_to_agent: bool = False
    allowed_emails: Optional[str] = None
    agent_hiring_template: Dict[str, Any] = field(default_factory=dict)
    agent_desktop_vm: Optional[str] = None
    agent_desktop_port_range: Optional[str] = None
    agent_desktop_credentials: Dict[str, Any] = field(default_factory=dict)
    agent_desktop_browser: bool = False
    allow_3d_model_generation: bool = False
    agent_long_term_memory: Optional[str] = None
    is_long_term_memory_enabled: bool = False
    allow_external_api: bool = False
    force_new_thread_on_programmatic_request: bool = False
    dynamic_model_selection_instructions: Optional[str] = None
    context_relevancy_ratio: Optional[float] = None
    reasoning_budget: Optional[int] = None
    reasoning_mode: Optional[ReasoningMode] = None
    reasoning_effort: Optional[ReasoningEffort] = None
    fn_reasoning_effort: Optional[ReasoningEffort] = None
    agent_fn_params: Dict[str, Any] = field(default_factory=dict)
    canvas_enabled: bool = False
    advanced_compute: bool = False
    re_rank: bool = False
    min_deep_search_pages_percentage: Optional[float] = None
    voice_name: Optional[str] = None
    vad_thresholds: Optional[float] = None
    analytics_instructions: Optional[str] = None
    rag_instructions: Optional[str] = None
    allow_ask_question: bool = False
    rag_search_strategy: Optional[SearchStrategy] = None
    min_chunks_before_completeness: Optional[float] = None
    rag_completeness_threshold: Optional[float] = None
    voice_instructions: Optional[str] = None
    stt_model: Optional[str] = None
    llm_voice_model: Optional[str] = None
    tts_model: Optional[str] = None
    default_voice_language: Optional[str] = None
    voice_min_silence_duration: Optional[float] = None
    voice_min_endpointing_delay: Optional[float] = None
    voice_min_interruption_duration: Optional[float] = None
    enable_voice_intermissions: bool = False
    voice_background_noise: Optional[str] = None
    sip_phone_number: Optional[str] = None
    sip_dispatch_rule_id: Optional[str] = None
    enable_inbound_sip: bool = False
    enable_outbound_sip: bool = False
    enable_json_output: bool = False
    json_output_structure: Dict[str, Any] = field(default_factory=dict)
    predicted_plan_json: Dict[str, Any] = field(default_factory=dict)
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Agent":
        """Create an Agent instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            type=data.get("type", ""),
            label=data.get("label", ""),
            description=data.get("description"),
            interpolation_string=data.get("interpolationString"),
            emoji=data.get("emoji"),
            color=data.get("color"),
            dark_color=data.get("darkColor"),
            icon=data.get("icon"),
            large_logo=data.get("largeLogo"),
            ico_url=data.get("icoUrl"),
            logo_background=data.get("logoBackground"),
            dark_logo_background=data.get("darkLogoBackground"),
            forced_theme=data.get("forcedTheme"),
            message_on_launch=data.get("messageOnLaunch"),
            messages_on_launch=data.get("messagesOnLaunch", []),
            rotate_messages_on_launch=data.get("rotateMessagesOnLaunch", False),
            rotate_messages_on_launch_interval=data.get("rotateMessagesOnLaunchInterval"),
            placeholder_message_on_loading=data.get("placeholderMessageOnLoading"),
            dynamic_placeholder_message_on_loading=data.get(
                "dynamicPlaceholderMessageOnLoading", False
            ),
            custom_greeting_instructions=data.get("customGreetingInstructions"),
            custom_conclusion_instructions=data.get("customConclusionInstructions"),
            custom_charting_instructions=data.get("customChartingInstructions"),
            custom_summarisation_instructions=data.get("customSummarisationInstructions"),
            custom_images_instructions=data.get("customImagesInstructions"),
            custom_tooling_instructions=data.get("customToolingInstructions"),
            placeholder_input_message=data.get("placeholderInputMessage"),
            open_by_default=data.get("openByDefault", False),
            agentic_coding_model=data.get("agenticCodingModel"),
            disclaimer=data.get("disclaimer"),
            show_documents_references=data.get("showDocumentsReferences", False),
            is_default=data.get("isDefault", False),
            has_memory=data.get("hasMemory", True),
            has_images=data.get("hasImages", False),
            prompt_top_keywords=data.get("promptTopKeywords", 5),
            key_words_for_knowledge_base=data.get("keyWordsForKnowledgeBase", True),
            has_topics_context=data.get("hasTopicsContext", True),
            has_functions=data.get("hasFunctions", False),
            advanced_language_detection=data.get("advancedLanguageDetection", False),
            allowed_topics=data.get("allowedTopics", {}),
            static_docs=data.get("staticDocs", {}),
            default_answer=data.get("defaultAnswer"),
            no_knowledge_default_answer=data.get("noKnowledgeDefaultAnswer"),
            quick_questions=data.get("quickQuestions"),
            recency_importance=data.get("recencyImportance"),
            max_recency_increase=data.get("maxRecencyIncrease"),
            recency_top_k_increase_factor=data.get("recencyTopKIncreaseFactor"),
            stop_sequence=data.get("stopSequence"),
            examples=data.get("examples", []),
            enhancement_passage=data.get("enhancementPassage"),
            inhibition_passage=data.get("inhibitionPassage"),
            pertinence_passage=data.get("pertinencePassage"),
            goals=data.get("goals"),
            qa_url=data.get("qaUrl"),
            top_k=data.get("topK", 10),
            mode=data.get("mode"),
            show_code_in_execution=data.get("showCodeInExecution", False),
            hide_reasoning=data.get("hideReasoning", False),
            charting=data.get("charting", False),
            summarisation=data.get("summarisation", True),
            compressor=data.get("compressor", False),
            doc_top_k=data.get("docTopK", 5),
            min_retrieval_score=data.get("minRetrievalScore"),
            time_significance=data.get("timeSignificance"),
            has_moderation=data.get("hasModeration", False),
            custom_moderations=data.get("customModerations", {}),
            moderation_message=data.get("moderationMessage"),
            language_detection_confidence=data.get("languageDetectionConfidence"),
            max_history=data.get("maxHistory", 10),
            max_tokens=data.get("maxTokens", 1000),
            topic_enhancer=data.get("topicEnhancer", False),
            max_topics=data.get("maxTopics", 3),
            blender=data.get("blender", False),
            temperature=data.get("temperature", 0.7),
            version=data.get("version", 1),
            subject=data.get("subject"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
            agent_functions=data.get("agentFunctions", []),
            is_global=data.get("isGlobal", False),
            prompt_buffering=data.get("promptBuffering", False),
            function_context=data.get("functionContext", {}),
            plain_text_output=data.get("plainTextOutput", False),
            chain_of_thoughts=data.get("chainOfThoughts", False),
            tree_of_thoughts=data.get("treeOfThoughts", False),
            extended_output=data.get("extendedOutput", False),
            agent_type=data.get("agentType"),
            agents_pool=data.get("agentsPool", []),
            mcps=data.get("mcps", []),
            max_planning_steps=data.get("maxPlanningSteps"),
            max_browser_steps=data.get("maxBrowserSteps"),
            virtual_desktop_instructions=data.get("virtualDesktopInstructions"),
            planning_instructions=data.get("planningInstructions"),
            planner_execution_attempts=data.get("plannerExecutionAttempts"),
            planner_requires_approval_for_plan_execution=data.get(
                "plannerRequiresApprovalForPlanExecution", False
            ),
            planner_email_on_approval_request=data.get("plannerEmailOnApprovalRequest", False),
            planner_email_on_failed_plan=data.get("plannerEmailOnFailedPlan", False),
            planner_email_on_completed_plan=data.get("plannerEmailOnCompletedPlan", False),
            planner_auto_agent_spawn=data.get("plannerAutoAgentSpawn", False),
            planner_retains_spawned_agents=data.get("plannerRetainsSpawnedAgents", False),
            planner_prompts_agents_programmatically=data.get(
                "plannerPromptsAgentsProgrammatically", False
            ),
            planner_deep_thinking=data.get("plannerDeepThinking", False),
            allow_replanning=data.get("allowReplanning", False),
            planner_proceeds_when_improvements_required=data.get(
                "plannerProceedsWhenImprovementsRequired", False
            ),
            planner_approver_emails=data.get("plannerApproverEmails", []),
            review_instructions=data.get("reviewInstructions"),
            show_references_to_internal_docs=data.get("showReferencesToInternalDocs", False),
            show_citations=data.get("showCitations", False),
            force_json_output=data.get("forceJsonOutput", False),
            agentic_rag=data.get("agenticRAG", False),
            prevent_widget_usage=data.get("preventWidgetUsage", False),
            allow_feedback=data.get("allowFeedback", False),
            generate_extended_report=data.get("generateExtendedReport", False),
            show_time_for_response=data.get("showTimeForResponse", False),
            show_detected_language=data.get("showDetectedLanguage", False),
            show_routed_model=data.get("showRoutedModel", False),
            is_multi_language_enabled=data.get("isMultiLanguageEnabled", False),
            has_math=data.get("hasMath", False),
            has_code=data.get("hasCode", False),
            has_computer=data.get("hasComputer", False),
            has_ner=data.get("hasNER", False),
            restricted_access=data.get("restrictedAccess", False),
            restricted_access_users=data.get("restrictedAccessUsers", []),
            splash_title=data.get("splashTitle"),
            show_splash_when_empty=data.get("showSplashWhenEmpty", False),
            llm_endpoint=data.get("llmEndpoint"),
            llm_url=data.get("llmUrl"),
            llm_url_status=data.get("llmUrlStatus"),
            llm_url_type=data.get("llmUrlType"),
            llm_base_model=data.get("llmBaseModel"),
            llm_provider=data.get("llmProvider"),
            llm_top_k=data.get("llmTopK"),
            llm_seed=data.get("llmSeed"),
            adverserial_provider=data.get("adverserialProvider"),
            adverserial_model=data.get("adverserialModel"),
            function_calling_provider=data.get("functionCallingProvider"),
            function_calling_model=data.get("functionCallingModel"),
            vision_provider=data.get("visionProvider"),
            vision_model=data.get("visionModel"),
            video_generation_provider=data.get("videoGenerationProvider"),
            video_generation_model=data.get("videoGenerationModel"),
            show_agent_name=data.get("showAgentName", False),
            allow_images_upload=data.get("allowImagesUpload", False),
            allow_videos_upload=data.get("allowVideosUpload", False),
            allow_audios_upload=data.get("allowAudiosUpload", False),
            allow_docs_upload=data.get("allowDocsUpload", False),
            allow_images_generation=data.get("allowImagesGeneration", False),
            base_image_generation_model=data.get("baseImageGenerationModel"),
            max_images_generated=data.get("maxImagesGenerated"),
            advanced_image_generation=data.get("advancedImageGeneration", False),
            allow_videos_generation=data.get("allowVideosGeneration", False),
            allow_audios_generation=data.get("allowAudiosGeneration", False),
            allow_3d_model_generation=data.get("allow3dModelGeneration", False),
            allow_introspection=data.get("allowIntrospection", False),
            voice_file_key=data.get("voiceFileKey"),
            allow_internet_search=data.get("allowInternetSearch", False),
            banned_domains=data.get("bannedDomains", []),
            max_urls_for_internet_search=data.get("maxUrlsForInternetSearch"),
            allow_deep_internet_search=data.get("allowDeepInternetSearch", False),
            internet_search_modes=data.get("internetSearchModes", []),
            internet_search_location=data.get("internetSearchLocation"),
            auto_voice_mode=data.get("autoVoiceMode", False),
            voice_sensitivity=data.get("voiceSensitivity"),
            keywords_weight_in_retrieval=data.get("keywordsWeightInRetrieval"),
            custom_code_execution_environments=data.get("customCodeExecutionEnvironments", []),
            phone_number_as_sender=data.get("phoneNumberAsSender"),
            whatsapp_number_as_sender=data.get("whatsappNumberAsSender"),
            email_as_sender=data.get("emailAsSender"),
            communication_services=data.get("communicationServices", []),
            channel_callback_delay=data.get("channelCallbackDelay"),
            ephemeral_agent=data.get("ephemeralAgent", False),
            parent_agent_id=data.get("parentAgentID"),
            parent_chat_id=data.get("parentChatID"),
            minimum_subscription_type=data.get("minimumSubscriptionType"),
            cloned_agent_id=data.get("clonedAgentID"),
            cloned_agent_id_icon=data.get("clonedAgentIDIcon"),
            has_content_creation=data.get("hasContentCreation", False),
            has_entity_extraction=data.get("hasEntityExtraction", False),
            department=data.get("department"),
            metadata=data.get("metadata", {}),
            allow_email_to_agent=data.get("allowEmailToAgent", False),
            allowed_emails=data.get("allowedEmails"),
            agent_hiring_template=data.get("agentHiringTemplate", {}),
            agent_desktop_vm=data.get("agentDesktopVM"),
            agent_desktop_port_range=data.get("agentDesktopPortRange"),
            agent_desktop_credentials=data.get("agentDesktopCredentials", {}),
            agent_desktop_browser=data.get("agentDesktopBrowser", False),
            agent_long_term_memory=data.get("agentLongTermMemory"),
            is_long_term_memory_enabled=data.get("isLongTermMemoryEnabled", False),
            allow_external_api=data.get("allowExternalAPI", False),
            force_new_thread_on_programmatic_request=data.get(
                "forceNewThreadOnProgrammaticRequest", False
            ),
            dynamic_model_selection_instructions=data.get("dynamicModelSelectionInstructions"),
            context_relevancy_ratio=data.get("contextRelevancyRatio"),
            reasoning_budget=data.get("reasoningBudget"),
            reasoning_mode=data.get("reasoningMode"),
            reasoning_effort=data.get("reasoningEffort"),
            fn_reasoning_effort=data.get("fnReasoningEffort"),
            agent_fn_params=data.get("agentFnParams", {}),
            canvas_enabled=data.get("canvasEnabled", False),
            advanced_compute=data.get("advancedCompute", False),
            re_rank=data.get("reRank", False),
            min_deep_search_pages_percentage=data.get("minDeepSearchPagesPercentage"),
            voice_name=data.get("voiceName"),
            vad_thresholds=data.get("vadThresholds"),
            analytics_instructions=data.get("analyticsInstructions"),
            rag_instructions=data.get("ragInstructions"),
            allow_ask_question=data.get("allowAskQuestion", False),
            rag_search_strategy=data.get("ragSearchStrategy"),
            min_chunks_before_completeness=data.get("minChunksBeforeCompleteness"),
            rag_completeness_threshold=data.get("ragCompletenessThreshold"),
            voice_instructions=data.get("voiceInstructions"),
            stt_model=data.get("sttModel"),
            llm_voice_model=data.get("llmVoiceModel"),
            tts_model=data.get("ttsModel"),
            default_voice_language=data.get("defaultVoiceLanguage"),
            voice_min_silence_duration=data.get("voiceMinSilenceDuration"),
            voice_min_endpointing_delay=data.get("voiceMinEndpointingDelay"),
            voice_min_interruption_duration=data.get("voiceMinInterruptionDuration"),
            enable_voice_intermissions=data.get("enableVoiceIntermissions", False),
            voice_background_noise=data.get("voiceBackgroundNoise"),
            sip_phone_number=data.get("sipPhoneNumber"),
            sip_dispatch_rule_id=data.get("sipDispatchRuleId"),
            enable_inbound_sip=data.get("enableInboundSip", False),
            enable_outbound_sip=data.get("enableOutboundSip", False),
            enable_json_output=data.get("enableJsonOutput", False),
            json_output_structure=data.get("jsonOutputStructure", {}),
            predicted_plan_json=data.get("predictedPlanJson", {}),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


# Agent Function types
@dataclass
class AgentFunction:
    """Represents an agent function."""

    id: str
    name: str
    description: str = ""
    url: Optional[str] = None
    workspace_id: str = ""
    parameters: Dict[str, Any] = field(default_factory=dict)
    model: Optional[str] = None
    request_type: Optional[FunctionRequestType] = None
    authorisation_type: Optional[AuthorisationType] = None
    depends_on_before_execution: List[str] = field(default_factory=list)
    depends_on_after_execution: List[str] = field(default_factory=list)
    authorisation_key: Optional[str] = None
    authorisation_id: Optional[str] = None
    params_as_path: bool = False
    scope: Optional[FunctionScope] = None
    json_path_extractor: Optional[str] = None
    headers: Dict[str, Any] = field(default_factory=dict)
    static_args: Dict[str, Any] = field(default_factory=dict)
    custom_execution_code: Optional[str] = None
    is_mcp_server: bool = False
    is_callback_fn: bool = False
    is_database_script: bool = False
    is_html_fn: bool = False
    is_chat_fn: bool = False
    is_hand_off_fn: bool = False
    is_code_template: bool = False
    is_agent_skill: bool = False
    code_execution_environment_id: Optional[str] = None
    on_fn_call: Dict[str, Any] = field(default_factory=dict)
    hand_off_agent: Optional[str] = None
    hand_off_to_original_agent_on_completion: bool = False
    hand_off_to_original_agent_on_failure: bool = False
    hand_off_agent_on_completion: Optional[str] = None
    hand_off_agent_on_failure: Optional[str] = None
    agent_skill_script: Optional[str] = None
    chat_script: Optional[str] = None
    chat_action: Optional[AgentFunctionChatAction] = None
    html_url: Optional[str] = None
    html_script: Optional[str] = None
    is_html_redirect_fn: bool = False
    html_form_on_successful_submit_message: Optional[str] = None
    html_form_on_failed_submit_message: Optional[str] = None
    db_schema_metadata: Dict[str, Any] = field(default_factory=dict)
    pre_execution_script: Optional[str] = None
    post_execution_script: Optional[str] = None
    db_script: Optional[str] = None
    db_connection_id: Optional[str] = None
    pre_execution_fn_id: Optional[str] = None
    pre_execution_mapping: Dict[str, Any] = field(default_factory=dict)
    oauth_connection_id: Optional[str] = None
    handed_off_to_human_on_call: bool = False
    graphql_query: Optional[str] = None
    graphql_mutation: Optional[str] = None
    graphql_operation_name: Optional[str] = None
    users_to_hand_off_to: List[str] = field(default_factory=list)
    created_by: Optional[str] = None
    updated_by: Optional[str] = None
    is_global: bool = False
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "AgentFunction":
        """Create an AgentFunction instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            description=data.get("description", ""),
            url=data.get("url"),
            workspace_id=_get_workspace_id(data),
            parameters=data.get("parameters", {}),
            model=data.get("model"),
            request_type=data.get("requestType"),
            authorisation_type=data.get("authorisationType"),
            depends_on_before_execution=data.get("dependsOnBeforeExecution", []),
            depends_on_after_execution=data.get("dependsOnAfterExecution", []),
            authorisation_key=data.get("authorisationKey"),
            authorisation_id=data.get("authorisationID"),
            params_as_path=data.get("paramsAsPath", False),
            scope=data.get("scope"),
            json_path_extractor=data.get("jsonPathExtractor"),
            headers=data.get("headers", {}),
            static_args=data.get("staticArgs", {}),
            custom_execution_code=data.get("customExecutionCode"),
            is_mcp_server=data.get("isMCPServer"),
            is_callback_fn=data.get("isCallbackFn"),
            is_database_script=data.get("isDatabaseScript", False),
            is_html_fn=data.get("isHtmlFn"),
            is_chat_fn=data.get("isChatFn"),
            is_hand_off_fn=data.get("isHandOffFn"),
            is_code_template=data.get("isCodeTemplate"),
            is_agent_skill=data.get("isAgentSkill"),
            code_execution_environment_id=data.get("codeExecutionEnvironmentID"),
            on_fn_call=data.get("onFnCall", {}),
            hand_off_agent=data.get("handOffAgent"),
            hand_off_to_original_agent_on_completion=data.get("handOffToOriginalAgentOnCompletion"),
            hand_off_to_original_agent_on_failure=data.get("handOffToOriginalAgentOnFailure"),
            hand_off_agent_on_completion=data.get("handOffAgentOnCompletion"),
            hand_off_agent_on_failure=data.get("handOffAgentOnFailure"),
            agent_skill_script=data.get("agentSkillScript"),
            chat_script=data.get("chatScript"),
            chat_action=data.get("chatAction"),
            html_url=data.get("htmlUrl"),
            html_script=data.get("htmlScript"),
            is_html_redirect_fn=data.get("isHtmlRedirectFn"),
            html_form_on_successful_submit_message=data.get("htmlFormOnSuccessfulSubmitMessage"),
            html_form_on_failed_submit_message=data.get("htmlFormOnFailedSubmitMessage"),
            db_schema_metadata=data.get("dbSchemaMetadata", {}),
            pre_execution_script=data.get("preExecutionScript"),
            post_execution_script=data.get("postExecutionScript"),
            db_script=data.get("dbScript"),
            db_connection_id=data.get("dbConnectionID"),
            pre_execution_fn_id=data.get("preExecutionFnID"),
            pre_execution_mapping=data.get("preExecutionMapping", {}),
            oauth_connection_id=data.get("OAuthConnectionID"),
            handed_off_to_human_on_call=data.get("handedOffToHumanOnCall"),
            graphql_query=data.get("graphqlQuery"),
            graphql_mutation=data.get("graphqlMutation"),
            graphql_operation_name=data.get("graphqlOperationName"),
            users_to_hand_off_to=data.get("usersToHandOffTo", []),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
            is_global=data.get("isGlobal", False),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


# Authorisation types
@dataclass
class Authorisation:
    """Represents an authorisation configuration."""

    id: str
    name: str
    type: str
    workspace_id: str = ""
    token_secret: Optional[str] = None
    description: Optional[str] = None
    scope: Optional[str] = None
    grant_type: Optional[str] = None
    client_id: Optional[str] = None
    client_secret: Optional[str] = None
    authorization_base_url: Optional[str] = None
    static_args: Optional[str] = None
    token_base_url: Optional[str] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Authorisation":
        """Create an Authorisation instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            type=data.get("type", ""),
            workspace_id=_get_workspace_id(data),
            token_secret=data.get("tokenSecret"),
            description=data.get("description"),
            scope=data.get("scope"),
            grant_type=data.get("grantType"),
            client_id=data.get("clientId"),
            client_secret=data.get("clientSecret"),
            authorization_base_url=data.get("authorizationBaseUrl"),
            static_args=data.get("staticArgs"),
            token_base_url=data.get("tokenBaseUrl"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


# Channel types
@dataclass
class Channel:
    """Represents a communication channel."""

    id: str
    name: str
    channel: str
    provider: str
    workspace_id: str = ""
    description: Optional[str] = None
    senderid: Optional[str] = None
    is_active: bool = True

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Channel":
        """Create a Channel instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            channel=data.get("channel", ""),
            provider=data.get("provider", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            senderid=data.get("senderid"),
            is_active=data.get("isActive", True),
        )


# Hosting types (maps to /connection/* API endpoints)
@dataclass
class Hosting:
    """Represents a hosting configuration for LLM/API endpoints."""

    id: str
    name: str
    type: HostingType
    workspace_id: str = ""
    description: Optional[str] = None
    token_secret: Optional[str] = None
    endpoint: Optional[str] = None
    model_name: Optional[str] = None
    base_model: Optional[str] = None
    custom_model: bool = False
    api_version: Optional[str] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Hosting":
        """Create a Hosting instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            type=data.get("type", "openai"),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            token_secret=data.get("tokenSecret"),
            endpoint=data.get("endpoint"),
            model_name=data.get("modelName"),
            base_model=data.get("baseModel"),
            custom_model=data.get("customModel", False),
            api_version=data.get("apiVersion"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# Alias for backward compatibility
Connection = Hosting


# Database Connection types
@dataclass
class DatabaseConnection:
    """Represents a database connection."""

    id: str
    name: str
    type: str
    host: str
    port: str
    username: str
    password_secret: str
    database: str
    workspace_id: str = ""
    description: Optional[str] = None
    schema: Optional[str] = None
    ssl: bool = False
    ssh: bool = False
    ssh_host: Optional[str] = None
    ssh_port: Optional[str] = None
    ssh_username: Optional[str] = None
    ssh_password_secret: Optional[str] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "DatabaseConnection":
        """Create a DatabaseConnection instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            type=data.get("type", ""),
            host=data.get("host", ""),
            port=data.get("port", ""),
            username=data.get("username", ""),
            password_secret=data.get("passwordSecret", ""),
            database=data.get("database", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            schema=data.get("schema"),
            ssl=data.get("ssl", False),
            ssh=data.get("ssh", False),
            ssh_host=data.get("sshHost"),
            ssh_port=data.get("sshPort"),
            ssh_username=data.get("sshUsername"),
            ssh_password_secret=data.get("sshPasswordSecret"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# Member types
@dataclass
class Member:
    """Represents a workspace member."""

    id: str
    user_id: str
    workspace_id: str = ""
    role: str = "member"
    status: str = "active"
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Member":
        """Create a Member instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            user_id=data.get("userID", ""),
            workspace_id=_get_workspace_id(data),
            role=data.get("role", "member"),
            status=data.get("status", "active"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# Site types
@dataclass
class Site:
    """Represents a website for crawling."""

    id: str
    name: str
    url: str
    workspace_id: str = ""
    description: Optional[str] = None
    topics: Dict[str, Any] = field(default_factory=dict)
    status: str = "active"
    last_validation_date: Optional[str] = None
    validation_token: Optional[str] = None
    allowed_paths: List[str] = field(default_factory=list)
    completion_percentage: float = 0.0

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Site":
        """Create a Site instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            url=data.get("url", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            topics=data.get("topics", {}),
            status=data.get("status", "active"),
            last_validation_date=data.get("lastValidationDate"),
            validation_token=data.get("validationToken"),
            allowed_paths=data.get("allowedPaths", []),
            completion_percentage=data.get("completion_percentage", 0.0),
        )


# Benchmark types
@dataclass
class Benchmark:
    """Represents a benchmark for testing agent performance."""

    id: str
    name: str
    workspace_id: str = ""
    description: Optional[str] = None
    questions: List[Dict[str, str]] = field(default_factory=list)
    files: List[str] = field(default_factory=list)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Benchmark":
        """Create a Benchmark instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            questions=data.get("questions", []),
            files=data.get("files", []),
        )


# CustomCodeExecutionEnvironment types (Hook)
@dataclass
class CustomCodeExecutionEnvironment:
    """Represents a custom code execution environment (formerly Hook)."""

    id: str
    name: str
    description: Optional[str] = None
    function_name: Optional[str] = None
    available_libraries: Optional[str] = None
    custom_execution_instructions: Optional[str] = None
    custom_execution_code: Optional[str] = None
    custom_execution_secrets: Dict[str, Any] = field(default_factory=dict)
    static_docs: Dict[str, Any] = field(default_factory=dict)
    allow_external_api: bool = False
    hardcoded_script: bool = False
    is_template: bool = False
    workspace_id: str = ""
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CustomCodeExecutionEnvironment":
        """Create a CustomCodeExecutionEnvironment instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            description=data.get("description"),
            function_name=data.get("functionName"),
            available_libraries=data.get("availableLibraries"),
            custom_execution_instructions=data.get("customExecutionInstructions"),
            custom_execution_code=data.get("customExecutionCode"),
            custom_execution_secrets=data.get("customExecutionSecrets", {}),
            static_docs=data.get("staticDocs", {}),
            allow_external_api=data.get("allowExternalAPI", False),
            hardcoded_script=data.get("hardcodedScript", False),
            is_template=data.get("isTemplate", False),
            workspace_id=_get_workspace_id(data),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# Alias for backward compatibility
Hook = CustomCodeExecutionEnvironment


# CustomExecutionEnvironment types (remote execution environment)
@dataclass
class CustomExecutionEnvironment:
    """Represents a custom remote execution environment."""

    id: str
    name: str
    workspace_id: str = ""
    description: Optional[str] = None
    function_name: Optional[str] = None
    available_libraries: Optional[str] = None
    ip: Optional[str] = None
    port: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
    is_global: bool = False
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CustomExecutionEnvironment":
        """Create a CustomExecutionEnvironment instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            function_name=data.get("functionName"),
            available_libraries=data.get("availableLibraries"),
            ip=data.get("ip"),
            port=data.get("port"),
            metadata=data.get("metadata", {}),
            is_global=data.get("isGlobal", False),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# CronSchedule type
@dataclass
class CronSchedule:
    """Represents a cron schedule configuration."""

    frequency: CronJobFrequency = "DAILY"
    interval: Optional[int] = None
    cron_expression: Optional[str] = None
    day_of_week: Optional[int] = None
    day_of_month: Optional[int] = None
    hour: Optional[int] = None
    minute: Optional[int] = None
    start_date: Optional[str] = None
    end_date: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CronSchedule":
        """Create a CronSchedule instance from a dictionary."""
        return cls(
            frequency=data.get("frequency", "DAILY"),
            interval=data.get("interval"),
            cron_expression=data.get("cronExpression"),
            day_of_week=data.get("dayOfWeek"),
            day_of_month=data.get("dayOfMonth"),
            hour=data.get("hour"),
            minute=data.get("minute"),
            start_date=data.get("startDate"),
            end_date=data.get("endDate"),
        )


# CronExecutionConfig type
@dataclass
class CronExecutionConfig:
    """Represents cron job execution configuration."""

    timeout: Optional[int] = None
    max_duration: Optional[int] = None
    priority: Optional[Priority] = None
    input_parameters: Dict[str, Any] = field(default_factory=dict)
    output_format: Optional[str] = None
    webhook_url: Optional[str] = None
    webhook_headers: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CronExecutionConfig":
        """Create a CronExecutionConfig instance from a dictionary."""
        return cls(
            timeout=data.get("timeout"),
            max_duration=data.get("maxDuration"),
            priority=data.get("priority"),
            input_parameters=data.get("inputParameters", {}),
            output_format=data.get("outputFormat"),
            webhook_url=data.get("webhookUrl"),
            webhook_headers=data.get("webhookHeaders", {}),
        )


# CronNotificationConfig type
@dataclass
class CronNotificationConfig:
    """Represents cron job notification configuration."""

    on_success: bool = False
    on_failure: bool = False
    on_start: bool = False
    email_recipients: List[str] = field(default_factory=list)
    communication_service_id: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CronNotificationConfig":
        """Create a CronNotificationConfig instance from a dictionary."""
        return cls(
            on_success=data.get("onSuccess", False),
            on_failure=data.get("onFailure", False),
            on_start=data.get("onStart", False),
            email_recipients=data.get("emailRecipients", []),
            communication_service_id=data.get("communicationServiceID"),
        )


# CronRetryConfig type
@dataclass
class CronRetryConfig:
    """Represents cron job retry configuration."""

    max_retries: int = 3
    retry_interval: int = 60
    backoff_multiplier: float = 1.5
    max_retry_interval: int = 3600

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "CronRetryConfig":
        """Create a CronRetryConfig instance from a dictionary."""
        return cls(
            max_retries=data.get("maxRetries", 3),
            retry_interval=data.get("retryInterval", 60),
            backoff_multiplier=data.get("backoffMultiplier", 1.5),
            max_retry_interval=data.get("maxRetryInterval", 3600),
        )


# Scheduled Job types (CronJob)
@dataclass
class ScheduledJob:
    """Represents a scheduled job (cron job)."""

    id: str
    name: str
    workspace_id: str = ""
    description: Optional[str] = None
    agent_id: Optional[str] = None
    custom_prompt_id: Optional[str] = None
    forced_prompt: Optional[str] = None
    schedule: Optional[CronSchedule] = None
    timezone: str = "UTC"
    is_active: bool = True
    last_run_at: Optional[str] = None
    next_run_at: Optional[str] = None
    status: CronJobStatus = "PENDING"
    execution_config: Optional[CronExecutionConfig] = None
    notification_config: Optional[CronNotificationConfig] = None
    retry_config: Optional[CronRetryConfig] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
    start_date: Optional[str] = None
    end_date: Optional[str] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ScheduledJob":
        """Create a ScheduledJob instance from a dictionary."""
        schedule = None
        if data.get("schedule"):
            schedule = CronSchedule.from_dict(data["schedule"])

        execution_config = None
        if data.get("executionConfig"):
            execution_config = CronExecutionConfig.from_dict(data["executionConfig"])

        notification_config = None
        if data.get("notificationConfig"):
            notification_config = CronNotificationConfig.from_dict(data["notificationConfig"])

        retry_config = None
        if data.get("retryConfig"):
            retry_config = CronRetryConfig.from_dict(data["retryConfig"])

        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            agent_id=data.get("agentID"),
            custom_prompt_id=data.get("customPromptID"),
            forced_prompt=data.get("forcedPrompt"),
            schedule=schedule,
            timezone=data.get("timezone", "UTC"),
            is_active=data.get("isActive", True),
            last_run_at=data.get("lastRunAt"),
            next_run_at=data.get("nextRunAt"),
            status=data.get("status", "PENDING"),
            execution_config=execution_config,
            notification_config=notification_config,
            retry_config=retry_config,
            metadata=data.get("metadata", {}),
            start_date=data.get("startDate"),
            end_date=data.get("endDate"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


# Alias for clarity
CronJob = ScheduledJob


# Secret types
@dataclass
class Secret:
    """Represents a secret."""

    id: str
    name: str
    workspace_id: str = ""
    description: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Secret":
        """Create a Secret instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            name=data.get("name", ""),
            workspace_id=_get_workspace_id(data),
            description=data.get("description"),
            created_at=data.get("createdAt"),
            updated_at=data.get("updatedAt"),
        )


# Dictionary types
@dataclass
class DictionaryEntry:
    """Represents a dictionary entry for translations."""

    id: str
    owner: str
    source_language: str
    target_language: str
    source_text: str
    target_text: str
    workspace_id: str = ""
    pos: Optional[str] = None
    description: Optional[str] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "DictionaryEntry":
        """Create a DictionaryEntry instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            owner=data.get("owner", ""),
            source_language=data.get("sourceLanguage", ""),
            target_language=data.get("targetLanguage", ""),
            source_text=data.get("sourceText", ""),
            target_text=data.get("targetText", ""),
            workspace_id=_get_workspace_id(data),
            pos=data.get("pos"),
            description=data.get("description"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# Embeddings types
@dataclass
class Embedding:
    """Represents a document embedding."""

    id: str
    chunk_id: str
    title: Optional[str] = None
    external_path: Optional[str] = None
    type: Optional[str] = None
    raw_text: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Embedding":
        """Create an Embedding instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            chunk_id=data.get("chunk_id", ""),
            title=data.get("title"),
            external_path=data.get("external_path"),
            type=data.get("type"),
            raw_text=data.get("raw_text"),
        )


# Charting Settings types
@dataclass
class ChartingSettings:
    """Represents workspace charting settings."""

    id: str
    workspace_id: str = ""
    primary_color: Optional[str] = None
    secondary_color: Optional[str] = None
    tertiary_color: Optional[str] = None
    primary_text_color: Optional[str] = None
    primary_border_color: Optional[str] = None
    line_color: Optional[str] = None
    plot_primary_color: Optional[str] = None
    plot_secondary_color: Optional[str] = None
    plot_tertiary_color: Optional[str] = None
    xy_background_color: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ChartingSettings":
        """Create a ChartingSettings instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            primary_color=data.get("primaryColor"),
            secondary_color=data.get("secondaryColor"),
            tertiary_color=data.get("tertiaryColor"),
            primary_text_color=data.get("primaryTextColor"),
            primary_border_color=data.get("primaryBorderColor"),
            line_color=data.get("lineColor"),
            plot_primary_color=data.get("plotPrimaryColor"),
            plot_secondary_color=data.get("plotSecondaryColor"),
            plot_tertiary_color=data.get("plotTertiaryColor"),
            xy_background_color=data.get("xyBackgroundColor"),
        )


# Embeddings Settings types
@dataclass
class EmbeddingsSettings:
    """Represents workspace embeddings settings."""

    id: str
    workspace_id: str = ""
    max_chunk_words: int = 500
    max_overlap_sentences: int = 2
    chunking_strategy: str = "keywords"
    image_extraction_strategy: str = "safe"
    min_image_width: int = 100
    min_image_height: int = 100
    aspect_ratio_min: float = 0.1
    aspect_ratio_max: float = 1.0

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EmbeddingsSettings":
        """Create an EmbeddingsSettings instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            max_chunk_words=data.get("maxChunkWords", 500),
            max_overlap_sentences=data.get("maxOverlapSentences", 2),
            chunking_strategy=data.get("chunkingStrategy", "keywords"),
            image_extraction_strategy=data.get("imageExtractionStrategy", "safe"),
            min_image_width=data.get("minImageWidth", 100),
            min_image_height=data.get("minImageHeight", 100),
            aspect_ratio_min=data.get("aspectRatioMin", 0.1),
            aspect_ratio_max=data.get("aspectRatioMax", 1.0),
        )


# Billing types
@dataclass
class MonthCostsResponse:
    """Represents monthly usage and cost breakdown."""

    api_usage: Dict[str, Any] = field(default_factory=dict)
    training_usage: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "MonthCostsResponse":
        """Create a MonthCostsResponse instance from a dictionary."""
        return cls(
            api_usage=data.get("apiUsage", {}),
            training_usage=data.get("trainingUsage", {}),
        )


# Stream types
@dataclass
class OutputStream:
    """Represents an output stream."""

    id: str
    workspace_id: str = ""
    content: Optional[str] = None
    type: Optional[str] = None
    status: Optional[str] = None
    scoring: Optional[float] = None
    metadata: Optional[Dict[str, Any]] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "OutputStream":
        """Create an OutputStream instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            content=data.get("content"),
            type=data.get("type"),
            status=data.get("status"),
            scoring=data.get("scoring"),
            metadata=data.get("metadata"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )


# Request Log types
@dataclass
class RequestLog:
    """Represents a request log."""

    id: str
    workspace_id: str = ""
    srt_key: Optional[str] = None
    type: Optional[str] = None
    status: Optional[str] = None
    words: Optional[int] = None
    tokens: Optional[int] = None
    sentences: Optional[int] = None
    created_by: Optional[str] = None
    updated_by: Optional[str] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "RequestLog":
        """Create a RequestLog instance from a dictionary."""
        return cls(
            id=data.get("id", ""),
            workspace_id=_get_workspace_id(data),
            srt_key=data.get("srtKey"),
            type=data.get("type"),
            status=data.get("status"),
            words=data.get("words"),
            tokens=data.get("tokens"),
            sentences=data.get("sentences"),
            created_by=data.get("createdBy"),
            updated_by=data.get("updatedBy"),
        )
