from __future__ import annotations from enum import Enum from typing import Any, Dict, List, Literal, Optional from pydantic import BaseModel, Field, field_validator, model_validator class RouteConfig(BaseModel): name: str = Field(..., min_length=1, description="Route name") utterances: List[str] = Field(..., min_length=1, description="Example phrases for this route") models: List[str] = Field(default_factory=list, description="Model identifiers assigned to this route") class SemanticRouterRequest(BaseModel): query: str = Field(..., min_length=1, max_length=50000, description="User query to route") routes: List[RouteConfig] = Field(..., min_length=1, description="Route definitions") threshold: float = Field(default=0.3, ge=0.0, le=1.0, description="Minimum similarity score to match") class SemanticRouterResponse(BaseModel): success: bool time_ms: float name: Optional[str] = None models: List[str] = [] error: Optional[str] = None confidence: Optional[float] = None margin: Optional[float] = None threshold: Optional[float] = None matched_utterance: Optional[str] = None class TokenCountRequest(BaseModel): text: str = Field(..., min_length=1, max_length=1000000, description="Text to count tokens for") encoding: str = Field(default="o200k_base", description="TikToken encoding name") class TokenCountResponse(BaseModel): success: bool time_ms: float token_count: int = 0 char_count: int = 0 encoding: str = "o200k_base" error: Optional[str] = None class ConversionMetadata(BaseModel): source: str char_count: int word_count: int line_count: int file_size_bytes: int mime_type: str content_hash: str token_estimate: int class ConversionResponse(BaseModel): success: bool time_ms: float content: str return_json: bool = False json_content: Optional[Any] = None metadata: Optional[ConversionMetadata] = None error_message: Optional[str] = None class UrlRequest(BaseModel): url: str return_json: bool = False mappings: Optional[Dict[str, Dict[str, Any]]] = None model_config = {"populate_by_name": True} @field_validator("url") @classmethod def validate_scheme(cls, v: str) -> str: if not v.startswith(("http://", "https://")): raise ValueError("Only http/https URLs are supported.") return v class BatchUrlRequest(BaseModel): urls: List[str] return_json: bool = False mappings: Optional[Dict[str, Dict[str, Any]]] = None @field_validator("urls") @classmethod def validate_urls(cls, v: List[str]) -> List[str]: for url in v: if not url.startswith(("http://", "https://")): raise ValueError(f"Invalid URL scheme: {url}") if len(v) > 20: raise ValueError("Maximum 20 URLs per batch request.") return v class BatchFileResult(BaseModel): filename: str success: bool time_ms: float content: Optional[str] = None json_content: Optional[Any] = None error: Optional[str] = None metadata: Optional[ConversionMetadata] = None class BatchResponse(BaseModel): total: int succeeded: int failed: int total_time_ms: float results: List[BatchFileResult] class HealthResponse(BaseModel): success: bool status: str version: str uptime_seconds: float timestamp: str class InfoResponse(BaseModel): success: bool app: str version: str python_version: str platform: str uptime_seconds: float max_upload_mb: int supported_extensions: int timestamp: str class SupportedFormatsResponse(BaseModel): success: bool total_count: int all_extensions: List[str] by_category: Dict[str, List[str]] class SpacyLabelsResponse(BaseModel): success: bool spacy_labels: Dict[str, str] source_types: Dict[str, str] example_mappings: Dict[str, Dict[str, Any]] class SSLConfig(BaseModel): enabled: bool = False ca_cert: Optional[str] = None cert: Optional[str] = None key: Optional[str] = None class DatabaseConnection(BaseModel): host: str port: Optional[int] = None database: str username: str password: str = Field(repr=False) selected_schema: str = Field(default="public", description="PostgreSQL schema (ignored for MySQL/MongoDB)") ssl: SSLConfig = SSLConfig() @field_validator("host") @classmethod def host_not_empty(cls, v: str) -> str: stripped = v.strip() if not stripped: raise ValueError("host must not be empty") return stripped @field_validator("port") @classmethod def validate_port(cls, v: Optional[int]) -> Optional[int]: if v is not None and (v < 1 or v > 65535): raise ValueError("port must be between 1 and 65535") return v @field_validator("database") @classmethod def database_not_empty(cls, v: str) -> str: stripped = v.strip() if not stripped: raise ValueError("database must not be empty") return stripped def safe_repr(self) -> str: return ( f"host={self.host}, port={self.port}, " f"database={self.database}, username={self.username}" ) class DatabaseQueryRequest(BaseModel): db_type: Literal["mysql", "postgresql", "mongodb"] connection: DatabaseConnection query: List[Any] use_transaction: bool = True query_timeout_seconds: float = Field(default=10.0, ge=1.0, le=300.0) connection_timeout_seconds: float = Field(default=10.0, ge=1.0, le=60.0) max_rows: int = Field(default=10000, ge=1, le=1_000_000) @model_validator(mode="after") def validate_query(self) -> "DatabaseQueryRequest": if not self.query: raise ValueError("query must not be empty") if self.db_type in ("mysql", "postgresql"): for i, q in enumerate(self.query): if not isinstance(q, str): raise ValueError(f"query[{i}] must be a string for {self.db_type}") if not q.strip(): raise ValueError(f"query[{i}] must not be empty") elif self.db_type == "mongodb": for i, q in enumerate(self.query): if not isinstance(q, dict): raise ValueError(f"query[{i}] must be an object with 'collection' and 'pipeline'") if "collection" not in q or "pipeline" not in q: raise ValueError(f"query[{i}] must have 'collection' and 'pipeline' fields") if not isinstance(q["pipeline"], list): raise ValueError(f"query[{i}].pipeline must be an array") return self def to_connection_config(self) -> Dict[str, Any]: return { "db_type": self.db_type, "host": self.connection.host, "port": self.connection.port or self._default_port(), "database": self.connection.database, "username": self.connection.username, "password": self.connection.password, "selected_schema": self.connection.selected_schema, "ssl_enabled": self.connection.ssl.enabled, "ssl_ca_cert": self.connection.ssl.ca_cert, "ssl_cert": self.connection.ssl.cert, "ssl_key": self.connection.ssl.key, "query_timeout_seconds": self.query_timeout_seconds, "connection_timeout_seconds": self.connection_timeout_seconds, "max_rows": self.max_rows, } def _default_port(self) -> int: return {"mysql": 3306, "postgresql": 5432, "mongodb": 27017}[self.db_type] class StatementResultSchema(BaseModel): success: bool rows: int = 0 data: List[Dict[str, Any]] = [] error: Optional[str] = None error_code: Optional[str] = None class DatabaseQueryError(BaseModel): message: str code: Optional[str] = None class DatabaseQueryResponse(BaseModel): success: bool execution_time_ms: float results: Optional[List[StatementResultSchema]] = None error: Optional[DatabaseQueryError] = None class TableValidationResult(BaseModel): name: str exists: bool error: Optional[str] = None class DatabaseValidateRequest(BaseModel): db_type: Literal["mysql", "postgresql", "mongodb"] connection: DatabaseConnection selected_schema: str = Field(default="public", description="PostgreSQL schema (ignored for MySQL/MongoDB)") table_or_collection_names: List[str] = Field(default_factory=list, description="Optional list of tables/collections to check for existence") def to_connection_config(self) -> Dict[str, Any]: return { "db_type": self.db_type, "host": self.connection.host, "port": self.connection.port or {"mysql": 3306, "postgresql": 5432, "mongodb": 27017}[self.db_type], "database": self.connection.database, "username": self.connection.username, "password": self.connection.password, "selected_schema": self.selected_schema, "ssl_enabled": self.connection.ssl.enabled, "ssl_ca_cert": self.connection.ssl.ca_cert, "ssl_cert": self.connection.ssl.cert, "ssl_key": self.connection.ssl.key, } class DatabaseValidateResponse(BaseModel): success: bool time_ms: float message: str connection_details: str error_message: Optional[str] = None tables: List[TableValidationResult] = [] class EmbeddingItem(BaseModel): success: bool time_ms: float embeddings: List[float] = Field(default_factory=list) dimension: int = 0 error_message: Optional[str] = None class EmbeddingRequest(BaseModel): content: List[str] = Field(..., min_length=1, max_length=10, description="Array of text strings to embed (max 10)") dimension: int = Field(default=384, ge=384, le=384, description="Target embedding dimension (384 only)") @field_validator("content") @classmethod def validate_content_length(cls, v: List[str]) -> List[str]: if len(v) > 10: raise ValueError("Maximum 10 text items allowed per request.") if len(v) < 1: raise ValueError("At least 1 text item is required.") return v class EmbeddingResponse(BaseModel): success: bool time_ms: float success_count: int failed_count: int error_message: Optional[str] = None results: List[EmbeddingItem] # class VisionUrlRequest(BaseModel): # DISABLED (OOM mitigation) # urls: List[str] = Field(..., min_length=1, max_length=5, description="Array of image URLs to embed (max 5)") # # @field_validator("urls") # @classmethod # def validate_urls(cls, v: List[str]) -> List[str]: # for url in v: # if not url.startswith(("http://", "https://")): # raise ValueError(f"Invalid URL scheme: {url}") # return v class CodeItem(BaseModel): language: Literal["python", "javascript"] # "java" DISABLED (OOM mitigation) code: str = Field(..., min_length=1, max_length=65536, description="Source code to execute") class CodeExecutionRequest(BaseModel): items: List[CodeItem] = Field(..., min_length=1, max_length=5, description="Code execution items (max 5)") class CodeExecutionItemResult(BaseModel): success: bool output: str = "" error: Optional[str] = None exit_code: Optional[int] = None execution_time_ms: Optional[float] = None language: str timed_out: bool = False class CodeExecutionResponse(BaseModel): success: bool time_ms: float success_count: int failed_count: int error_message: Optional[str] = None results: List[CodeExecutionItemResult] # --------------------------------------------------------------------------- # Web Search (SearXNG) # --------------------------------------------------------------------------- class WebSearchResult(BaseModel): title: str url: str content: str = "" engine: str = "" category: str = "" published_date: Optional[str] = None class WebSearchSuggestion(BaseModel): suggestion: str class WebSearchInfobox(BaseModel): title: str = "" content: str = "" infobox: str = "" engine: str = "" urls: List[Dict[str, Any]] = [] class WebSearchRequest(BaseModel): q: str = Field(..., min_length=1, max_length=500, description="Search query") categories: str = Field(default="general", description="Comma-separated categories: general,images,videos,news,music,it,science,map,files,social media") language: str = Field(default="en", description="Language code (en, fr, de, hi, bn, etc.)") pageno: int = Field(default=1, ge=1, le=100, description="Page number") time_range: Optional[str] = Field(default=None, description="Time range: day, week, month, year") safesearch: int = Field(default=0, ge=0, le=2, description="Safe search level: 0=off, 1=moderate, 2=strict") engines: Optional[str] = Field(default=None, description="Comma-separated engine names to restrict to") max_results: int = Field(default=10, ge=1, le=50, description="Max results to return") class WebSearchResponse(BaseModel): success: bool time_ms: float query: str number_of_results: int results: List[WebSearchResult] suggestions: List[str] = [] infoboxes: List[WebSearchInfobox] = [] error: Optional[str] = None class WebSearchConfigResponse(BaseModel): success: bool time_ms: float instance_name: Optional[str] = None version: Optional[str] = None engines: List[Dict[str, Any]] = [] categories: List[str] = [] plugins: List[str] = [] error: Optional[str] = None # --------------------------------------------------------------------------- # Web Scraping (Scrapling) # --------------------------------------------------------------------------- class FetcherType(str, Enum): http = "http" dynamic = "dynamic" stealth = "stealth" class SelectorType(str, Enum): css = "css" xpath = "xpath" class ExtractionRule(BaseModel): field_name: str = Field(..., description="Key name in the output JSON.") selector: str = Field(..., description="CSS or XPath selector (e.g. '.price::text').") selector_type: SelectorType = Field(SelectorType.css) extract_all: bool = Field(False, description="Get a list of all matches.") auto_save: bool = Field(False, description="Commit element's DOM fingerprint to local SQLite.") auto_match: bool = Field(False, description="Use deterministic healing if layout breaks.") class ScrapeRequest(BaseModel): url: str = Field(..., description="Target URL to scrape.") fetcher_type: FetcherType = Field(FetcherType.http) rules: List[ExtractionRule] = Field(..., min_length=1, max_length=50) proxy: Optional[str] = Field(None, description="Proxy URL for this request.") network_idle: bool = Field(False, description="Wait for network idle (Dynamic/Stealth only).") class ScrapeResponse(BaseModel): success: bool time_ms: float url: str data: Dict[str, Any] fetcher: str error: Optional[str] = None class ScrapeHealthResponse(BaseModel): success: bool framework: str version: str fetchers_available: List[str] error: Optional[str] = None class WebSearchAutocompleteRequest(BaseModel): q: str = Field(..., min_length=1, max_length=200, description="Search query prefix for autocomplete") class WebSearchAutocompleteResponse(BaseModel): success: bool time_ms: float query: str suggestions: List[str] = [] error: Optional[str] = None class WebSearchStatsResponse(BaseModel): success: bool time_ms: float stats: Optional[Dict[str, Any]] = None error: Optional[str] = None class WebSearchEngineDescriptionsResponse(BaseModel): success: bool time_ms: float engines: Optional[Dict[str, Any]] = None error: Optional[str] = None class TokenGenerateRequest(BaseModel): subject: str = Field(..., min_length=1, max_length=256, description="Token subject (user ID, client ID, etc.)") role: Optional[str] = Field(None, max_length=64, description="User role for RBAC") permissions: Optional[List[str]] = Field(None, description="List of permission strings") issuer: Optional[str] = Field(None, max_length=128, description="Token issuer (overrides default)") audience: Optional[str] = Field(None, max_length=128, description="Token audience") expiry_minutes: Optional[int] = Field(None, ge=1, le=525600, description="Token lifetime in minutes") not_before_minutes: int = Field(default=0, ge=0, le=525600, description="Delay token validity by N minutes") extra_claims: Optional[Dict[str, Any]] = Field(None, description="Additional custom claims") secret: Optional[str] = Field(None, description="JWT signing secret (auto-generated if not provided)") algorithm: Optional[str] = Field(None, pattern="^(HS256|HS384|HS512)$", description="JWT signing algorithm (defaults to HS256)") class TokenGenerateResponse(BaseModel): success: bool time_ms: float token: Optional[str] = None claims: Optional[Dict[str, Any]] = None expires_at: Optional[str] = None valid_for: Optional[str] = None secret: Optional[str] = None algorithm: Optional[str] = None error: Optional[str] = None class TokenValidateRequest(BaseModel): token: str = Field(..., min_length=1, description="JWT token string to validate") audience: Optional[str] = Field(None, description="Expected audience to verify against") secret: Optional[str] = Field(None, description="Signing secret used during generation (uses server default if not provided)") algorithm: Optional[str] = Field(None, pattern="^(HS256|HS384|HS512)$", description="Algorithm used during generation (uses server default if not provided)") class TokenValidateResponse(BaseModel): success: bool time_ms: float valid: bool claims: Optional[Dict[str, Any]] = None error: Optional[str] = None class SqlValidationRequest(BaseModel): query: str = Field(..., min_length=1, max_length=100000, description="SQL query string to validate") dialect: Optional[str] = Field(None, description="SQL dialect (mysql, postgres, bigquery, snowflake, sqlite, etc.)") class SqlValidationResponse(BaseModel): success: bool time_ms: float valid: bool query_type: str dialect: Optional[str] = None is_read_only: bool errors: List[str] = [] warnings: List[str] = [] tables: List[str] = [] columns: List[str] = [] # --------------------------------------------------------------------------- # Vector Store (RAG) - Powered by Zvec # --------------------------------------------------------------------------- class VectorStoreCreate(BaseModel): name: str = Field(..., min_length=1, max_length=256, description="Human-readable name") description: Optional[str] = Field(None, max_length=1024) metadata: Dict[str, Any] = Field(default_factory=dict) class VectorStoreResponse(BaseModel): success: bool vector_store_id: str app_id: str name: str description: Optional[str] = None embedding_dimension: int document_count: int created_at: str metadata: Dict[str, Any] = {} warning: Optional[Dict[str, str]] = None class VectorStoreListResponse(BaseModel): success: bool total: int stores: List[VectorStoreResponse] class DocumentIngestRequest(BaseModel): doc_id: str = Field(..., min_length=1, max_length=256) text: str = Field(..., min_length=1) source: Optional[str] = Field(None, max_length=512) metadata: Dict[str, Any] = Field(default_factory=dict) chunk_size: Optional[int] = Field(None, ge=64, le=4096) chunk_overlap: Optional[int] = Field(None, ge=0, le=512) class DocumentIngestResponse(BaseModel): success: bool vector_store_id: str doc_id: str chunks_ingested: int time_ms: float error: Optional[str] = None class DocumentIngestUrlRequest(BaseModel): url: str = Field(..., min_length=1, description="URL of the PDF file to ingest") doc_id: str = Field(..., min_length=1, max_length=256) chunk_size: int = Field(512, ge=64, le=4096) chunk_overlap: int = Field(64, ge=0, le=512) class SearchRequest(BaseModel): query: str = Field(..., min_length=1, max_length=5000, description="Natural language query") top_k: int = Field(default=10, ge=1, le=100, description="Max results to return") filter: Optional[str] = Field(None, description="Filter expression (e.g. 'category = \"tech\"')") min_score: Optional[float] = Field(None, ge=0.0, le=1.0, description="Minimum similarity score threshold") include_vectors: bool = Field(default=False, description="Include vector embeddings in results") include_metadata: bool = Field(default=False, description="Include source metadata in results") class SearchResultItem(BaseModel): rank: int doc_id: str chunk_index: int text: str score: float source: Optional[str] = None metadata: Dict[str, Any] = {} vector: Optional[List[float]] = None class SearchResponse(BaseModel): success: bool vector_store_id: str query: str results: List[SearchResultItem] total_results: int time_ms: float error: Optional[str] = None class DeleteRequest(BaseModel): ids: Optional[List[str]] = Field(None) filter: Optional[str] = Field(None) class DeleteResponse(BaseModel): success: bool vector_store_id: str deleted_count: int time_ms: float error: Optional[str] = None # --------------------------------------------------------------------------- # Webhook / Socket # --------------------------------------------------------------------------- class ChannelCreateRequest(BaseModel): channel_id: Optional[str] = Field(None, min_length=1, max_length=64, description="Optional custom channel ID") secret: Optional[str] = Field(None, min_length=1, description="HMAC secret for webhook verification") buffer_size: Optional[int] = Field(None, ge=0, le=10000, description="Replay buffer size (0 = no replay)") class ChannelCreateResponse(BaseModel): channel_id: str webhook_url: str ws_url: str secret: Optional[str] = None buffer_size: int class ChannelInfoResponse(BaseModel): channel_id: str subscribers: int messages: int buffered: int buffer_size: int has_secret: bool created_at: float last_activity: float class ChannelListItem(BaseModel): channel_id: str subscribers: int messages: int buffered: int created_at: float last_activity: float class ChannelListResponse(BaseModel): channels: List[ChannelListItem] class ChannelDeleteResponse(BaseModel): deleted: str class WebhookResponse(BaseModel): status: str channel: str subscribers_notified: int message_id: int class WebhookSocketStatsResponse(BaseModel): channels: int total_messages: int total_subscribers: int channels_detail: Dict[str, Dict[str, Any]]