""" GumYum NPC API Shared Data Models and Exceptions Cross-engine AI-powered NPC dialogue and quest system This module contains shared data models and exceptions used by both sync and async clients. """ from enum import Enum from typing import Dict, List, Optional, Any, Union from datetime import datetime try: from pydantic import BaseModel, Field except ImportError: BaseModel = None Field = None # ============================================================================ # EXCEPTIONS # ============================================================================ class GumYumError(Exception): """Base exception for all GumYum SDK errors""" def __init__( self, message: str, status_code: Optional[int] = None, response_data: Optional[Dict[str, Any]] = None, ): super().__init__(message) self.message = message self.status_code = status_code self.response_data = response_data or {} def __repr__(self) -> str: return f"{self.__class__.__name__}({self.message!r}, status_code={self.status_code})" class GumYumAPIError(GumYumError): """General API error from the server""" pass class GumYumAuthError(GumYumError): """Authentication or authorization error (401, 403)""" pass class GumYumNotFoundError(GumYumError): """Resource not found error (404)""" pass class GumYumValidationError(GumYumError): """Request validation error (400, 422)""" def __init__( self, message: str, status_code: Optional[int] = None, response_data: Optional[Dict[str, Any]] = None, validation_errors: Optional[list] = None, ): super().__init__(message, status_code, response_data) self.validation_errors = validation_errors or [] class GumYumServerError(GumYumError): """Server error (5xx)""" pass class GumYumNetworkError(GumYumError): """Network connectivity error""" pass class GumYumTimeoutError(GumYumNetworkError): """Request timeout error""" pass class GumYumRateLimitError(GumYumAPIError): """Rate limit exceeded (429)""" def __init__( self, message: str, status_code: Optional[int] = None, response_data: Optional[Dict[str, Any]] = None, retry_after: Optional[int] = None, ): super().__init__(message, status_code, response_data) self.retry_after = retry_after # Seconds to wait before retry # ============================================================================ # ENUMS # ============================================================================ class ChatRole(str, Enum): """Chat message roles""" USER = "user" ASSISTANT = "assistant" SYSTEM = "system" # ============================================================================ # CHAT MODELS # ============================================================================ class ChatMessage(BaseModel): """Chat message with role and content""" role: Union[ChatRole, str] content: str def to_dict(self) -> dict: """Convert to API-compatible dictionary""" return {"role": self.role, "content": self.content} class ChatChoice(BaseModel): """Chat completion choice""" index: int message: ChatMessage finish_reason: Optional[str] = None class Usage(BaseModel): """Token usage information""" prompt_tokens: int completion_tokens: int total_tokens: int # Optional fields that may be None prompt_tokens_details: Optional[Dict[str, Any]] = None completion_tokens_details: Optional[Dict[str, Any]] = None class ChatCompletion(BaseModel): """OpenAI-compatible chat completion response""" id: str object: str = "chat.completion" created: int model: str choices: List[ChatChoice] usage: Optional[Usage] = None npc_context: Optional[Dict[str, Any]] = None mood_transition: Optional[Dict[str, Any]] = None # ============================================================================ # NPC MODELS # ============================================================================ class SpawnedData(BaseModel): """Data determined at spawn time""" location: str mood: str stress_level: int # 1-9 class NPCProfile(BaseModel): """Full NPC profile data returned from save/update operations""" npc_id: int # 64-bit integer NPC ID name: str profession: str age: Optional[int] = None sex: Optional[str] = None # "male", "female", "neutral" personality_type: int # Personality type (1-9) wing: Optional[int] = None stress_level: Optional[int] = None mood: Optional[str] = None likes: List[str] = Field(default_factory=list) dislikes: List[str] = Field(default_factory=list) motivations: List[str] = Field(default_factory=list) fears: List[str] = Field(default_factory=list) relationships: List[Dict[str, Any]] = Field(default_factory=list) background: Optional[str] = None location: Optional[str] = None purpose: Optional[str] = None spawned: SpawnedData created_at: Optional[Union[str, int]] = None updated_at: Optional[Union[str, int]] = None custom_name: Optional[str] = None inventory: List[Dict[str, Any]] = Field(default_factory=list) stats: Dict[str, Any] = Field(default_factory=dict) skills: List[str] = Field(default_factory=list) tags: List[str] = Field(default_factory=list) metadata: Dict[str, Any] = Field(default_factory=dict) universe_id: Optional[str] = None seed: Optional[int] = None cached: Optional[bool] = None cache_url: Optional[str] = None class NPC(BaseModel): """NPC instance with chat capabilities""" npc_id: int name: str profession: str personality_type: int # Personality type (1-9) mood: Optional[str] = None spawned: Optional[SpawnedData] = None universe_id: Optional[str] = None world_seed: Optional[int] = None cached: bool = False cache_url: Optional[str] = None chat_history: List[dict] = Field(default_factory=list) age: Optional[int] = None sex: Optional[str] = None # "male", "female", "neutral" wing: Optional[int] = None stress_level: Optional[int] = None likes: List[str] = Field(default_factory=list) dislikes: List[str] = Field(default_factory=list) motivations: List[str] = Field(default_factory=list) fears: List[str] = Field(default_factory=list) relationships: List[Dict[str, Any]] = Field(default_factory=list) background: Optional[str] = None location: Optional[str] = None purpose: Optional[str] = None # ============================================================================ # UNIVERSE MODELS # ============================================================================ class UniverseThemeInfo(BaseModel): """Universe theme information""" id: str title: str description: Optional[str] = None tags: List[str] = Field(default_factory=list) mood_id: Optional[str] = None class UniverseListItem(BaseModel): """Universe list item for listing endpoints""" universe_id: str name: str hash: str custom_name: Optional[str] = None theme_id: Optional[str] = None mood_id: Optional[str] = None mood_name: Optional[str] = None class UniverseData(BaseModel): """Full universe data structure""" universe_id: str name: str hash: str custom_name: Optional[str] = None theme_data: Optional[UniverseThemeInfo] = None data: Dict[str, Any] = Field(default_factory=dict) mood_id: Optional[str] = None mood_name: Optional[str] = None class PublicUniverse(BaseModel): """Public universe information""" id: str name: str hash: Optional[str] = None description: Optional[str] = None tags: List[str] = Field(default_factory=list) mood_id: Optional[str] = None mood_name: Optional[str] = None npc_count: int = 0 # ============================================================================ # AUTH MODELS # ============================================================================ class AuthToken(BaseModel): """Authentication token response""" access_token: str refresh_token: str token_type: str = "Bearer" expires_in: int user_id: str username: str class UserProfile(BaseModel): """User profile information""" user_id: str username: str email: str created_at: Union[str, int] # Can be timestamp or ISO string universes_count: int = 0 npcs_count: int = 0 statistics: Optional[Dict[str, Any]] = None class ApiKey(BaseModel): """API key information (without secret)""" id: str name: str public_key: str scopes: List[str] created_at: str last_used_at: Optional[str] = None expires_at: Optional[str] = None class ApiKeyPair(BaseModel): """API key pair with secret (only returned on creation)""" id: str name: str public_key: str secret_key: str scopes: List[str] created_at: str expires_at: Optional[str] = None # ============================================================================ # STATS MODELS # ============================================================================ class APIUsage(BaseModel): """API usage statistics - flexible structure""" model_config = {"extra": "allow"} # Allow additional fields requests_today: Optional[int] = 0 requests_month: Optional[int] = 0 rate_limit: Optional[int] = 0 class NPCStats(BaseModel): """User's NPC statistics - flexible structure to match API""" model_config = {"extra": "allow"} # Allow additional fields total_npcs: Optional[int] = 0 total_messages: Optional[int] = 0 unique_universes: Optional[int] = 0 api_usage: Optional[Dict[str, Any]] = Field( default_factory=dict ) # API returns dict structure source: Optional[str] = None # ============================================================================ # MOOD MODELS # ============================================================================ class MoodData(BaseModel): """Mood data structure""" categories: Dict[str, Dict[str, Dict[str, str]]] class MoodItem(BaseModel): """Mood list item""" mood_id: str categories_count: int filename: str class MoodUploadResponse(BaseModel): """Mood upload response""" success: bool mood_id: str categories_count: int