codeoptix 0.1.0__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.
Files changed (91) hide show
  1. codeoptix/__init__.py +8 -0
  2. codeoptix/acp/__init__.py +33 -0
  3. codeoptix/acp/agent.py +209 -0
  4. codeoptix/acp/bridge.py +402 -0
  5. codeoptix/acp/client_adapter.py +312 -0
  6. codeoptix/acp/code_extractor.py +125 -0
  7. codeoptix/acp/orchestrator.py +349 -0
  8. codeoptix/acp/registry.py +294 -0
  9. codeoptix/adapters/__init__.py +18 -0
  10. codeoptix/adapters/base.py +50 -0
  11. codeoptix/adapters/basic.py +195 -0
  12. codeoptix/adapters/claude_code.py +218 -0
  13. codeoptix/adapters/codex.py +327 -0
  14. codeoptix/adapters/factory.py +56 -0
  15. codeoptix/adapters/gemini_cli.py +370 -0
  16. codeoptix/artifacts/__init__.py +5 -0
  17. codeoptix/artifacts/manager.py +193 -0
  18. codeoptix/behaviors/__init__.py +45 -0
  19. codeoptix/behaviors/base.py +81 -0
  20. codeoptix/behaviors/insecure_code.py +129 -0
  21. codeoptix/behaviors/plan_drift.py +192 -0
  22. codeoptix/behaviors/vacuous_tests.py +198 -0
  23. codeoptix/cli.py +1472 -0
  24. codeoptix/evaluation/__init__.py +23 -0
  25. codeoptix/evaluation/bloom_integration.py +271 -0
  26. codeoptix/evaluation/engine.py +274 -0
  27. codeoptix/evaluation/evaluators.py +308 -0
  28. codeoptix/evaluation/scenario_generator.py +222 -0
  29. codeoptix/evolution/__init__.py +7 -0
  30. codeoptix/evolution/engine.py +206 -0
  31. codeoptix/evolution/gepa_integration.py +149 -0
  32. codeoptix/evolution/proposer.py +185 -0
  33. codeoptix/linters/__init__.py +13 -0
  34. codeoptix/linters/bandit_linter.py +172 -0
  35. codeoptix/linters/base.py +105 -0
  36. codeoptix/linters/coverage_linter.py +156 -0
  37. codeoptix/linters/flake8_linter.py +156 -0
  38. codeoptix/linters/html_accessibility_linter.py +374 -0
  39. codeoptix/linters/language_detector.py +150 -0
  40. codeoptix/linters/mypy_linter.py +184 -0
  41. codeoptix/linters/pip_audit_linter.py +152 -0
  42. codeoptix/linters/pylint_linter.py +198 -0
  43. codeoptix/linters/ruff_linter.py +206 -0
  44. codeoptix/linters/runner.py +186 -0
  45. codeoptix/linters/safety_linter.py +184 -0
  46. codeoptix/reflection/__init__.py +6 -0
  47. codeoptix/reflection/engine.py +70 -0
  48. codeoptix/reflection/generator.py +209 -0
  49. codeoptix/utils/__init__.py +1 -0
  50. codeoptix/utils/config.py +91 -0
  51. codeoptix/utils/llm.py +332 -0
  52. codeoptix/utils/retry.py +133 -0
  53. codeoptix/vendor/__init__.py +2 -0
  54. codeoptix/vendor/bloom/README.md +26 -0
  55. codeoptix/vendor/bloom/__init__.py +11 -0
  56. codeoptix/vendor/bloom/globals.py +39 -0
  57. codeoptix/vendor/bloom/orchestrators/ConversationOrchestrator.py +450 -0
  58. codeoptix/vendor/bloom/orchestrators/SimEnvOrchestrator.py +839 -0
  59. codeoptix/vendor/bloom/prompts/configurable_prompts/README.md +85 -0
  60. codeoptix/vendor/bloom/prompts/configurable_prompts/default.json +18 -0
  61. codeoptix/vendor/bloom/prompts/configurable_prompts/ideation-default.json +18 -0
  62. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_animal-welfare.json +18 -0
  63. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_contextual-optimism.json +18 -0
  64. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defend-objects.json +18 -0
  65. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_defer-to-users.json +18 -0
  66. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_emotional-bond.json +18 -0
  67. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_flattery.json +18 -0
  68. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_hardcode-test-cases.json +18 -0
  69. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_increasing-pep.json +18 -0
  70. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_research-sandbagging.json +18 -0
  71. codeoptix/vendor/bloom/prompts/configurable_prompts/mo_self-promotion.json +18 -0
  72. codeoptix/vendor/bloom/prompts/configurable_prompts/sandbag.json +18 -0
  73. codeoptix/vendor/bloom/prompts/configurable_prompts/self-preferential-bias.json +18 -0
  74. codeoptix/vendor/bloom/prompts/configurable_prompts/static-prompts.yaml +72 -0
  75. codeoptix/vendor/bloom/prompts/configurable_prompts/web-search.json +18 -0
  76. codeoptix/vendor/bloom/prompts/step1_understanding.py +63 -0
  77. codeoptix/vendor/bloom/prompts/step2_ideation.py +254 -0
  78. codeoptix/vendor/bloom/prompts/step3_rollout.py +120 -0
  79. codeoptix/vendor/bloom/prompts/step4_judgment.py +183 -0
  80. codeoptix/vendor/bloom/schemas/behavior.schema.json +160 -0
  81. codeoptix/vendor/bloom/schemas/conversation.schema.json +51 -0
  82. codeoptix/vendor/bloom/schemas/transcript_schema.json +2225 -0
  83. codeoptix/vendor/bloom/scripts/step2_ideation.py +667 -0
  84. codeoptix/vendor/bloom/scripts/step4_judgment.py +811 -0
  85. codeoptix/vendor/bloom/transcript_utils.py +440 -0
  86. codeoptix/vendor/bloom/utils.py +700 -0
  87. codeoptix-0.1.0.dist-info/METADATA +304 -0
  88. codeoptix-0.1.0.dist-info/RECORD +91 -0
  89. codeoptix-0.1.0.dist-info/WHEEL +4 -0
  90. codeoptix-0.1.0.dist-info/entry_points.txt +2 -0
  91. codeoptix-0.1.0.dist-info/licenses/LICENSE +203 -0
