codeoptix 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codeoptix/__init__.py +8 -0
- codeoptix/acp/__init__.py +33 -0
- codeoptix/acp/agent.py +209 -0
- codeoptix/acp/bridge.py +402 -0
- codeoptix/acp/client_adapter.py +312 -0
- codeoptix/acp/code_extractor.py +125 -0
- codeoptix/acp/orchestrator.py +349 -0
- codeoptix/acp/registry.py +294 -0
- codeoptix/adapters/__init__.py +18 -0
- codeoptix/adapters/base.py +50 -0
- codeoptix/adapters/basic.py +195 -0
- codeoptix/adapters/claude_code.py +221 -0
- codeoptix/adapters/codex.py +327 -0
- codeoptix/adapters/factory.py +56 -0
- codeoptix/adapters/gemini_cli.py +370 -0
- codeoptix/artifacts/__init__.py +5 -0
- codeoptix/artifacts/manager.py +193 -0
- codeoptix/behaviors/__init__.py +45 -0
- codeoptix/behaviors/base.py +81 -0
- codeoptix/behaviors/insecure_code.py +129 -0
- codeoptix/behaviors/plan_drift.py +192 -0
- codeoptix/behaviors/vacuous_tests.py +198 -0
- codeoptix/cli.py +1468 -0
- codeoptix/evaluation/__init__.py +23 -0
- codeoptix/evaluation/bloom_integration.py +271 -0
- codeoptix/evaluation/engine.py +274 -0
- codeoptix/evaluation/evaluators.py +308 -0
- codeoptix/evaluation/scenario_generator.py +222 -0
- codeoptix/evolution/__init__.py +7 -0
- codeoptix/evolution/engine.py +206 -0
- codeoptix/evolution/gepa_integration.py +149 -0
- codeoptix/evolution/proposer.py +185 -0
- codeoptix/linters/__init__.py +13 -0
- codeoptix/linters/bandit_linter.py +172 -0
- codeoptix/linters/base.py +105 -0
- codeoptix/linters/coverage_linter.py +156 -0
- codeoptix/linters/flake8_linter.py +156 -0
- codeoptix/linters/html_accessibility_linter.py +374 -0
- codeoptix/linters/language_detector.py +150 -0
- codeoptix/linters/mypy_linter.py +184 -0
- codeoptix/linters/pip_audit_linter.py +152 -0
- codeoptix/linters/pylint_linter.py +198 -0
- codeoptix/linters/ruff_linter.py +206 -0
- codeoptix/linters/runner.py +186 -0
- codeoptix/linters/safety_linter.py +184 -0
- codeoptix/reflection/__init__.py +6 -0
- codeoptix/reflection/engine.py +70 -0
- codeoptix/reflection/generator.py +209 -0
- codeoptix/utils/__init__.py +1 -0
- codeoptix/utils/config.py +91 -0
- codeoptix/utils/llm.py +334 -0
- codeoptix/utils/retry.py +133 -0
- codeoptix/vendor/__init__.py +2 -0
- codeoptix/vendor/bloom/README.md +26 -0
- codeoptix/vendor/bloom/__init__.py +11 -0
- codeoptix/vendor/bloom/globals.py +39 -0
- codeoptix/vendor/bloom/orchestrators/ConversationOrchestrator.py +450 -0
- codeoptix/vendor/bloom/orchestrators/SimEnvOrchestrator.py +839 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/README.md +85 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/default.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/ideation-default.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_animal-welfare.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_contextual-optimism.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defend-objects.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defer-to-users.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_emotional-bond.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_flattery.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_hardcode-test-cases.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_increasing-pep.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_research-sandbagging.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/mo_self-promotion.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/sandbag.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/self-preferential-bias.json +18 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/static-prompts.yaml +72 -0
- codeoptix/vendor/bloom/prompts/configurable_prompts/web-search.json +18 -0
- codeoptix/vendor/bloom/prompts/step1_understanding.py +63 -0
- codeoptix/vendor/bloom/prompts/step2_ideation.py +254 -0
- codeoptix/vendor/bloom/prompts/step3_rollout.py +120 -0
- codeoptix/vendor/bloom/prompts/step4_judgment.py +183 -0
- codeoptix/vendor/bloom/schemas/behavior.schema.json +160 -0
- codeoptix/vendor/bloom/schemas/conversation.schema.json +51 -0
- codeoptix/vendor/bloom/schemas/transcript_schema.json +2225 -0
- codeoptix/vendor/bloom/scripts/step2_ideation.py +667 -0
- codeoptix/vendor/bloom/scripts/step4_judgment.py +811 -0
- codeoptix/vendor/bloom/transcript_utils.py +440 -0
- codeoptix/vendor/bloom/utils.py +700 -0
- codeoptix-0.1.3.dist-info/METADATA +295 -0
- codeoptix-0.1.3.dist-info/RECORD +92 -0
- codeoptix-0.1.3.dist-info/WHEEL +5 -0
- codeoptix-0.1.3.dist-info/entry_points.txt +2 -0
- codeoptix-0.1.3.dist-info/licenses/LICENSE +203 -0
- codeoptix-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Base adapter interface for coding agents."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AgentOutput:
|
|
10
|
+
"""Standardized output from any coding agent."""
|
|
11
|
+
|
|
12
|
+
code: str
|
|
13
|
+
tests: str | None = None
|
|
14
|
+
traces: list[dict[str, Any]] | None = None
|
|
15
|
+
metadata: dict[str, Any] | None = None
|
|
16
|
+
prompt_used: str | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentAdapter(ABC):
|
|
20
|
+
"""Base interface for agent adapters."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: dict[str, Any]):
|
|
23
|
+
"""Initialize adapter with configuration."""
|
|
24
|
+
self.config = config
|
|
25
|
+
self._current_prompt: str | None = None
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def execute(self, prompt: str, context: dict[str, Any] | None = None) -> AgentOutput:
|
|
29
|
+
"""
|
|
30
|
+
Execute agent with prompt and return standardized output.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
prompt: The task prompt for the agent
|
|
34
|
+
context: Optional context (files, workspace info, etc.)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
AgentOutput with code, tests, traces, and metadata
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def get_prompt(self) -> str:
|
|
42
|
+
"""Get current agent prompt/policy."""
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def update_prompt(self, new_prompt: str) -> None:
|
|
46
|
+
"""Update agent prompt/policy."""
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def get_adapter_type(self) -> str:
|
|
50
|
+
"""Get the type identifier for this adapter."""
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Basic agent adapter for testing and simple use cases."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codeoptix.adapters.base import AgentAdapter, AgentOutput
|
|
6
|
+
from codeoptix.utils.llm import LLMClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasicAdapter(AgentAdapter):
|
|
10
|
+
"""
|
|
11
|
+
Basic agent adapter that works with any LLM provider.
|
|
12
|
+
|
|
13
|
+
This adapter doesn't require any external agent software and can be used
|
|
14
|
+
for testing or simple evaluation scenarios. It uses the LLM directly
|
|
15
|
+
with a simple coding assistant prompt.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, config: dict[str, Any]):
|
|
19
|
+
"""Initialize basic adapter."""
|
|
20
|
+
super().__init__(config)
|
|
21
|
+
|
|
22
|
+
# Get LLM configuration
|
|
23
|
+
llm_config = config.get("llm_config", {})
|
|
24
|
+
if not llm_config:
|
|
25
|
+
raise ValueError("BasicAdapter requires 'llm_config' in configuration")
|
|
26
|
+
|
|
27
|
+
# Create LLM client
|
|
28
|
+
from codeoptix.utils.llm import LLMProvider, create_llm_client
|
|
29
|
+
|
|
30
|
+
provider_name = llm_config.get("provider", "ollama")
|
|
31
|
+
self.llm_client: LLMClient = create_llm_client(
|
|
32
|
+
LLMProvider(provider_name), llm_config.get("api_key"), llm_config.get("model")
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Set model
|
|
36
|
+
self.model = llm_config.get("model", "llama3.2:3b")
|
|
37
|
+
|
|
38
|
+
# Set default prompt
|
|
39
|
+
self._current_prompt = config.get("prompt") or self._get_default_prompt()
|
|
40
|
+
|
|
41
|
+
def get_adapter_type(self) -> str:
|
|
42
|
+
"""Get adapter type."""
|
|
43
|
+
return "basic"
|
|
44
|
+
|
|
45
|
+
def _get_default_prompt(self) -> str:
|
|
46
|
+
"""Get default basic coding assistant prompt."""
|
|
47
|
+
return """You are a helpful coding assistant. Your task is to write clean, secure, and well-tested code.
|
|
48
|
+
|
|
49
|
+
Guidelines:
|
|
50
|
+
- Write secure code: validate inputs, avoid hardcoded secrets, use proper error handling
|
|
51
|
+
- Write comprehensive tests: cover edge cases, use meaningful assertions
|
|
52
|
+
- Follow coding best practices: clear variable names, proper structure, documentation
|
|
53
|
+
- Consider the user's requirements and context provided
|
|
54
|
+
|
|
55
|
+
When given a coding task, provide:
|
|
56
|
+
1. Well-structured, readable code
|
|
57
|
+
2. Appropriate tests for the code
|
|
58
|
+
3. Brief explanation of the implementation"""
|
|
59
|
+
|
|
60
|
+
def get_prompt(self) -> str:
|
|
61
|
+
"""Get current system prompt."""
|
|
62
|
+
return self._current_prompt or self._get_default_prompt()
|
|
63
|
+
|
|
64
|
+
def update_prompt(self, new_prompt: str) -> None:
|
|
65
|
+
"""Update the system prompt."""
|
|
66
|
+
self._current_prompt = new_prompt
|
|
67
|
+
|
|
68
|
+
def execute(self, prompt: str, context: dict[str, Any] | None = None) -> "AgentOutput":
|
|
69
|
+
"""
|
|
70
|
+
Execute a coding task using the LLM directly.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
prompt: The coding task prompt
|
|
74
|
+
context: Optional context information
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
AgentOutput with generated code and tests
|
|
78
|
+
"""
|
|
79
|
+
from codeoptix.adapters.base import AgentOutput
|
|
80
|
+
|
|
81
|
+
context = context or {}
|
|
82
|
+
|
|
83
|
+
# Build the full prompt
|
|
84
|
+
full_prompt = self._build_full_prompt(prompt, context)
|
|
85
|
+
|
|
86
|
+
# Get response from LLM
|
|
87
|
+
messages = [
|
|
88
|
+
{"role": "system", "content": self._current_prompt},
|
|
89
|
+
{"role": "user", "content": full_prompt},
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
response = self.llm_client.chat_completion(
|
|
93
|
+
messages=messages, model=self.model, temperature=0.7, max_tokens=2048
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Parse the response into code and tests
|
|
97
|
+
code, tests = self._parse_response(response)
|
|
98
|
+
|
|
99
|
+
return AgentOutput(
|
|
100
|
+
code=code,
|
|
101
|
+
tests=tests,
|
|
102
|
+
prompt_used=self._current_prompt,
|
|
103
|
+
metadata={"model": self.model, "adapter_type": "basic", "full_response": response},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _build_full_prompt(self, prompt: str, context: dict[str, Any]) -> str:
|
|
107
|
+
"""Build the full prompt including context."""
|
|
108
|
+
parts = []
|
|
109
|
+
|
|
110
|
+
# Add context if provided
|
|
111
|
+
if context.get("plan"):
|
|
112
|
+
parts.append(f"Plan/Requirements: {context['plan']}")
|
|
113
|
+
if context.get("existing_code"):
|
|
114
|
+
parts.append(f"Existing Code:\n{context['existing_code']}")
|
|
115
|
+
if context.get("requirements"):
|
|
116
|
+
parts.append(f"Requirements: {context['requirements']}")
|
|
117
|
+
|
|
118
|
+
# Add the main task
|
|
119
|
+
parts.append(f"Task: {prompt}")
|
|
120
|
+
|
|
121
|
+
# Add output format instructions
|
|
122
|
+
parts.append("""
|
|
123
|
+
Please provide your response in the following format:
|
|
124
|
+
|
|
125
|
+
CODE:
|
|
126
|
+
```python
|
|
127
|
+
# Your code here - must be valid Python
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
TESTS:
|
|
131
|
+
```python
|
|
132
|
+
# Your tests here - must be valid Python
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
EXPLANATION:
|
|
136
|
+
Brief explanation of your implementation.
|
|
137
|
+
""")
|
|
138
|
+
|
|
139
|
+
return "\n\n".join(parts)
|
|
140
|
+
|
|
141
|
+
def _parse_response(self, response: str) -> tuple[str, str]:
|
|
142
|
+
"""Parse LLM response into code and tests."""
|
|
143
|
+
code = ""
|
|
144
|
+
tests = ""
|
|
145
|
+
|
|
146
|
+
# Simple parsing - look for CODE and TESTS sections
|
|
147
|
+
lines = response.split("\n")
|
|
148
|
+
current_section = None
|
|
149
|
+
code_lines = []
|
|
150
|
+
test_lines = []
|
|
151
|
+
|
|
152
|
+
for line in lines:
|
|
153
|
+
line_lower = line.lower().strip()
|
|
154
|
+
if line_lower.startswith("code:") or "```" in line_lower:
|
|
155
|
+
current_section = "code"
|
|
156
|
+
continue
|
|
157
|
+
if line_lower.startswith(("tests:", "test:")):
|
|
158
|
+
current_section = "tests"
|
|
159
|
+
continue
|
|
160
|
+
if current_section == "code" and line.strip():
|
|
161
|
+
# Remove markdown code blocks
|
|
162
|
+
if "```" in line:
|
|
163
|
+
continue
|
|
164
|
+
code_lines.append(line)
|
|
165
|
+
elif current_section == "tests" and line.strip():
|
|
166
|
+
# Remove markdown code blocks
|
|
167
|
+
if "```" in line:
|
|
168
|
+
continue
|
|
169
|
+
test_lines.append(line)
|
|
170
|
+
|
|
171
|
+
# If no clear sections found, try to extract from the whole response
|
|
172
|
+
if not code_lines and not test_lines:
|
|
173
|
+
# Look for function definitions for code
|
|
174
|
+
# Look for test functions for tests
|
|
175
|
+
for line in lines:
|
|
176
|
+
if line.strip().startswith("def ") and "test" in line.lower():
|
|
177
|
+
current_section = "tests"
|
|
178
|
+
test_lines.append(line)
|
|
179
|
+
elif line.strip().startswith("def ") and current_section != "tests":
|
|
180
|
+
current_section = "code"
|
|
181
|
+
code_lines.append(line)
|
|
182
|
+
elif current_section == "code":
|
|
183
|
+
code_lines.append(line)
|
|
184
|
+
elif current_section == "tests":
|
|
185
|
+
test_lines.append(line)
|
|
186
|
+
|
|
187
|
+
code = "\n".join(code_lines).strip()
|
|
188
|
+
tests = "\n".join(test_lines).strip()
|
|
189
|
+
|
|
190
|
+
# Fallback if parsing failed
|
|
191
|
+
if not code and not tests:
|
|
192
|
+
# Assume the entire response is code
|
|
193
|
+
code = response.strip()
|
|
194
|
+
|
|
195
|
+
return code, tests
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Claude Code adapter for CodeOptix.
|
|
2
|
+
|
|
3
|
+
This adapter interfaces with Claude Code by using the Anthropic API directly,
|
|
4
|
+
which is what Claude Code uses under the hood. Since Claude Code is not open source,
|
|
5
|
+
we use the Anthropic SDK to match the same API patterns.
|
|
6
|
+
|
|
7
|
+
Note: Claude Code is a full CLI tool with plugins, hooks, and agents. This adapter
|
|
8
|
+
provides a simplified interface for CodeOptix's evaluation framework using the
|
|
9
|
+
same Anthropic API that Claude Code uses.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from codeoptix.adapters.base import AgentAdapter, AgentOutput
|
|
16
|
+
from codeoptix.utils.llm import LLMProvider, create_llm_client
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClaudeCodeAdapter(AgentAdapter):
|
|
20
|
+
"""
|
|
21
|
+
Adapter for Claude Code (Anthropic's coding agent).
|
|
22
|
+
|
|
23
|
+
Claude Code uses the Anthropic Messages API. This adapter uses the same
|
|
24
|
+
API directly to match Claude Code's behavior. The adapter follows the
|
|
25
|
+
patterns from the Anthropic Python SDK reference implementation.
|
|
26
|
+
|
|
27
|
+
Since Claude Code is not open source, we use the Anthropic API directly,
|
|
28
|
+
which is the same API that Claude Code uses internally.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, config: dict[str, Any]):
|
|
32
|
+
"""Initialize Claude Code adapter."""
|
|
33
|
+
super().__init__(config)
|
|
34
|
+
|
|
35
|
+
# Get LLM configuration
|
|
36
|
+
llm_config = config.get("llm_config", {})
|
|
37
|
+
# Respect provider from config (defaults to ANTHROPIC for backward compatibility)
|
|
38
|
+
provider_name = llm_config.get("provider", "anthropic").upper()
|
|
39
|
+
try:
|
|
40
|
+
provider = LLMProvider[provider_name]
|
|
41
|
+
except KeyError as e:
|
|
42
|
+
supported_providers = ", ".join([p.value for p in LLMProvider])
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Invalid LLM provider '{provider_name}'. "
|
|
45
|
+
f"Supported providers: {supported_providers}"
|
|
46
|
+
) from e
|
|
47
|
+
|
|
48
|
+
# Get API key (not required for Ollama)
|
|
49
|
+
api_key = None
|
|
50
|
+
if provider != LLMProvider.OLLAMA:
|
|
51
|
+
api_key = (
|
|
52
|
+
llm_config.get("api_key")
|
|
53
|
+
or os.getenv(f"{provider_name}_API_KEY")
|
|
54
|
+
or os.getenv("ANTHROPIC_API_KEY")
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self.llm_client = create_llm_client(provider, api_key=api_key)
|
|
58
|
+
# Use model from config, or default based on provider
|
|
59
|
+
if provider == LLMProvider.OLLAMA:
|
|
60
|
+
self.model = llm_config.get("model", "llama3.1:8b")
|
|
61
|
+
else:
|
|
62
|
+
# Use current Claude Code default model (claude-opus-4-5-20251101)
|
|
63
|
+
self.model = llm_config.get("model", "claude-opus-4-5-20251101")
|
|
64
|
+
self.temperature = llm_config.get("temperature", 1.0)
|
|
65
|
+
self.max_tokens = llm_config.get("max_tokens", 4096)
|
|
66
|
+
|
|
67
|
+
# Get initial prompt if provided
|
|
68
|
+
self._current_prompt = config.get("prompt") or self._get_default_prompt()
|
|
69
|
+
|
|
70
|
+
def _get_default_prompt(self) -> str:
|
|
71
|
+
"""Get default Claude Code system prompt.
|
|
72
|
+
|
|
73
|
+
This matches the style of prompts used in Claude Code plugins and
|
|
74
|
+
follows Claude Code's best practices for coding assistants.
|
|
75
|
+
"""
|
|
76
|
+
return """You are a helpful coding assistant. You write clean, secure, and well-tested code.
|
|
77
|
+
Follow best practices:
|
|
78
|
+
- Write secure code (no hardcoded secrets, validate inputs)
|
|
79
|
+
- Write meaningful tests
|
|
80
|
+
- Follow the user's requirements and planning artifacts
|
|
81
|
+
- Use appropriate error handling
|
|
82
|
+
- Write clear, maintainable code"""
|
|
83
|
+
|
|
84
|
+
def execute(self, prompt: str, context: dict[str, Any] | None = None) -> AgentOutput:
|
|
85
|
+
"""
|
|
86
|
+
Execute Claude Code with a task prompt.
|
|
87
|
+
|
|
88
|
+
Uses the Anthropic Messages API (same API that Claude Code uses) to
|
|
89
|
+
generate code and test responses. Follows the patterns from the
|
|
90
|
+
Anthropic Python SDK reference implementation.
|
|
91
|
+
"""
|
|
92
|
+
context = context or {}
|
|
93
|
+
|
|
94
|
+
# Build messages for Claude (following Anthropic SDK pattern)
|
|
95
|
+
messages = []
|
|
96
|
+
|
|
97
|
+
# Add system prompt (Anthropic API uses separate system parameter)
|
|
98
|
+
system_prompt = self._current_prompt if self._current_prompt else None
|
|
99
|
+
|
|
100
|
+
# Add context if provided
|
|
101
|
+
user_prompt = prompt
|
|
102
|
+
if context:
|
|
103
|
+
context_str = self._format_context(context)
|
|
104
|
+
user_prompt = f"{context_str}\n\nTask: {prompt}"
|
|
105
|
+
|
|
106
|
+
messages.append({"role": "user", "content": user_prompt})
|
|
107
|
+
|
|
108
|
+
# Get response from Claude using Anthropic API
|
|
109
|
+
try:
|
|
110
|
+
# Use the LLM client which wraps Anthropic SDK
|
|
111
|
+
# The client handles system message separately (as per Anthropic API)
|
|
112
|
+
if system_prompt:
|
|
113
|
+
messages.insert(0, {"role": "system", "content": system_prompt})
|
|
114
|
+
|
|
115
|
+
response = self.llm_client.chat_completion(
|
|
116
|
+
messages=messages,
|
|
117
|
+
model=self.model,
|
|
118
|
+
temperature=self.temperature,
|
|
119
|
+
max_tokens=self.max_tokens,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Extract code from response
|
|
123
|
+
code, tests = self._extract_code_and_tests(response)
|
|
124
|
+
|
|
125
|
+
return AgentOutput(
|
|
126
|
+
code=code,
|
|
127
|
+
tests=tests,
|
|
128
|
+
traces=[{"type": "llm_response", "content": response}],
|
|
129
|
+
metadata={
|
|
130
|
+
"model": self.model,
|
|
131
|
+
"temperature": self.temperature,
|
|
132
|
+
"provider": "anthropic",
|
|
133
|
+
"api": "messages", # Anthropic Messages API
|
|
134
|
+
},
|
|
135
|
+
prompt_used=self._current_prompt,
|
|
136
|
+
)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
# Return error output
|
|
139
|
+
return AgentOutput(
|
|
140
|
+
code="",
|
|
141
|
+
tests=None,
|
|
142
|
+
traces=[{"type": "error", "error": str(e)}],
|
|
143
|
+
metadata={"error": str(e)},
|
|
144
|
+
prompt_used=self._current_prompt,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _format_context(self, context: dict[str, Any]) -> str:
|
|
148
|
+
"""Format context for inclusion in prompt."""
|
|
149
|
+
parts = []
|
|
150
|
+
|
|
151
|
+
if "files" in context:
|
|
152
|
+
parts.append("Files:")
|
|
153
|
+
for file_path, content in context["files"].items():
|
|
154
|
+
parts.append(f"\n{file_path}:\n{content}")
|
|
155
|
+
|
|
156
|
+
if "workspace" in context:
|
|
157
|
+
parts.append(f"\nWorkspace: {context['workspace']}")
|
|
158
|
+
|
|
159
|
+
return "\n".join(parts)
|
|
160
|
+
|
|
161
|
+
def _extract_code_and_tests(self, response: str) -> tuple[str, str | None]:
|
|
162
|
+
"""
|
|
163
|
+
Extract code and tests from Claude response.
|
|
164
|
+
|
|
165
|
+
Claude Code typically returns code in markdown code blocks. This method
|
|
166
|
+
extracts Python code blocks and identifies test files/patterns.
|
|
167
|
+
"""
|
|
168
|
+
import re
|
|
169
|
+
|
|
170
|
+
code = ""
|
|
171
|
+
tests = None
|
|
172
|
+
|
|
173
|
+
# Find all code blocks (support various language tags)
|
|
174
|
+
# Pattern matches: ```python, ```py, ```, etc.
|
|
175
|
+
code_blocks = re.findall(r"```(?:python|py)?\n(.*?)```", response, re.DOTALL)
|
|
176
|
+
|
|
177
|
+
if code_blocks:
|
|
178
|
+
# First block is usually the main code
|
|
179
|
+
code = code_blocks[0].strip()
|
|
180
|
+
|
|
181
|
+
# Look for test files or test blocks
|
|
182
|
+
# Pattern 1: Explicit test file mentions
|
|
183
|
+
test_file_pattern = r"(?:test|tests?)[^`]*?\.py.*?```(?:python|py)?\n(.*?)```"
|
|
184
|
+
# Pattern 2: Test function definitions
|
|
185
|
+
test_function_pattern = r"def\s+test_.*?```(?:python|py)?\n(.*?)```"
|
|
186
|
+
# Pattern 3: Test class definitions
|
|
187
|
+
test_class_pattern = r"class\s+Test.*?```(?:python|py)?\n(.*?)```"
|
|
188
|
+
|
|
189
|
+
test_patterns = [test_file_pattern, test_function_pattern, test_class_pattern]
|
|
190
|
+
|
|
191
|
+
test_matches = []
|
|
192
|
+
for pattern in test_patterns:
|
|
193
|
+
matches = re.findall(pattern, response, re.DOTALL | re.IGNORECASE)
|
|
194
|
+
if matches:
|
|
195
|
+
test_matches.extend(matches)
|
|
196
|
+
|
|
197
|
+
if test_matches:
|
|
198
|
+
tests = "\n\n".join(test_matches)
|
|
199
|
+
|
|
200
|
+
# If no code blocks found, check if entire response is code
|
|
201
|
+
if not code:
|
|
202
|
+
# Check if response looks like code (no markdown, mostly code-like)
|
|
203
|
+
if not response.strip().startswith("#") and "\n" in response:
|
|
204
|
+
# Might be raw code without markdown
|
|
205
|
+
code = response.strip()
|
|
206
|
+
else:
|
|
207
|
+
code = response
|
|
208
|
+
|
|
209
|
+
return code, tests
|
|
210
|
+
|
|
211
|
+
def get_prompt(self) -> str:
|
|
212
|
+
"""Get current Claude Code prompt."""
|
|
213
|
+
return self._current_prompt or ""
|
|
214
|
+
|
|
215
|
+
def update_prompt(self, new_prompt: str) -> None:
|
|
216
|
+
"""Update Claude Code prompt."""
|
|
217
|
+
self._current_prompt = new_prompt
|
|
218
|
+
|
|
219
|
+
def get_adapter_type(self) -> str:
|
|
220
|
+
"""Get adapter type identifier."""
|
|
221
|
+
return "claude-code"
|