#!/usr/bin/env python3 """ engine.py — The Chaos Game Engine Thin coordinator that owns the GameEngine class. All heavy lifting is delegated to sub-modules: paths, models, prompts, config, context, state, tools_handler, llm, validation, parsing, strategies. """ from __future__ import annotations import sys from pathlib import Path from engine_lib.paths import CONFIG_PATH from engine_lib.models import GenerationResult, TurnResult from engine_lib import config from engine_lib import strategies class GameEngine: """Owns configuration and delegates generation to standalone strategies.""" def __init__(self, session_dir: str | Path | None = None): from engine_lib.paths import SESSION_DIR self.session_dir = Path(session_dir) if session_dir else SESSION_DIR self.config = config.load_config(CONFIG_PATH) # ── Config accessors ──────────────────────────────────────────────── @property def model(self) -> str: return config.get_model(self.config) @property def api_key(self) -> str | None: return config.get_api_key(self.config) @property def api_base(self) -> str | None: return config.get_api_base(self.config) @property def temperature(self) -> float: return config.get_temperature(self.config) @property def max_tokens(self) -> int: return config.get_max_tokens(self.config) @property def timeout(self) -> int: return config.get_timeout(self.config) # ── Generation (delegated) ────────────────────────────────────────── def generate( self, player_action: str | None = None, last_narrative: str | None = None, ) -> GenerationResult: return strategies.generate( player_action=player_action, last_narrative=last_narrative, model=self.model, temperature=self.temperature, timeout=self.timeout, max_tokens=self.max_tokens, api_key=self.api_key, api_base=self.api_base, ) def generate_stream(self, player_action=None, last_narrative=None): yield from strategies.generate_stream( player_action=player_action, last_narrative=last_narrative, model=self.model, temperature=self.temperature, timeout=self.timeout, max_tokens=self.max_tokens, api_key=self.api_key, api_base=self.api_base, ) def generate_with_tools( self, player_action: str | None = None, last_prompt: str | None = None, on_thought: callable = None, on_action: callable = None, on_player_roll: callable = None, on_debug: callable = None, ) -> TurnResult: return strategies.generate_with_tools( player_action=player_action, last_prompt=last_prompt, on_thought=on_thought, on_action=on_action, on_player_roll=on_player_roll, on_debug=on_debug, model=self.model, temperature=self.temperature, timeout=self.timeout, max_tokens=self.max_tokens, api_key=self.api_key, api_base=self.api_base, ) def generate_with_tools_single( self, player_action: str | None = None, last_prompt: str | None = None, on_thought: callable = None, on_action: callable = None, on_player_roll: callable = None, on_debug: callable = None, ) -> TurnResult: return strategies.generate_with_tools_single( player_action=player_action, last_prompt=last_prompt, on_thought=on_thought, on_action=on_action, on_player_roll=on_player_roll, on_debug=on_debug, model=self.model, temperature=self.temperature, timeout=self.timeout, max_tokens=self.max_tokens, api_key=self.api_key, api_base=self.api_base, ) # ── CLI entry point (for testing) ───────────────────────────────────────── def main(): """Generate a turn from the command line (debug/testing).""" import argparse parser = argparse.ArgumentParser(description="The Chaos Game Engine (CLI)") parser.add_argument("--action", "-a", help="Player action text") parser.add_argument("--last", "-l", help="Last narrative text") args = parser.parse_args() engine = GameEngine() result = engine.generate_with_tools_single( player_action=args.action, last_prompt=args.last, ) if result.error: print(f"ERROR: {result.error}", file=sys.stderr) sys.exit(1) print(result.book_log) if result.user_prompt: print(f"\n{result.user_prompt}") if result.log_entry: print(f"\n[Log] {result.log_entry}") if result.ambience: print(f"[Ambience] {result.ambience}") if __name__ == "__main__": main()