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,327 @@
|
|
|
1
|
+
"""Codex (OpenAI) adapter for CodeOptix.
|
|
2
|
+
|
|
3
|
+
This adapter interfaces with OpenAI Codex CLI, which uses the Responses API
|
|
4
|
+
for agent-based code generation and execution. The adapter follows the pattern
|
|
5
|
+
used in the official Codex TypeScript SDK, executing the CLI via subprocess
|
|
6
|
+
and parsing JSONL output.
|
|
7
|
+
|
|
8
|
+
Note: Codex is a full CLI tool that executes code in sandboxes. This adapter
|
|
9
|
+
provides a simplified interface for CodeOptix's evaluation framework.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import subprocess
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from codeoptix.adapters.base import AgentAdapter, AgentOutput
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CodexAdapter(AgentAdapter):
|
|
21
|
+
"""
|
|
22
|
+
Adapter for OpenAI Codex CLI.
|
|
23
|
+
|
|
24
|
+
Codex is a coding agent that uses OpenAI's Responses API. This adapter
|
|
25
|
+
executes Codex via the CLI (similar to the TypeScript SDK) and extracts
|
|
26
|
+
code and test outputs from the agent's responses.
|
|
27
|
+
|
|
28
|
+
The adapter uses `codex exec` in non-interactive mode with JSON output
|
|
29
|
+
to capture structured responses from the agent.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, config: dict[str, Any]):
|
|
33
|
+
"""Initialize Codex adapter."""
|
|
34
|
+
super().__init__(config)
|
|
35
|
+
|
|
36
|
+
# Get LLM configuration
|
|
37
|
+
llm_config = config.get("llm_config", {})
|
|
38
|
+
self.api_key = (
|
|
39
|
+
llm_config.get("api_key") or os.getenv("OPENAI_API_KEY") or os.getenv("CODEX_API_KEY")
|
|
40
|
+
)
|
|
41
|
+
self.model = llm_config.get("model", "gpt-4o")
|
|
42
|
+
self.base_url = llm_config.get("base_url")
|
|
43
|
+
|
|
44
|
+
# Codex CLI path (defaults to system PATH)
|
|
45
|
+
self.codex_path = config.get("codex_path") or self._find_codex_path()
|
|
46
|
+
|
|
47
|
+
# Working directory for Codex execution
|
|
48
|
+
self.working_directory = config.get("working_directory") or os.getcwd()
|
|
49
|
+
|
|
50
|
+
# Sandbox mode (read-only by default for safety)
|
|
51
|
+
self.sandbox_mode = config.get("sandbox_mode", "read-only")
|
|
52
|
+
|
|
53
|
+
# Get initial prompt if provided
|
|
54
|
+
self._current_prompt = config.get("prompt") or self._get_default_prompt()
|
|
55
|
+
|
|
56
|
+
def _find_codex_path(self) -> str:
|
|
57
|
+
"""Find Codex CLI executable path."""
|
|
58
|
+
# First check if codex is in PATH
|
|
59
|
+
import shutil
|
|
60
|
+
|
|
61
|
+
codex_path = shutil.which("codex")
|
|
62
|
+
if codex_path:
|
|
63
|
+
return codex_path
|
|
64
|
+
|
|
65
|
+
# Fallback: try common installation locations
|
|
66
|
+
possible_paths = [
|
|
67
|
+
os.path.expanduser("~/.local/bin/codex"),
|
|
68
|
+
"/usr/local/bin/codex",
|
|
69
|
+
"/opt/homebrew/bin/codex", # macOS Homebrew
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
for path in possible_paths:
|
|
73
|
+
if os.path.exists(path) and os.access(path, os.X_OK):
|
|
74
|
+
return path
|
|
75
|
+
|
|
76
|
+
# If not found, return "codex" and let subprocess handle the error
|
|
77
|
+
return "codex"
|
|
78
|
+
|
|
79
|
+
def _get_default_prompt(self) -> str:
|
|
80
|
+
"""Get default Codex system prompt."""
|
|
81
|
+
return """You are an expert Python programmer. Write clean, secure, and well-tested code.
|
|
82
|
+
Best practices:
|
|
83
|
+
- No hardcoded secrets or credentials
|
|
84
|
+
- Write comprehensive tests
|
|
85
|
+
- Follow specifications exactly"""
|
|
86
|
+
|
|
87
|
+
def execute(self, prompt: str, context: dict[str, Any] | None = None) -> AgentOutput:
|
|
88
|
+
"""
|
|
89
|
+
Execute Codex with a task prompt.
|
|
90
|
+
|
|
91
|
+
Uses `codex exec` in non-interactive mode with JSON output to capture
|
|
92
|
+
the agent's responses, code generation, and test execution.
|
|
93
|
+
"""
|
|
94
|
+
context = context or {}
|
|
95
|
+
|
|
96
|
+
# Build the full prompt with context
|
|
97
|
+
full_prompt = self._build_prompt(prompt, context)
|
|
98
|
+
|
|
99
|
+
# Execute Codex CLI
|
|
100
|
+
try:
|
|
101
|
+
result = self._execute_codex_cli(full_prompt)
|
|
102
|
+
|
|
103
|
+
# Parse JSONL output to extract code and tests
|
|
104
|
+
code, tests = self._extract_from_jsonl(result)
|
|
105
|
+
|
|
106
|
+
return AgentOutput(
|
|
107
|
+
code=code,
|
|
108
|
+
tests=tests,
|
|
109
|
+
traces=result.get("traces", []),
|
|
110
|
+
metadata={
|
|
111
|
+
"model": self.model,
|
|
112
|
+
"provider": "openai",
|
|
113
|
+
"codex_version": result.get("codex_version"),
|
|
114
|
+
"usage": result.get("usage"),
|
|
115
|
+
},
|
|
116
|
+
prompt_used=self._current_prompt,
|
|
117
|
+
)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
return AgentOutput(
|
|
120
|
+
code="",
|
|
121
|
+
tests=None,
|
|
122
|
+
traces=[{"type": "error", "error": str(e)}],
|
|
123
|
+
metadata={"error": str(e)},
|
|
124
|
+
prompt_used=self._current_prompt,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def _build_prompt(self, prompt: str, context: dict[str, Any]) -> str:
|
|
128
|
+
"""Build the full prompt including system instructions and context."""
|
|
129
|
+
parts = []
|
|
130
|
+
|
|
131
|
+
# Add system prompt if available
|
|
132
|
+
if self._current_prompt:
|
|
133
|
+
parts.append(f"System instructions: {self._current_prompt}\n")
|
|
134
|
+
|
|
135
|
+
# Add context
|
|
136
|
+
if context:
|
|
137
|
+
if "plan" in context:
|
|
138
|
+
parts.append(f"Plan: {context['plan']}\n")
|
|
139
|
+
if "requirements" in context:
|
|
140
|
+
reqs = context["requirements"]
|
|
141
|
+
if isinstance(reqs, list):
|
|
142
|
+
reqs = "\n".join(f"- {r}" for r in reqs)
|
|
143
|
+
parts.append(f"Requirements:\n{reqs}\n")
|
|
144
|
+
if "files" in context:
|
|
145
|
+
parts.append("Files:")
|
|
146
|
+
for file_path, content in context["files"].items():
|
|
147
|
+
parts.append(f"\n{file_path}:\n{content}")
|
|
148
|
+
|
|
149
|
+
# Add the main task
|
|
150
|
+
parts.append(f"\nTask: {prompt}")
|
|
151
|
+
|
|
152
|
+
return "\n".join(parts)
|
|
153
|
+
|
|
154
|
+
def _execute_codex_cli(self, prompt: str) -> dict[str, Any]:
|
|
155
|
+
"""
|
|
156
|
+
Execute Codex CLI via subprocess and parse JSONL output.
|
|
157
|
+
|
|
158
|
+
This follows the pattern from the TypeScript SDK, using `codex exec`
|
|
159
|
+
with `--experimental-json` flag to get structured output.
|
|
160
|
+
"""
|
|
161
|
+
# Build command arguments
|
|
162
|
+
cmd = [self.codex_path, "exec", "--experimental-json"]
|
|
163
|
+
|
|
164
|
+
# Add model if specified
|
|
165
|
+
if self.model:
|
|
166
|
+
cmd.extend(["--model", self.model])
|
|
167
|
+
|
|
168
|
+
# Add sandbox mode
|
|
169
|
+
if self.sandbox_mode:
|
|
170
|
+
cmd.extend(["--sandbox", self.sandbox_mode])
|
|
171
|
+
|
|
172
|
+
# Add working directory
|
|
173
|
+
if self.working_directory:
|
|
174
|
+
cmd.extend(["--cd", self.working_directory])
|
|
175
|
+
|
|
176
|
+
# Skip git repo check for evaluation contexts
|
|
177
|
+
cmd.append("--skip-git-repo-check")
|
|
178
|
+
|
|
179
|
+
# Set up environment
|
|
180
|
+
env = os.environ.copy()
|
|
181
|
+
if self.api_key:
|
|
182
|
+
env["CODEX_API_KEY"] = self.api_key
|
|
183
|
+
if self.base_url:
|
|
184
|
+
env["OPENAI_BASE_URL"] = self.base_url
|
|
185
|
+
|
|
186
|
+
# Execute and capture output
|
|
187
|
+
try:
|
|
188
|
+
process = subprocess.Popen(
|
|
189
|
+
cmd,
|
|
190
|
+
stdin=subprocess.PIPE,
|
|
191
|
+
stdout=subprocess.PIPE,
|
|
192
|
+
stderr=subprocess.PIPE,
|
|
193
|
+
text=True,
|
|
194
|
+
env=env,
|
|
195
|
+
cwd=self.working_directory,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
stdout, stderr = process.communicate(input=prompt, timeout=300)
|
|
199
|
+
|
|
200
|
+
if process.returncode != 0:
|
|
201
|
+
raise RuntimeError(f"Codex CLI exited with code {process.returncode}: {stderr}")
|
|
202
|
+
|
|
203
|
+
# Parse JSONL output
|
|
204
|
+
return self._parse_jsonl_output(stdout, stderr)
|
|
205
|
+
|
|
206
|
+
except subprocess.TimeoutExpired:
|
|
207
|
+
process.kill()
|
|
208
|
+
raise RuntimeError("Codex CLI execution timed out after 300 seconds")
|
|
209
|
+
except FileNotFoundError:
|
|
210
|
+
raise RuntimeError(
|
|
211
|
+
f"Codex CLI not found at '{self.codex_path}'. "
|
|
212
|
+
"Please install Codex CLI: npm install -g @openai/codex"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def _parse_jsonl_output(self, stdout: str, stderr: str) -> dict[str, Any]:
|
|
216
|
+
"""
|
|
217
|
+
Parse JSONL output from Codex CLI.
|
|
218
|
+
|
|
219
|
+
Codex outputs events as JSON Lines (JSONL), including:
|
|
220
|
+
- thread.started
|
|
221
|
+
- turn.started/turn.completed
|
|
222
|
+
- item.completed (agent_message, command_execution, file_change, etc.)
|
|
223
|
+
"""
|
|
224
|
+
traces = []
|
|
225
|
+
items = []
|
|
226
|
+
usage = None
|
|
227
|
+
final_message = None
|
|
228
|
+
|
|
229
|
+
# Parse each JSON line
|
|
230
|
+
for line in stdout.strip().split("\n"):
|
|
231
|
+
if not line.strip():
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
event = json.loads(line)
|
|
236
|
+
event_type = event.get("type", "")
|
|
237
|
+
|
|
238
|
+
traces.append(event)
|
|
239
|
+
|
|
240
|
+
if event_type == "turn.completed":
|
|
241
|
+
usage = event.get("usage")
|
|
242
|
+
elif event_type == "item.completed":
|
|
243
|
+
item = event.get("item", {})
|
|
244
|
+
items.append(item)
|
|
245
|
+
|
|
246
|
+
# Extract final agent message
|
|
247
|
+
if item.get("type") == "agent_message":
|
|
248
|
+
final_message = item.get("text", "")
|
|
249
|
+
|
|
250
|
+
except json.JSONDecodeError:
|
|
251
|
+
# Skip invalid JSON lines
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
"traces": traces,
|
|
256
|
+
"items": items,
|
|
257
|
+
"usage": usage,
|
|
258
|
+
"final_message": final_message,
|
|
259
|
+
"stderr": stderr,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
def _extract_from_jsonl(self, result: dict[str, Any]) -> tuple[str, str | None]:
|
|
263
|
+
"""
|
|
264
|
+
Extract code and tests from Codex JSONL output.
|
|
265
|
+
|
|
266
|
+
Looks for:
|
|
267
|
+
- file_change items (code files created/modified)
|
|
268
|
+
- command_execution items (test runs)
|
|
269
|
+
- agent_message items (code in markdown blocks)
|
|
270
|
+
"""
|
|
271
|
+
items = result.get("items", [])
|
|
272
|
+
code = ""
|
|
273
|
+
tests = None
|
|
274
|
+
|
|
275
|
+
# Look for file changes (code files)
|
|
276
|
+
code_files = []
|
|
277
|
+
test_files = []
|
|
278
|
+
|
|
279
|
+
for item in items:
|
|
280
|
+
item_type = item.get("type")
|
|
281
|
+
|
|
282
|
+
if item_type == "file_change":
|
|
283
|
+
file_path = item.get("file_path", "")
|
|
284
|
+
content = item.get("content", "")
|
|
285
|
+
|
|
286
|
+
if file_path.endswith(".py"):
|
|
287
|
+
if "test" in file_path.lower() or "test_" in file_path:
|
|
288
|
+
test_files.append((file_path, content))
|
|
289
|
+
else:
|
|
290
|
+
code_files.append((file_path, content))
|
|
291
|
+
|
|
292
|
+
elif item_type == "agent_message":
|
|
293
|
+
# Extract code from markdown code blocks in messages
|
|
294
|
+
text = item.get("text", "")
|
|
295
|
+
import re
|
|
296
|
+
|
|
297
|
+
code_blocks = re.findall(r"```(?:python)?\n(.*?)```", text, re.DOTALL)
|
|
298
|
+
if code_blocks:
|
|
299
|
+
code = code_blocks[0].strip()
|
|
300
|
+
|
|
301
|
+
# Use file changes if available, otherwise use extracted code
|
|
302
|
+
if code_files:
|
|
303
|
+
# Combine all code files
|
|
304
|
+
code = "\n\n".join(f"# {path}\n{content}" for path, content in code_files)
|
|
305
|
+
elif not code:
|
|
306
|
+
# Fallback to final message
|
|
307
|
+
final_message = result.get("final_message", "")
|
|
308
|
+
if final_message:
|
|
309
|
+
code = final_message
|
|
310
|
+
|
|
311
|
+
# Extract tests
|
|
312
|
+
if test_files:
|
|
313
|
+
tests = "\n\n".join(f"# {path}\n{content}" for path, content in test_files)
|
|
314
|
+
|
|
315
|
+
return code, tests
|
|
316
|
+
|
|
317
|
+
def get_prompt(self) -> str:
|
|
318
|
+
"""Get current Codex prompt."""
|
|
319
|
+
return self._current_prompt or ""
|
|
320
|
+
|
|
321
|
+
def update_prompt(self, new_prompt: str) -> None:
|
|
322
|
+
"""Update Codex prompt."""
|
|
323
|
+
self._current_prompt = new_prompt
|
|
324
|
+
|
|
325
|
+
def get_adapter_type(self) -> str:
|
|
326
|
+
"""Get adapter type identifier."""
|
|
327
|
+
return "codex"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Factory for creating agent adapters."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codeoptix.adapters.base import AgentAdapter
|
|
6
|
+
from codeoptix.adapters.basic import BasicAdapter
|
|
7
|
+
from codeoptix.adapters.claude_code import ClaudeCodeAdapter
|
|
8
|
+
from codeoptix.adapters.codex import CodexAdapter
|
|
9
|
+
from codeoptix.adapters.gemini_cli import GeminiCLIAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_adapter(adapter_type: str, config: dict[str, Any]) -> AgentAdapter:
|
|
13
|
+
"""
|
|
14
|
+
Factory function to create an agent adapter.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
adapter_type: Type of adapter ("basic", "claude-code", "codex", "gemini-cli")
|
|
18
|
+
config: Configuration dictionary for the adapter
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
AgentAdapter instance
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If adapter_type is not supported
|
|
25
|
+
"""
|
|
26
|
+
adapter_map = {
|
|
27
|
+
"basic": BasicAdapter,
|
|
28
|
+
"claude-code": ClaudeCodeAdapter,
|
|
29
|
+
"codex": CodexAdapter,
|
|
30
|
+
"gemini-cli": GeminiCLIAdapter,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
adapter_class = adapter_map.get(adapter_type)
|
|
34
|
+
if adapter_class is None:
|
|
35
|
+
supported = ", ".join(adapter_map.keys())
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"Unsupported adapter type: '{adapter_type}'. "
|
|
38
|
+
f"Supported types: {supported}. "
|
|
39
|
+
f"For testing without external agents, use 'basic'."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
return adapter_class(config)
|
|
44
|
+
except KeyError as e:
|
|
45
|
+
if "api_key" in str(e) or "llm_config" in str(e):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Missing required configuration for {adapter_type}. "
|
|
48
|
+
f"Please provide 'llm_config' with 'api_key' in the adapter configuration. "
|
|
49
|
+
f"Example: {{'llm_config': {{'provider': 'openai', 'api_key': 'your-key'}}}}"
|
|
50
|
+
) from e
|
|
51
|
+
raise
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Failed to initialize {adapter_type} adapter: {e}. "
|
|
55
|
+
f"Please check your configuration and API keys."
|
|
56
|
+
) from e
|