"""Claude client abstraction. SDK preferred, `claude -p` CLI fallback.""" from __future__ import annotations import os import shutil import subprocess # Anthropic SDK requires fully-qualified dated model IDs (the bare alias # `claude-haiku-4-5` works in Claude Code CLI but may not resolve via SDK). DEFAULT_MODEL = "claude-haiku-4-5-20251001" def _have_anthropic_sdk() -> bool: try: import anthropic # noqa: F401 return True except ImportError: return False def _have_claude_cli() -> bool: return shutil.which("claude") is not None def _call_via_sdk(prompt: str, model: str, max_tokens: int) -> str: import anthropic client = anthropic.Anthropic() response = client.messages.create( model=model, max_tokens=max_tokens, messages=[{"role": "user", "content": prompt}], ) return response.content[0].text def _call_via_cli(prompt: str, model: str, max_tokens: int) -> str: """Shell out to Claude Code CLI. max_tokens is ignored (CLI handles defaults).""" result = subprocess.run( ["claude", "-p", prompt, "--model", model], capture_output=True, text=True, check=True, timeout=60, ) return result.stdout.strip() def call_claude( prompt: str, *, model: str = DEFAULT_MODEL, max_tokens: int = 1000, ) -> str: """Send a prompt to Claude. Tries the anthropic SDK first (requires ANTHROPIC_API_KEY), falls back to the `claude -p` CLI if available. Raises RuntimeError if neither works. Note: ``max_tokens`` is honored by the SDK path but ignored by the CLI path (the CLI sets its own defaults). """ if _have_anthropic_sdk() and os.getenv("ANTHROPIC_API_KEY"): return _call_via_sdk(prompt, model, max_tokens) if _have_claude_cli(): return _call_via_cli(prompt, model, max_tokens) raise RuntimeError( "No Claude client available. Either:\n" " - Install the `anthropic` SDK and set ANTHROPIC_API_KEY, or\n" " - Install Claude Code CLI (`claude` on PATH)" )