@@ -0,0 +1,349 @@
1
+ """Agent Orchestration - Route to best agent for each task."""
2
+
3
+ import logging
4
+ from typing import Any
5
+
6
+ from acp import text_block
7
+
8
+ from codeoptix.acp.code_extractor import extract_code_from_message, extract_code_from_text
9
+ from codeoptix.acp.registry import ACPAgentRegistry
10
+ from codeoptix.evaluation import EvaluationEngine
11
+ from codeoptix.utils.llm import LLMClient
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class AgentOrchestrator:
17
+ """Orchestrates multiple ACP agents for task execution."""
18
+
19
+ def __init__(
20
+ self,
21
+ registry: ACPAgentRegistry,
22
+ evaluation_engine: EvaluationEngine | None = None,
23
+ llm_client: LLMClient | None = None,
24
+ ):
25
+ """Initialize agent orchestrator.
26
+
27
+ Args:
28
+ registry: ACP agent registry
29
+ evaluation_engine: Optional evaluation engine
30
+ llm_client: Optional LLM client
31
+ """
32
+ self.registry = registry
33
+ self.evaluation_engine = evaluation_engine
34
+ self.llm_client = llm_client
35
+
36
+ async def route_to_agent(
37
+ self,
38
+ prompt: str,
39
+ agent_name: str | None = None,
40
+ context: dict[str, Any] | None = None,
41
+ ) -> dict[str, Any]:
42
+ """Route a prompt to the best agent.
43
+
44
+ Args:
45
+ prompt: The prompt to execute
46
+ agent_name: Specific agent name (if None, selects best agent)
47
+ context: Additional context
48
+
49
+ Returns:
50
+ Result dictionary with agent response and metadata
51
+ """
52
+ # Select agent
53
+ if agent_name:
54
+ selected_agent = agent_name
55
+ else:
56
+ selected_agent = await self._select_best_agent(prompt, context)
57
+
58
+ if not selected_agent:
59
+ raise ValueError("No agent available")
60
+
61
+ # Connect to agent
62
+ connection = await self.registry.connect(selected_agent)
63
+ session_id = self.registry.get_session_id(selected_agent)
64
+
65
+ if not session_id:
66
+ raise RuntimeError(f"No session ID for agent {selected_agent}")
67
+
68
+ # Send prompt
69
+ response = await connection.prompt(
70
+ session_id=session_id,
71
+ prompt=[text_block(prompt)],
72
+ )
73
+
74
+ return {
75
+ "agent": selected_agent,
76
+ "response": response,
77
+ "session_id": session_id,
78
+ }
79
+
80
+ async def _select_best_agent(
81
+ self,
82
+ prompt: str,
83
+ context: dict[str, Any] | None = None,
84
+ ) -> str | None:
85
+ """Select the best agent for a given prompt.
86
+
87
+ Args:
88
+ prompt: The prompt
89
+ context: Additional context
90
+
91
+ Returns:
92
+ Agent name or None if no agents available
93
+ """
94
+ agents = self.registry.list_agents()
95
+ if not agents:
96
+ return None
97
+
98
+ # Intelligent agent selection based on:
99
+ # - Agent capabilities (from registry)
100
+ # - Task type (inferred from prompt)
101
+ # - Context requirements
102
+
103
+ # Check if context specifies an agent
104
+ if context and "preferred_agent" in context:
105
+ preferred = context["preferred_agent"]
106
+ if preferred in agents:
107
+ return preferred
108
+
109
+ # Infer task type from prompt
110
+ prompt_lower = prompt.lower()
111
+
112
+ # Security-focused tasks
113
+ if any(
114
+ keyword in prompt_lower
115
+ for keyword in ["security", "secure", "vulnerability", "exploit", "attack"]
116
+ ):
117
+ # Prefer agents with security capabilities
118
+ for agent_name in agents:
119
+ agent_config = self.registry.get_agent(agent_name)
120
+ if agent_config and "security" in [c.lower() for c in agent_config.capabilities]:
121
+ return agent_name
122
+
123
+ # Code review tasks
124
+ if any(keyword in prompt_lower for keyword in ["review", "critique", "judge", "evaluate"]):
125
+ # Prefer agents with review capabilities
126
+ for agent_name in agents:
127
+ agent_config = self.registry.get_agent(agent_name)
128
+ if agent_config and "review" in [c.lower() for c in agent_config.capabilities]:
129
+ return agent_name
130
+
131
+ # Default: use first available agent
132
+ return agents[0]
133
+
134
+ async def execute_multi_agent_workflow(
135
+ self,
136
+ workflow: list[dict[str, Any]],
137
+ ) -> list[dict[str, Any]]:
138
+ """Execute a multi-agent workflow.
139
+
140
+ Args:
141
+ workflow: List of workflow steps, each with 'agent', 'prompt', etc.
142
+
143
+ Returns:
144
+ List of results from each step
145
+ """
146
+ results = []
147
+ for step in workflow:
148
+ agent_name = step.get("agent")
149
+ prompt = step.get("prompt", "")
150
+ context = step.get("context", {})
151
+
152
+ result = await self.route_to_agent(
153
+ prompt=prompt,
154
+ agent_name=agent_name,
155
+ context=context,
156
+ )
157
+ results.append(result)
158
+
159
+ return results
160
+
161
+
162
+ class MultiAgentJudge:
163
+ """Multi-agent judge - Use different agents for generation vs. judgment."""
164
+
165
+ def __init__(
166
+ self,
167
+ registry: ACPAgentRegistry,
168
+ generate_agent: str,
169
+ judge_agent: str,
170
+ evaluation_engine: EvaluationEngine | None = None,
171
+ llm_client: LLMClient | None = None,
172
+ ):
173
+ """Initialize multi-agent judge.
174
+
175
+ Args:
176
+ registry: ACP agent registry
177
+ generate_agent: Name of agent for code generation
178
+ judge_agent: Name of agent for code judgment/critique
179
+ evaluation_engine: Optional evaluation engine
180
+ llm_client: Optional LLM client
181
+ """
182
+ self.registry = registry
183
+ self.generate_agent = generate_agent
184
+ self.judge_agent = judge_agent
185
+ self.evaluation_engine = evaluation_engine
186
+ self.llm_client = llm_client
187
+
188
+ async def generate_and_judge(
189
+ self,
190
+ prompt: str,
191
+ context: dict[str, Any] | None = None,
192
+ ) -> dict[str, Any]:
193
+ """Generate code with one agent and judge with another.
194
+
195
+ Args:
196
+ prompt: The prompt for code generation
197
+ context: Additional context
198
+
199
+ Returns:
200
+ Dictionary with generated code, judgment, and evaluation results
201
+ """
202
+ # Step 1: Generate code with generate agent
203
+ generate_conn = await self.registry.connect(self.generate_agent)
204
+ generate_session = self.registry.get_session_id(self.generate_agent)
205
+
206
+ if not generate_session:
207
+ raise RuntimeError(f"No session for generate agent {self.generate_agent}")
208
+
209
+ generate_response = await generate_conn.prompt(
210
+ session_id=generate_session,
211
+ prompt=[text_block(prompt)],
212
+ )
213
+
214
+ # Extract generated code (simplified - would need proper extraction)
215
+ generated_code = self._extract_code_from_response(generate_response)
216
+
217
+ # Step 2: Judge code with judge agent
218
+ judge_prompt = f"""Please review and critique the following code:
219
+
220
+ ```python
221
+ {generated_code}
222
+ ```
223
+
224
+ Provide a detailed critique focusing on:
225
+ - Code quality and best practices
226
+ - Potential bugs or issues
227
+ - Security concerns
228
+ - Performance considerations
229
+ - Suggestions for improvement
230
+ """
231
+
232
+ judge_conn = await self.registry.connect(self.judge_agent)
233
+ judge_session = self.registry.get_session_id(self.judge_agent)
234
+
235
+ if not judge_session:
236
+ raise RuntimeError(f"No session for judge agent {self.judge_agent}")
237
+
238
+ judge_response = await judge_conn.prompt(
239
+ session_id=judge_session,
240
+ prompt=[text_block(judge_prompt)],
241
+ )
242
+
243
+ # Extract judgment
244
+ judgment = self._extract_text_from_response(judge_response)
245
+
246
+ # Step 3: Evaluate both with CodeOptiX
247
+ evaluation_results = None
248
+ if self.evaluation_engine:
249
+ from codeoptix.adapters.base import AgentOutput
250
+
251
+ AgentOutput(
252
+ code=generated_code,
253
+ tests="",
254
+ messages=[],
255
+ metadata={"source": "multi_agent_judge", "judgment": judgment},
256
+ )
257
+
258
+ # Evaluate behaviors
259
+ evaluation_results = await self.evaluation_engine.evaluate_behaviors(
260
+ behavior_names=["insecure-code", "vacuous-tests", "plan-drift"],
261
+ context={"code": generated_code, "judgment": judgment},
262
+ )
263
+
264
+ return {
265
+ "generated_code": generated_code,
266
+ "judgment": judgment,
267
+ "evaluation_results": evaluation_results,
268
+ "generate_agent": self.generate_agent,
269
+ "judge_agent": self.judge_agent,
270
+ }
271
+
272
+ def _extract_code_from_response(self, response: Any) -> str:
273
+ """Extract code from agent response.
274
+
275
+ Args:
276
+ response: ACP prompt response
277
+
278
+ Returns:
279
+ Extracted code as string
280
+ """
281
+ if not response:
282
+ return ""
283
+
284
+ # Extract from response messages
285
+ code_blocks = []
286
+
287
+ # Check if response has messages
288
+ if hasattr(response, "messages"):
289
+ for message in response.messages:
290
+ if hasattr(message, "content"):
291
+ content = message.content
292
+ if isinstance(content, str):
293
+ code_blocks.extend(extract_code_from_text(content))
294
+ elif hasattr(content, "text"):
295
+ code_blocks.extend(extract_code_from_text(getattr(content, "text", "")))
296
+
297
+ # Check if response has updates
298
+ if hasattr(response, "updates"):
299
+ for update in response.updates:
300
+ code_blocks.extend(extract_code_from_message(update))
301
+
302
+ # Combine all code blocks
303
+ if code_blocks:
304
+ # Prefer code blocks over inline code
305
+ block_codes = [cb["content"] for cb in code_blocks if cb.get("type") == "block"]
306
+ if block_codes:
307
+ return "\n\n".join(block_codes)
308
+ # Fallback to inline code
309
+ inline_codes = [cb["content"] for cb in code_blocks if cb.get("type") == "inline"]
310
+ if inline_codes:
311
+ return "\n".join(inline_codes)
312
+
313
+ return ""
314
+
315
+ def _extract_text_from_response(self, response: Any) -> str:
316
+ """Extract text from agent response.
317
+
318
+ Args:
319
+ response: ACP prompt response
320
+
321
+ Returns:
322
+ Extracted text as string
323
+ """
324
+ if not response:
325
+ return ""
326
+
327
+ text_parts = []
328
+
329
+ # Check if response has messages
330
+ if hasattr(response, "messages"):
331
+ for message in response.messages:
332
+ if hasattr(message, "content"):
333
+ content = message.content
334
+ if isinstance(content, str):
335
+ text_parts.append(content)
336
+ elif hasattr(content, "text"):
337
+ text_parts.append(getattr(content, "text", ""))
338
+
339
+ # Check if response has updates
340
+ if hasattr(response, "updates"):
341
+ for update in response.updates:
342
+ if hasattr(update, "content"):
343
+ content = update.content
344
+ if isinstance(content, str):
345
+ text_parts.append(content)
346
+ elif hasattr(content, "text"):
347
+ text_parts.append(getattr(content, "text", ""))
348
+
349
+ return "\n".join(text_parts)
@@ -0,0 +1,294 @@
1
+ """ACP Agent Registry - Manage and connect to multiple ACP-compatible agents."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from acp import PROTOCOL_VERSION, Client, connect_to_agent
9
+ from acp.core import ClientSideConnection
10
+ from acp.schema import (
11
+ ClientCapabilities,
12
+ CreateTerminalResponse,
13
+ Implementation,
14
+ ReadTextFileResponse,
15
+ RequestPermissionResponse,
16
+ WriteTextFileResponse,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class ACPAgentConfig:
24
+ """Configuration for an ACP agent."""
25
+
26
+ name: str
27
+ command: list[str]
28
+ cwd: str | None = None
29
+ env: dict[str, str] = field(default_factory=dict)
30
+ description: str = ""
31
+ capabilities: list[str] = field(default_factory=list)
32
+
33
+
34
+ class ACPAgentRegistry:
35
+ """Registry for managing ACP-compatible agents."""
36
+
37
+ def __init__(self):
38
+ """Initialize the agent registry."""
39
+ self._agents: dict[str, ACPAgentConfig] = {}
40
+ self._connections: dict[str, ClientSideConnection] = {}
41
+ self._sessions: dict[str, str] = {} # agent_name -> session_id
42
+
43
+ def register(
44
+ self,
45
+ name: str,
46
+ command: list[str],
47
+ cwd: str | None = None,
48
+ env: dict[str, str] | None = None,
49
+ description: str = "",
50
+ capabilities: list[str] | None = None,
51
+ ) -> None:
52
+ """Register an ACP agent.
53
+
54
+ Args:
55
+ name: Unique name for the agent
56
+ command: Command to spawn the agent (e.g., ["python", "agent.py"])
57
+ cwd: Working directory for the agent
58
+ env: Environment variables
59
+ description: Description of the agent
60
+ capabilities: List of agent capabilities
61
+ """
62
+ config = ACPAgentConfig(
63
+ name=name,
64
+ command=command,
65
+ cwd=cwd,
66
+ env=env or {},
67
+ description=description,
68
+ capabilities=capabilities or [],
69
+ )
70
+ self._agents[name] = config
71
+ logger.info(f"Registered ACP agent: {name}")
72
+
73
+ def unregister(self, name: str) -> None:
74
+ """Unregister an ACP agent.
75
+
76
+ Args:
77
+ name: Name of the agent to unregister
78
+ """
79
+ if name in self._agents:
80
+ # Close connection if open
81
+ if name in self._connections:
82
+ self._connections.pop(name)
83
+ if name in self._sessions:
84
+ self._sessions.pop(name)
85
+ del self._agents[name]
86
+ logger.info(f"Unregistered ACP agent: {name}")
87
+
88
+ def list_agents(self) -> list[str]:
89
+ """List all registered agent names."""
90
+ return list(self._agents.keys())
91
+
92
+ def get_agent(self, name: str) -> ACPAgentConfig | None:
93
+ """Get agent configuration.
94
+
95
+ Args:
96
+ name: Agent name
97
+
98
+ Returns:
99
+ Agent configuration or None if not found
100
+ """
101
+ return self._agents.get(name)
102
+
103
+ async def connect(self, name: str) -> ClientSideConnection:
104
+ """Connect to a registered ACP agent.
105
+
106
+ Args:
107
+ name: Agent name
108
+
109
+ Returns:
110
+ ClientSideConnection to the agent
111
+
112
+ Raises:
113
+ ValueError: If agent not found
114
+ RuntimeError: If connection fails
115
+ """
116
+ if name not in self._agents:
117
+ raise ValueError(f"Agent '{name}' not found in registry")
118
+
119
+ # Return existing connection if available
120
+ if name in self._connections:
121
+ return self._connections[name]
122
+
123
+ config = self._agents[name]
124
+
125
+ # Spawn agent process
126
+ process = await asyncio.create_subprocess_exec(
127
+ *config.command,
128
+ stdin=asyncio.subprocess.PIPE,
129
+ stdout=asyncio.subprocess.PIPE,
130
+ cwd=config.cwd,
131
+ env={**config.env, **dict(asyncio.get_event_loop().get_environ())},
132
+ )
133
+
134
+ if process.stdin is None or process.stdout is None:
135
+ raise RuntimeError("Agent process does not expose stdio pipes")
136
+
137
+ # Create client implementation
138
+ client_impl = _RegistryClientImpl()
139
+ connection = connect_to_agent(client_impl, process.stdin, process.stdout)
140
+
141
+ # Initialize connection
142
+ await connection.initialize(
143
+ protocol_version=PROTOCOL_VERSION,
144
+ client_capabilities=ClientCapabilities(),
145
+ client_info=Implementation(
146
+ name="codeoptix-registry",
147
+ title="CodeOptiX Agent Registry",
148
+ version="0.1.0",
149
+ ),
150
+ )
151
+
152
+ # Create new session
153
+ session = await connection.new_session(mcp_servers=[], cwd=config.cwd or ".")
154
+ self._sessions[name] = session.session_id
155
+
156
+ # Store connection
157
+ self._connections[name] = connection
158
+
159
+ logger.info(f"Connected to ACP agent: {name}")
160
+ return connection
161
+
162
+ async def disconnect(self, name: str) -> None:
163
+ """Disconnect from an agent.
164
+
165
+ Args:
166
+ name: Agent name
167
+ """
168
+ if name in self._connections:
169
+ connection = self._connections[name]
170
+ try:
171
+ # Try to gracefully close the connection
172
+ if hasattr(connection, "close"):
173
+ await connection.close()
174
+ except Exception as e:
175
+ logger.warning(f"Error closing connection to agent {name}: {e}")
176
+ finally:
177
+ self._connections.pop(name)
178
+ if name in self._sessions:
179
+ self._sessions.pop(name)
180
+ logger.info(f"Disconnected from ACP agent: {name}")
181
+
182
+ def get_session_id(self, name: str) -> str | None:
183
+ """Get session ID for an agent.
184
+
185
+ Args:
186
+ name: Agent name
187
+
188
+ Returns:
189
+ Session ID or None if not connected
190
+ """
191
+ return self._sessions.get(name)
192
+
193
+
194
+ class _RegistryClientImpl(Client):
195
+ """Internal client implementation for registry connections."""
196
+
197
+ async def request_permission(
198
+ self,
199
+ options: list,
200
+ session_id: str,
201
+ tool_call: Any,
202
+ **kwargs: Any,
203
+ ) -> RequestPermissionResponse:
204
+ """Handle permission requests."""
205
+ return RequestPermissionResponse(granted=True)
206
+
207
+ async def write_text_file(
208
+ self,
209
+ content: str,
210
+ path: str,
211
+ session_id: str,
212
+ **kwargs: Any,
213
+ ) -> WriteTextFileResponse | None:
214
+ """Handle file write requests."""
215
+ return WriteTextFileResponse()
216
+
217
+ async def read_text_file(
218
+ self,
219
+ path: str,
220
+ session_id: str,
221
+ limit: int | None = None,
222
+ line: int | None = None,
223
+ **kwargs: Any,
224
+ ) -> ReadTextFileResponse:
225
+ """Handle file read requests."""
226
+ try:
227
+ with open(path, encoding="utf-8") as f:
228
+ if line is not None:
229
+ lines = f.readlines()
230
+ if 0 <= line < len(lines):
231
+ content = lines[line]
232
+ else:
233
+ content = ""
234
+ elif limit is not None:
235
+ content = f.read(limit)
236
+ else:
237
+ content = f.read()
238
+ return ReadTextFileResponse(content=content)
239
+ except Exception as e:
240
+ logger.error(f"Error reading file {path}: {e}")
241
+ raise
242
+
243
+ async def create_terminal(self, *args: Any, **kwargs: Any) -> CreateTerminalResponse:
244
+ """Handle terminal creation."""
245
+ from acp.exceptions import RequestError
246
+
247
+ raise RequestError.method_not_found("terminal/create")
248
+
249
+ async def terminal_output(self, *args: Any, **kwargs: Any) -> Any:
250
+ """Handle terminal output."""
251
+ from acp.exceptions import RequestError
252
+
253
+ raise RequestError.method_not_found("terminal/output")
254
+
255
+ async def release_terminal(self, *args: Any, **kwargs: Any) -> Any:
256
+ """Handle terminal release."""
257
+ from acp.exceptions import RequestError
258
+
259
+ raise RequestError.method_not_found("terminal/release")
260
+
261
+ async def wait_for_terminal_exit(self, *args: Any, **kwargs: Any) -> Any:
262
+ """Handle terminal exit wait."""
263
+ from acp.exceptions import RequestError
264
+
265
+ raise RequestError.method_not_found("terminal/wait_for_exit")
266
+
267
+ async def kill_terminal(self, *args: Any, **kwargs: Any) -> Any:
268
+ """Handle terminal kill."""
269
+ from acp.exceptions import RequestError
270
+
271
+ raise RequestError.method_not_found("terminal/kill")
272
+
273
+ async def session_update(
274
+ self,
275
+ session_id: str,
276
+ update: Any,
277
+ **kwargs: Any,
278
+ ) -> None:
279
+ """Handle session updates from agent."""
280
+ # Registry doesn't need to handle updates
281
+
282
+ async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
283
+ """Handle extension methods."""
284
+ from acp.exceptions import RequestError
285
+
286
+ raise RequestError.method_not_found(method)
287
+
288
+ async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
289
+ """Handle extension notifications."""
290
+ logger.debug(f"Extension notification: {method}")
291
+
292
+ def on_connect(self, conn: Any) -> None:
293
+ """Called when client connects to agent."""
294
+ logger.debug("Registry connected to ACP agent")
@@ -0,0 +1,18 @@
1
+ """Agent adapters for CodeOptix."""
2
+
3
+ from codeoptix.adapters.base import AgentAdapter, AgentOutput
4
+ from codeoptix.adapters.basic import BasicAdapter
5
+ from codeoptix.adapters.claude_code import ClaudeCodeAdapter
6
+ from codeoptix.adapters.codex import CodexAdapter
7
+ from codeoptix.adapters.factory import create_adapter
8
+ from codeoptix.adapters.gemini_cli import GeminiCLIAdapter
9
+
10
+ __all__ = [
11
+ "AgentAdapter",
12
+ "AgentOutput",
13
+ "BasicAdapter",
14
+ "ClaudeCodeAdapter",
15
+ "CodexAdapter",
16
+ "GeminiCLIAdapter",
17
+ "create_adapter",
18
+ ]