"""CLI backend registry. Each backend knows how to build a command-line invocation for a specific AI CLI tool. To add a new backend (gemini, opencode, tarmina, …), subclass ``BackendSpec``, implement ``build_cmd``, and register an instance in ``BACKENDS``. """ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from typing import TYPE_CHECKING if TYPE_CHECKING: from pathlib import Path @dataclass(frozen=True, slots=True) class BackendSpec(ABC): """How to invoke a specific CLI backend.""" name: str @abstractmethod def build_cmd( self, *, cli: str, model: str, prompt: str, repo_dir: Path, plugin_dir: Path | None = None, ) -> tuple[list[str], str | None]: """Return ``(argv, cwd_or_None)``.""" def build_edit_cmd( self, *, cli: str, model: str, prompt: str, repo_dir: Path, plugin_dir: Path | None = None, agent: str = "codeflash-ci", ) -> tuple[list[str], str | None]: """Return ``(argv, cwd_or_None)`` with autonomous edit permissions. Default falls back to ``build_cmd``; override for backends that need extra flags to enable file editing. """ return self.build_cmd( cli=cli, model=model, prompt=prompt, repo_dir=repo_dir, plugin_dir=plugin_dir, ) @dataclass(frozen=True, slots=True) class ClaudeBackend(BackendSpec): """Claude Code CLI backend.""" def build_cmd( self, *, cli: str, model: str, prompt: str, repo_dir: Path, plugin_dir: Path | None = None, ) -> tuple[list[str], str | None]: cmd = [cli, "-p", prompt, "--model", model] if plugin_dir: cmd += ["--plugin-dir", str(plugin_dir)] return cmd, str(repo_dir) def build_edit_cmd( self, *, cli: str, model: str, prompt: str, repo_dir: Path, plugin_dir: Path | None = None, agent: str = "codeflash-ci", ) -> tuple[list[str], str | None]: cmd = [ cli, "-p", prompt, "--model", model, "--agent", agent, "--max-turns", "200", "--dangerously-skip-permissions", ] if plugin_dir: cmd += ["--plugin-dir", str(plugin_dir)] return cmd, str(repo_dir) @dataclass(frozen=True, slots=True) class CodexBackend(BackendSpec): """OpenAI Codex CLI backend.""" def build_cmd( self, *, cli: str, model: str, prompt: str, repo_dir: Path, plugin_dir: Path | None = None, ) -> tuple[list[str], str | None]: cmd = [ cli, "exec", "--model", model, "--full-auto", "-C", str(repo_dir), "-o", "/dev/stdout", prompt, ] return cmd, None BACKENDS: dict[str, BackendSpec] = { "claude": ClaudeBackend(name="claude"), "codex": CodexBackend(name="codex"), } def get_backend(name: str) -> BackendSpec: """Look up a backend by name. Raises ``ValueError`` for unknown backends. """ if name not in BACKENDS: known = ", ".join(sorted(BACKENDS)) msg = f"Unknown backend {name!r}. Known: {known}" raise ValueError(msg) return BACKENDS[name]