npc-clients-python/gumyum_models.py

401 lines
10 KiB
Python

"""
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