| | """ |
| | DungeonMaster AI - MCP Integration Models |
| | |
| | Pydantic models for enhanced tool results and protocols for game state access. |
| | """ |
| |
|
| | from __future__ import annotations |
| |
|
| | from datetime import datetime |
| | from enum import Enum |
| | from typing import Protocol, runtime_checkable |
| |
|
| | from pydantic import BaseModel, Field |
| |
|
| |
|
| | class ConnectionState(str, Enum): |
| | """Connection state for MCP server.""" |
| |
|
| | DISCONNECTED = "disconnected" |
| | CONNECTING = "connecting" |
| | CONNECTED = "connected" |
| | ERROR = "error" |
| | RECONNECTING = "reconnecting" |
| |
|
| |
|
| | class CircuitBreakerState(str, Enum): |
| | """Circuit breaker states for connection management.""" |
| |
|
| | CLOSED = "closed" |
| | OPEN = "open" |
| | HALF_OPEN = "half_open" |
| |
|
| |
|
| | class RollType(str, Enum): |
| | """Types of dice rolls for formatting.""" |
| |
|
| | STANDARD = "standard" |
| | ATTACK = "attack" |
| | DAMAGE = "damage" |
| | SAVE = "save" |
| | CHECK = "check" |
| | INITIATIVE = "initiative" |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | @runtime_checkable |
| | class GameStateProtocol(Protocol): |
| | """ |
| | Protocol for game state access. |
| | |
| | This interface allows tool wrappers to update game state without |
| | depending on the full GameState implementation (coming in Phase 4). |
| | """ |
| |
|
| | session_id: str |
| | in_combat: bool |
| | party: list[str] |
| | recent_events: list[dict[str, object]] |
| |
|
| | def add_event( |
| | self, |
| | event_type: str, |
| | description: str, |
| | data: dict[str, object], |
| | ) -> None: |
| | """Add an event to recent events.""" |
| | ... |
| |
|
| | def get_character(self, character_id: str) -> dict[str, object] | None: |
| | """Get character data from cache.""" |
| | ... |
| |
|
| | def update_character_cache( |
| | self, |
| | character_id: str, |
| | data: dict[str, object], |
| | ) -> None: |
| | """Update character data in cache.""" |
| | ... |
| |
|
| | def set_combat_state(self, combat_state: dict[str, object] | None) -> None: |
| | """Set or clear combat state.""" |
| | ... |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class FormattedResult(BaseModel): |
| | """ |
| | Base class for formatted tool results. |
| | |
| | All tool wrappers return results in this format to provide |
| | multiple output formats for different consumers. |
| | """ |
| |
|
| | raw_result: dict[str, object] = Field( |
| | default_factory=dict, |
| | description="Original MCP tool result", |
| | ) |
| | chat_display: str = Field( |
| | default="", |
| | description="Markdown formatted for chat display", |
| | ) |
| | ui_data: dict[str, object] = Field( |
| | default_factory=dict, |
| | description="Structured data for UI components", |
| | ) |
| | voice_narration: str = Field( |
| | default="", |
| | description="TTS-friendly plain text for voice narration", |
| | ) |
| |
|
| | |
| | state_updated: bool = Field( |
| | default=False, |
| | description="Whether game state was updated", |
| | ) |
| | events_logged: list[str] = Field( |
| | default_factory=list, |
| | description="List of event types logged to session", |
| | ) |
| | ui_updates_needed: list[str] = Field( |
| | default_factory=list, |
| | description="UI components that need refresh", |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class DiceRollResult(FormattedResult): |
| | """Enhanced dice roll result with game context.""" |
| |
|
| | notation: str = Field( |
| | default="", |
| | description="Dice notation (e.g., '2d6+3')", |
| | ) |
| | individual_rolls: list[int] = Field( |
| | default_factory=list, |
| | description="Individual dice results", |
| | ) |
| | modifier: int = Field( |
| | default=0, |
| | description="Modifier applied to roll", |
| | ) |
| | total: int = Field( |
| | default=0, |
| | description="Total roll result", |
| | ) |
| | roll_type: RollType = Field( |
| | default=RollType.STANDARD, |
| | description="Type of roll for context", |
| | ) |
| | is_critical: bool = Field( |
| | default=False, |
| | description="Natural 20 on d20", |
| | ) |
| | is_fumble: bool = Field( |
| | default=False, |
| | description="Natural 1 on d20", |
| | ) |
| | success: bool | None = Field( |
| | default=None, |
| | description="Success/failure for checks with DC", |
| | ) |
| | dc: int | None = Field( |
| | default=None, |
| | description="Difficulty class if applicable", |
| | ) |
| | reason: str = Field( |
| | default="", |
| | description="Reason for the roll", |
| | ) |
| | timestamp: datetime = Field( |
| | default_factory=datetime.now, |
| | description="When the roll occurred", |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class HPChangeResult(FormattedResult): |
| | """Enhanced HP modification result with death handling.""" |
| |
|
| | character_id: str = Field( |
| | default="", |
| | description="Character ID", |
| | ) |
| | character_name: str = Field( |
| | default="Unknown", |
| | description="Character name for display", |
| | ) |
| | previous_hp: int = Field( |
| | default=0, |
| | description="HP before modification", |
| | ) |
| | current_hp: int = Field( |
| | default=0, |
| | description="HP after modification", |
| | ) |
| | max_hp: int = Field( |
| | default=1, |
| | description="Maximum HP", |
| | ) |
| | change_amount: int = Field( |
| | default=0, |
| | description="Absolute value of HP change", |
| | ) |
| | is_damage: bool = Field( |
| | default=False, |
| | description="True if damage, False if healing", |
| | ) |
| | damage_type: str | None = Field( |
| | default=None, |
| | description="Type of damage if applicable", |
| | ) |
| | is_unconscious: bool = Field( |
| | default=False, |
| | description="Character is at 0 HP or below", |
| | ) |
| | requires_death_save: bool = Field( |
| | default=False, |
| | description="Character needs to make death saves", |
| | ) |
| | is_dead: bool = Field( |
| | default=False, |
| | description="Character died (massive damage)", |
| | ) |
| | is_bloodied: bool = Field( |
| | default=False, |
| | description="Character is at 50% HP or below", |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class CombatantInfo(BaseModel): |
| | """Information about a combatant in initiative order.""" |
| |
|
| | id: str = Field(description="Combatant ID") |
| | name: str = Field(description="Combatant name") |
| | initiative: int = Field(description="Initiative roll result") |
| | is_player: bool = Field(default=False, description="Is this a player character") |
| | is_current: bool = Field(default=False, description="Is it this combatant's turn") |
| | hp_current: int = Field(default=0, description="Current HP") |
| | hp_max: int = Field(default=1, description="Maximum HP") |
| | hp_percent: float = Field(default=100.0, description="HP percentage") |
| | conditions: list[str] = Field(default_factory=list, description="Active conditions") |
| | status: str = Field(default="healthy", description="Status string") |
| |
|
| |
|
| | class CombatStateResult(FormattedResult): |
| | """Enhanced combat state result.""" |
| |
|
| | action: str = Field( |
| | default="", |
| | description="Combat action: start, end, next_turn, etc.", |
| | ) |
| | round_number: int | None = Field( |
| | default=None, |
| | description="Current combat round", |
| | ) |
| | current_combatant: str | None = Field( |
| | default=None, |
| | description="Name of current combatant", |
| | ) |
| | current_combatant_is_player: bool = Field( |
| | default=False, |
| | description="Whether current combatant is a player", |
| | ) |
| | turn_order: list[CombatantInfo] = Field( |
| | default_factory=list, |
| | description="Initiative order with combatant info", |
| | ) |
| | combat_ended: bool = Field( |
| | default=False, |
| | description="Whether combat has ended", |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class MCPConnectionStatus(BaseModel): |
| | """Status information for MCP connection.""" |
| |
|
| | state: ConnectionState = Field( |
| | default=ConnectionState.DISCONNECTED, |
| | description="Current connection state", |
| | ) |
| | is_available: bool = Field( |
| | default=False, |
| | description="Whether MCP is available for use", |
| | ) |
| | url: str = Field( |
| | default="", |
| | description="MCP server URL", |
| | ) |
| | last_successful_call: datetime | None = Field( |
| | default=None, |
| | description="When the last successful call was made", |
| | ) |
| | consecutive_failures: int = Field( |
| | default=0, |
| | description="Number of consecutive failures", |
| | ) |
| | circuit_breaker_state: CircuitBreakerState = Field( |
| | default=CircuitBreakerState.CLOSED, |
| | description="Circuit breaker state", |
| | ) |
| | tools_count: int = Field( |
| | default=0, |
| | description="Number of available tools", |
| | ) |
| | error_message: str | None = Field( |
| | default=None, |
| | description="Last error message if any", |
| | ) |
| |
|