"""Agentic Claude Code adapter. llm-connect's ClaudeCodeAdapter is a text-generation adapter (`claude --print`, no working directory, no tool grants). An executor run needs an *agentic* session: file edits and git commits inside the target repo, under a hard tool allow-list. This adapter subclasses it, keeping the llm-connect LLMAdapter interface so a hosted adapter can be swapped in later, and adds: - cwd pinned to the target repo - --permission-mode acceptEdits - allow-list from a named tool profile (default: green-commit-only) """ from __future__ import annotations import subprocess from pathlib import Path from llm_connect.claude_code import ClaudeCodeAdapter from llm_connect.exceptions import LLMSubprocessError, LLMTimeoutError from llm_connect.models import LLMResponse, RunConfig from agent_harness.profiles import ToolProfile, get_profile # Backward-compatible alias for the seed profile allow-list string. ALLOWED_TOOLS = get_profile("green-commit-only").allowed_tools class AgenticClaudeCodeAdapter(ClaudeCodeAdapter): def __init__( self, workdir: Path, *, tool_profile: str | ToolProfile = "green-commit-only", **kwargs, ): super().__init__(**kwargs) self._workdir = workdir if isinstance(tool_profile, ToolProfile): self._profile = tool_profile else: self._profile = get_profile(tool_profile) @property def tool_profile(self) -> ToolProfile: return self._profile def _build_command(self, config: RunConfig) -> list[str]: cmd = [ self._cli_path, "--print", "--permission-mode", "acceptEdits", "--allowedTools", self._profile.allowed_tools, ] if self._model: cmd.extend(["--model", self._model]) return cmd def execute_prompt(self, prompt: str, config: RunConfig) -> LLMResponse: self._preflight_budget(config) cmd = self._build_command(config) timeout = config.timeout_seconds or self._config.timeout_seconds try: result = subprocess.run( cmd, input=prompt, capture_output=True, text=True, timeout=timeout, cwd=self._workdir, ) except subprocess.TimeoutExpired as exc: raise LLMTimeoutError( f"claude CLI timed out after {timeout}s", cause=exc ) from exc if result.returncode != 0: raise LLMSubprocessError( f"claude CLI exited with code {result.returncode}", return_code=result.returncode, stderr=result.stderr, ) response = LLMResponse( content=result.stdout, model=self._model or "claude-code-cli", usage={}, finish_reason="stop", metadata={ "provider": "claude-code-agentic", "cli_path": self._cli_path, "workdir": str(self._workdir), "tool_profile": self._profile.name, }, ) self._consume_budget(config, response) return response