Files
Aperant/auto-claude/runners/ai_analyzer/claude_client.py
T
2025-12-16 21:57:45 +01:00

143 lines
4.0 KiB
Python

"""
Claude SDK client wrapper for AI analysis.
"""
import json
import os
from pathlib import Path
from typing import Any
try:
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
CLAUDE_SDK_AVAILABLE = True
except ImportError:
CLAUDE_SDK_AVAILABLE = False
class ClaudeAnalysisClient:
"""Wrapper for Claude SDK client with analysis-specific configuration."""
DEFAULT_MODEL = "claude-sonnet-4-5-20250929"
ALLOWED_TOOLS = ["Read", "Glob", "Grep"]
MAX_TURNS = 50
def __init__(self, project_dir: Path):
"""
Initialize Claude client.
Args:
project_dir: Root directory of project being analyzed
"""
if not CLAUDE_SDK_AVAILABLE:
raise RuntimeError(
"claude-agent-sdk not available. Install with: pip install claude-agent-sdk"
)
self.project_dir = project_dir
self._validate_oauth_token()
def _validate_oauth_token(self) -> None:
"""Validate that CLAUDE_CODE_OAUTH_TOKEN is set."""
if not os.environ.get("CLAUDE_CODE_OAUTH_TOKEN"):
raise ValueError("CLAUDE_CODE_OAUTH_TOKEN not set. Run: claude setup-token")
async def run_analysis_query(self, prompt: str) -> str:
"""
Run a Claude query for analysis.
Args:
prompt: The analysis prompt
Returns:
Claude's response text
"""
settings_file = self._create_settings_file()
try:
client = self._create_client(settings_file)
async with client:
await client.query(prompt)
return await self._collect_response(client)
finally:
# Cleanup settings file
if settings_file.exists():
settings_file.unlink()
def _create_settings_file(self) -> Path:
"""
Create temporary security settings file.
Returns:
Path to settings file
"""
settings = {
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Read(./**)",
"Glob(./**)",
"Grep(./**)",
],
},
}
settings_file = self.project_dir / ".claude_ai_analyzer_settings.json"
with open(settings_file, "w") as f:
json.dump(settings, f, indent=2)
return settings_file
def _create_client(self, settings_file: Path) -> Any:
"""
Create configured Claude SDK client.
Args:
settings_file: Path to security settings file
Returns:
ClaudeSDKClient instance
"""
system_prompt = (
f"You are a senior software architect analyzing this codebase. "
f"Your working directory is: {self.project_dir.resolve()}\n"
f"Use Read, Grep, and Glob tools to analyze actual code. "
f"Output your analysis as valid JSON only."
)
return ClaudeSDKClient(
options=ClaudeAgentOptions(
model=self.DEFAULT_MODEL,
system_prompt=system_prompt,
allowed_tools=self.ALLOWED_TOOLS,
max_turns=self.MAX_TURNS,
cwd=str(self.project_dir.resolve()),
settings=str(settings_file.resolve()),
)
)
async def _collect_response(self, client: Any) -> str:
"""
Collect text response from Claude client.
Args:
client: ClaudeSDKClient instance
Returns:
Collected response text
"""
response_text = ""
async for msg in client.receive_response():
msg_type = type(msg).__name__
if msg_type == "AssistantMessage":
for content in msg.content:
if hasattr(content, "text"):
response_text += content.text
return response_text