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
codeoptix/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""CodeOptiX: Agentic Code Optimization & Deep Evaluation for Superior Coding Agent Experience.
|
|
2
|
+
|
|
3
|
+
The universal code optimization engine that improves coding agent experience with deep evaluations and optimization. When AI coding agents dazzle with impressive code but leave you wondering about quality, maintainability, security, and reliability, CodeOptiX ensures proper behavior through evaluations, reflection, and self-improvement.
|
|
4
|
+
|
|
5
|
+
Built by Superagentic AI - Advancing AI agent optimization and autonomous systems.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.3"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""ACP (Agent Client Protocol) integration for CodeOptiX.
|
|
2
|
+
|
|
3
|
+
This module provides ACP integration:
|
|
4
|
+
- CodeOptiX as an ACP agent (can be used by editors)
|
|
5
|
+
- ACP client adapter (connect to other agents via ACP)
|
|
6
|
+
- Quality bridge functionality
|
|
7
|
+
- Agent registry and orchestration
|
|
8
|
+
- Multi-agent judge support
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from codeoptix.acp.agent import CodeOptiXAgent
|
|
12
|
+
from codeoptix.acp.bridge import ACPQualityBridge
|
|
13
|
+
from codeoptix.acp.client_adapter import ACPClientAdapter
|
|
14
|
+
from codeoptix.acp.code_extractor import (
|
|
15
|
+
extract_all_code,
|
|
16
|
+
extract_code_from_message,
|
|
17
|
+
extract_code_from_text,
|
|
18
|
+
)
|
|
19
|
+
from codeoptix.acp.orchestrator import AgentOrchestrator, MultiAgentJudge
|
|
20
|
+
from codeoptix.acp.registry import ACPAgentConfig, ACPAgentRegistry
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ACPAgentConfig",
|
|
24
|
+
"ACPAgentRegistry",
|
|
25
|
+
"ACPClientAdapter",
|
|
26
|
+
"ACPQualityBridge",
|
|
27
|
+
"AgentOrchestrator",
|
|
28
|
+
"CodeOptiXAgent",
|
|
29
|
+
"MultiAgentJudge",
|
|
30
|
+
"extract_all_code",
|
|
31
|
+
"extract_code_from_message",
|
|
32
|
+
"extract_code_from_text",
|
|
33
|
+
]
|
codeoptix/acp/agent.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""CodeOptiX as an ACP Agent.
|
|
2
|
+
|
|
3
|
+
This allows CodeOptiX to be used by ACP-compatible editors (Zed, JetBrains, Neovim, etc.)
|
|
4
|
+
as a quality engineering agent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
from acp import (
|
|
11
|
+
Agent,
|
|
12
|
+
InitializeResponse,
|
|
13
|
+
NewSessionResponse,
|
|
14
|
+
PromptResponse,
|
|
15
|
+
text_block,
|
|
16
|
+
update_agent_message,
|
|
17
|
+
)
|
|
18
|
+
from acp.interfaces import Client
|
|
19
|
+
from acp.schema import (
|
|
20
|
+
AudioContentBlock,
|
|
21
|
+
ClientCapabilities,
|
|
22
|
+
EmbeddedResourceContentBlock,
|
|
23
|
+
HttpMcpServer,
|
|
24
|
+
ImageContentBlock,
|
|
25
|
+
Implementation,
|
|
26
|
+
McpServerStdio,
|
|
27
|
+
ResourceContentBlock,
|
|
28
|
+
SseMcpServer,
|
|
29
|
+
TextContentBlock,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
from codeoptix.acp.code_extractor import extract_code_from_text
|
|
33
|
+
from codeoptix.adapters.base import AgentOutput
|
|
34
|
+
from codeoptix.evaluation import EvaluationEngine
|
|
35
|
+
from codeoptix.utils.llm import LLMClient
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CodeOptiXAgent(Agent):
|
|
39
|
+
"""CodeOptiX as an ACP agent for quality engineering."""
|
|
40
|
+
|
|
41
|
+
_conn: Client | None = None
|
|
42
|
+
_evaluation_engine: EvaluationEngine | None = None
|
|
43
|
+
_llm_client: LLMClient | None = None
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
evaluation_engine: EvaluationEngine | None = None,
|
|
48
|
+
llm_client: LLMClient | None = None,
|
|
49
|
+
behaviors: list[str] | None = None,
|
|
50
|
+
):
|
|
51
|
+
"""Initialize CodeOptiX ACP agent.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
evaluation_engine: Optional evaluation engine (will create default if not provided)
|
|
55
|
+
llm_client: Optional LLM client (will create default if not provided)
|
|
56
|
+
behaviors: List of behavior names to evaluate (default: all)
|
|
57
|
+
"""
|
|
58
|
+
self._evaluation_engine = evaluation_engine
|
|
59
|
+
self._llm_client = llm_client
|
|
60
|
+
self._behaviors = behaviors or ["insecure-code", "vacuous-tests", "plan-drift"]
|
|
61
|
+
|
|
62
|
+
# Initialize default evaluation engine if not provided
|
|
63
|
+
if not self._evaluation_engine and self._llm_client:
|
|
64
|
+
# Create a minimal adapter for evaluation (dummy adapter)
|
|
65
|
+
from codeoptix.adapters.base import AgentAdapter
|
|
66
|
+
from codeoptix.adapters.factory import create_adapter
|
|
67
|
+
|
|
68
|
+
# Try to create a default adapter (will use dummy if none available)
|
|
69
|
+
try:
|
|
70
|
+
adapter = create_adapter("claude-code", {"llm_config": {"provider": "anthropic"}})
|
|
71
|
+
except Exception:
|
|
72
|
+
# Create a minimal dummy adapter
|
|
73
|
+
class DummyAdapter(AgentAdapter):
|
|
74
|
+
def get_adapter_type(self) -> str:
|
|
75
|
+
return "dummy"
|
|
76
|
+
|
|
77
|
+
def generate_code(self, prompt: str, **kwargs) -> AgentOutput:
|
|
78
|
+
return AgentOutput(code="", tests="", messages=[], metadata={})
|
|
79
|
+
|
|
80
|
+
adapter = DummyAdapter()
|
|
81
|
+
|
|
82
|
+
self._evaluation_engine = EvaluationEngine(adapter, self._llm_client)
|
|
83
|
+
|
|
84
|
+
def on_connect(self, conn: Client) -> None:
|
|
85
|
+
"""Called when agent connects to client."""
|
|
86
|
+
self._conn = conn
|
|
87
|
+
|
|
88
|
+
async def initialize(
|
|
89
|
+
self,
|
|
90
|
+
protocol_version: int,
|
|
91
|
+
client_capabilities: ClientCapabilities | None = None,
|
|
92
|
+
client_info: Implementation | None = None,
|
|
93
|
+
**kwargs: Any,
|
|
94
|
+
) -> InitializeResponse:
|
|
95
|
+
"""Initialize the agent with client capabilities."""
|
|
96
|
+
return InitializeResponse(protocol_version=protocol_version)
|
|
97
|
+
|
|
98
|
+
async def new_session(
|
|
99
|
+
self,
|
|
100
|
+
cwd: str,
|
|
101
|
+
mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio],
|
|
102
|
+
**kwargs: Any,
|
|
103
|
+
) -> NewSessionResponse:
|
|
104
|
+
"""Create a new session."""
|
|
105
|
+
session_id = uuid4().hex
|
|
106
|
+
return NewSessionResponse(session_id=session_id)
|
|
107
|
+
|
|
108
|
+
async def prompt(
|
|
109
|
+
self,
|
|
110
|
+
prompt: list[
|
|
111
|
+
TextContentBlock
|
|
112
|
+
| ImageContentBlock
|
|
113
|
+
| AudioContentBlock
|
|
114
|
+
| ResourceContentBlock
|
|
115
|
+
| EmbeddedResourceContentBlock
|
|
116
|
+
],
|
|
117
|
+
session_id: str,
|
|
118
|
+
**kwargs: Any,
|
|
119
|
+
) -> PromptResponse:
|
|
120
|
+
"""Handle a prompt from the client.
|
|
121
|
+
|
|
122
|
+
This is where CodeOptiX performs quality engineering on the request.
|
|
123
|
+
"""
|
|
124
|
+
if not self._conn:
|
|
125
|
+
return PromptResponse(stop_reason="error", error="Not connected to client")
|
|
126
|
+
|
|
127
|
+
# Extract text from prompt blocks
|
|
128
|
+
prompt_text = ""
|
|
129
|
+
for block in prompt:
|
|
130
|
+
if isinstance(block, dict):
|
|
131
|
+
text = block.get("text", "")
|
|
132
|
+
elif isinstance(block, TextContentBlock):
|
|
133
|
+
text = block.text
|
|
134
|
+
else:
|
|
135
|
+
text = getattr(block, "text", "")
|
|
136
|
+
if text:
|
|
137
|
+
prompt_text += text + "\n"
|
|
138
|
+
|
|
139
|
+
# Send initial response
|
|
140
|
+
await self._conn.session_update(
|
|
141
|
+
session_id=session_id,
|
|
142
|
+
update=update_agent_message(text_block("🔍 CodeOptiX: Analyzing code quality...")),
|
|
143
|
+
source="codeoptix",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Extract code from prompt
|
|
147
|
+
code_blocks = extract_code_from_text(prompt_text)
|
|
148
|
+
|
|
149
|
+
# Perform quality evaluation
|
|
150
|
+
evaluation_results = None
|
|
151
|
+
if self._evaluation_engine and code_blocks:
|
|
152
|
+
try:
|
|
153
|
+
# Extract code content
|
|
154
|
+
code_content = "\n\n".join([cb["content"] for cb in code_blocks])
|
|
155
|
+
|
|
156
|
+
if code_content:
|
|
157
|
+
# Create agent output for evaluation
|
|
158
|
+
AgentOutput(
|
|
159
|
+
code=code_content,
|
|
160
|
+
tests="",
|
|
161
|
+
messages=[],
|
|
162
|
+
metadata={"source": "acp_agent", "prompt": prompt_text[:200]},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Evaluate behaviors
|
|
166
|
+
evaluation_results = self._evaluation_engine.evaluate_behaviors(
|
|
167
|
+
behavior_names=self._behaviors,
|
|
168
|
+
context={"code": code_content, "prompt": prompt_text},
|
|
169
|
+
)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
import logging
|
|
172
|
+
|
|
173
|
+
logger = logging.getLogger(__name__)
|
|
174
|
+
logger.error(f"Error during quality evaluation: {e}")
|
|
175
|
+
evaluation_results = {"error": str(e)}
|
|
176
|
+
|
|
177
|
+
# Format response
|
|
178
|
+
if evaluation_results and "behaviors" in evaluation_results:
|
|
179
|
+
response_lines = ["## 🔍 CodeOptiX Quality Report\n"]
|
|
180
|
+
|
|
181
|
+
overall_score = evaluation_results.get("overall_score", 0.0)
|
|
182
|
+
response_lines.append(f"**Overall Score:** {overall_score:.2%}\n")
|
|
183
|
+
|
|
184
|
+
behaviors = evaluation_results.get("behaviors", {})
|
|
185
|
+
for behavior_name, behavior_data in behaviors.items():
|
|
186
|
+
passed = behavior_data.get("passed", True)
|
|
187
|
+
score = behavior_data.get("score", 0.0)
|
|
188
|
+
emoji = "✅" if passed else "❌"
|
|
189
|
+
|
|
190
|
+
response_lines.append(f"{emoji} **{behavior_name}**: {score:.2%}")
|
|
191
|
+
|
|
192
|
+
if not passed and behavior_data.get("evidence"):
|
|
193
|
+
evidence = behavior_data["evidence"][:3] # Limit to 3 items
|
|
194
|
+
for ev in evidence:
|
|
195
|
+
response_lines.append(f" - {ev}")
|
|
196
|
+
|
|
197
|
+
response_text = "\n".join(response_lines)
|
|
198
|
+
elif code_blocks:
|
|
199
|
+
response_text = f"CodeOptiX analyzed {len(code_blocks)} code block(s).\n\n✅ Quality check complete."
|
|
200
|
+
else:
|
|
201
|
+
response_text = f"CodeOptiX received your request:\n\n{prompt_text[:500]}\n\n⚠️ No code blocks detected for quality evaluation."
|
|
202
|
+
|
|
203
|
+
await self._conn.session_update(
|
|
204
|
+
session_id=session_id,
|
|
205
|
+
update=update_agent_message(text_block(response_text)),
|
|
206
|
+
source="codeoptix",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return PromptResponse(stop_reason="end_turn")
|
codeoptix/acp/bridge.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""ACP Quality Bridge - CodeOptiX as quality middleware between editor and agents.
|
|
2
|
+
|
|
3
|
+
This implements the "Quality Bridge" pattern where CodeOptiX sits between
|
|
4
|
+
the editor and coding agents, automatically performing quality checks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from acp import PROTOCOL_VERSION, Client, connect_to_agent, text_block, update_agent_message
|
|
12
|
+
from acp.core import ClientSideConnection
|
|
13
|
+
from acp.schema import (
|
|
14
|
+
AgentMessageChunk,
|
|
15
|
+
ClientCapabilities,
|
|
16
|
+
Implementation,
|
|
17
|
+
TextContentBlock,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from codeoptix.acp.code_extractor import extract_all_code, extract_code_from_message
|
|
21
|
+
from codeoptix.acp.registry import ACPAgentRegistry
|
|
22
|
+
from codeoptix.evaluation import EvaluationEngine
|
|
23
|
+
from codeoptix.utils.llm import LLMClient
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ACPQualityBridge:
|
|
29
|
+
"""CodeOptiX as a quality bridge between editor and agents via ACP."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
agent_command: list[str] | None = None,
|
|
34
|
+
agent_name: str | None = None,
|
|
35
|
+
evaluation_engine: EvaluationEngine | None = None,
|
|
36
|
+
llm_client: LLMClient | None = None,
|
|
37
|
+
auto_eval: bool = True,
|
|
38
|
+
registry: ACPAgentRegistry | None = None,
|
|
39
|
+
behaviors: list[str] | None = None,
|
|
40
|
+
):
|
|
41
|
+
"""Initialize ACP quality bridge.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
agent_command: Command to spawn the ACP agent (e.g., ["python", "agent.py"])
|
|
45
|
+
agent_name: Name of agent in registry (alternative to agent_command)
|
|
46
|
+
evaluation_engine: Optional evaluation engine
|
|
47
|
+
llm_client: Optional LLM client
|
|
48
|
+
auto_eval: Whether to automatically evaluate code quality
|
|
49
|
+
registry: Optional agent registry (for multi-agent support)
|
|
50
|
+
behaviors: List of behavior names to evaluate (default: all)
|
|
51
|
+
"""
|
|
52
|
+
if not agent_command and not agent_name:
|
|
53
|
+
raise ValueError("Either agent_command or agent_name must be provided")
|
|
54
|
+
|
|
55
|
+
self.agent_command = agent_command
|
|
56
|
+
self.agent_name = agent_name
|
|
57
|
+
self.evaluation_engine = evaluation_engine
|
|
58
|
+
self.llm_client = llm_client
|
|
59
|
+
self.auto_eval = auto_eval
|
|
60
|
+
self.registry = registry
|
|
61
|
+
self.behaviors = behaviors or ["insecure-code", "vacuous-tests", "plan-drift"]
|
|
62
|
+
self._connection: ClientSideConnection | None = None
|
|
63
|
+
self._session_id: str | None = None
|
|
64
|
+
self._collected_updates: list[Any] = [] # Collect updates for code extraction
|
|
65
|
+
|
|
66
|
+
async def connect(self, cwd: str | None = None) -> None:
|
|
67
|
+
"""Connect to the ACP agent."""
|
|
68
|
+
if self.agent_name and self.registry:
|
|
69
|
+
# Use registry to connect
|
|
70
|
+
self._connection = await self.registry.connect(self.agent_name)
|
|
71
|
+
self._session_id = self.registry.get_session_id(self.agent_name)
|
|
72
|
+
elif self.agent_command:
|
|
73
|
+
# Spawn agent process directly
|
|
74
|
+
process = await asyncio.create_subprocess_exec(
|
|
75
|
+
*self.agent_command,
|
|
76
|
+
stdin=asyncio.subprocess.PIPE,
|
|
77
|
+
stdout=asyncio.subprocess.PIPE,
|
|
78
|
+
cwd=cwd,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if process.stdin is None or process.stdout is None:
|
|
82
|
+
raise RuntimeError("Agent process does not expose stdio pipes")
|
|
83
|
+
|
|
84
|
+
# Create bridge client implementation
|
|
85
|
+
client_impl = _BridgeClientImpl(self)
|
|
86
|
+
self._connection = connect_to_agent(client_impl, process.stdin, process.stdout)
|
|
87
|
+
|
|
88
|
+
# Initialize connection
|
|
89
|
+
await self._connection.initialize(
|
|
90
|
+
protocol_version=PROTOCOL_VERSION,
|
|
91
|
+
client_capabilities=ClientCapabilities(),
|
|
92
|
+
client_info=Implementation(
|
|
93
|
+
name="codeoptix-bridge",
|
|
94
|
+
title="CodeOptiX Quality Bridge",
|
|
95
|
+
version="0.1.0",
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Create new session
|
|
100
|
+
session = await self._connection.new_session(mcp_servers=[], cwd=cwd or ".")
|
|
101
|
+
self._session_id = session.session_id
|
|
102
|
+
else:
|
|
103
|
+
raise RuntimeError("No agent command or registry agent name provided")
|
|
104
|
+
|
|
105
|
+
async def prompt(
|
|
106
|
+
self,
|
|
107
|
+
prompt: str,
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> str:
|
|
110
|
+
"""Send a prompt through the quality bridge."""
|
|
111
|
+
if not self._connection or not self._session_id:
|
|
112
|
+
raise RuntimeError("Not connected to ACP agent")
|
|
113
|
+
|
|
114
|
+
# Clear collected updates
|
|
115
|
+
self._collected_updates = []
|
|
116
|
+
|
|
117
|
+
# Send prompt to agent
|
|
118
|
+
response = await self._connection.prompt(
|
|
119
|
+
session_id=self._session_id,
|
|
120
|
+
prompt=[text_block(prompt)],
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Perform quality evaluation on response
|
|
124
|
+
if self.auto_eval and self.evaluation_engine and self._collected_updates:
|
|
125
|
+
await self._evaluate_and_feedback()
|
|
126
|
+
|
|
127
|
+
return response.stop_reason or "end_turn"
|
|
128
|
+
|
|
129
|
+
async def _evaluate_and_feedback(self) -> None:
|
|
130
|
+
"""Evaluate collected code and send feedback to editor."""
|
|
131
|
+
if not self._connection or not self._session_id:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# Extract code from collected updates
|
|
135
|
+
code_blocks = extract_all_code(self._collected_updates)
|
|
136
|
+
|
|
137
|
+
if not code_blocks:
|
|
138
|
+
logger.debug("No code blocks found in agent response")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# Send evaluation status
|
|
142
|
+
await self._connection.session_update(
|
|
143
|
+
session_id=self._session_id,
|
|
144
|
+
update=update_agent_message(text_block("🔍 CodeOptiX: Evaluating code quality...")),
|
|
145
|
+
source="codeoptix",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# Evaluate each code block
|
|
150
|
+
all_results = []
|
|
151
|
+
for code_block in code_blocks:
|
|
152
|
+
code_content = code_block.get("content", "")
|
|
153
|
+
if not code_content:
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# Evaluate code block (synchronous)
|
|
157
|
+
results = self.bridge._evaluate_code_block(code_content, code_block)
|
|
158
|
+
|
|
159
|
+
all_results.append(
|
|
160
|
+
{
|
|
161
|
+
"code_block": code_block,
|
|
162
|
+
"results": results,
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Format and send feedback
|
|
167
|
+
feedback = self._format_quality_feedback(all_results)
|
|
168
|
+
await self._connection.session_update(
|
|
169
|
+
session_id=self._session_id,
|
|
170
|
+
update=update_agent_message(text_block(feedback)),
|
|
171
|
+
source="codeoptix",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Error during quality evaluation: {e}")
|
|
176
|
+
await self._connection.session_update(
|
|
177
|
+
session_id=self._session_id,
|
|
178
|
+
update=update_agent_message(text_block(f"⚠️ CodeOptiX: Evaluation error: {e!s}")),
|
|
179
|
+
source="codeoptix",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _format_quality_feedback(self, results: list[dict[str, Any]]) -> str:
|
|
183
|
+
"""Format quality evaluation results for display.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
results: List of evaluation results
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Formatted feedback string
|
|
190
|
+
"""
|
|
191
|
+
lines = ["## 🔍 CodeOptiX Quality Report\n"]
|
|
192
|
+
|
|
193
|
+
for i, result_data in enumerate(results, 1):
|
|
194
|
+
code_block = result_data["code_block"]
|
|
195
|
+
results_dict = result_data["results"]
|
|
196
|
+
|
|
197
|
+
lines.append(f"### Code Block {i} ({code_block.get('language', 'text')})\n")
|
|
198
|
+
|
|
199
|
+
if "behaviors" in results_dict:
|
|
200
|
+
for behavior_name, behavior_data in results_dict["behaviors"].items():
|
|
201
|
+
passed = behavior_data.get("passed", True)
|
|
202
|
+
score = behavior_data.get("score", 0.0)
|
|
203
|
+
emoji = "✅" if passed else "❌"
|
|
204
|
+
|
|
205
|
+
lines.append(f"{emoji} **{behavior_name}**: {score:.2%}")
|
|
206
|
+
|
|
207
|
+
if not passed and behavior_data.get("evidence"):
|
|
208
|
+
evidence = behavior_data["evidence"][:2] # Limit to 2 items
|
|
209
|
+
for ev in evidence:
|
|
210
|
+
lines.append(f" - {ev}")
|
|
211
|
+
|
|
212
|
+
lines.append("")
|
|
213
|
+
|
|
214
|
+
return "\n".join(lines)
|
|
215
|
+
|
|
216
|
+
async def close(self) -> None:
|
|
217
|
+
"""Close the bridge connection."""
|
|
218
|
+
if self._connection:
|
|
219
|
+
self._connection = None
|
|
220
|
+
self._session_id = None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class _BridgeClientImpl(Client):
|
|
224
|
+
"""Internal client implementation for quality bridge."""
|
|
225
|
+
|
|
226
|
+
def __init__(self, bridge: ACPQualityBridge):
|
|
227
|
+
"""Initialize bridge client."""
|
|
228
|
+
self.bridge = bridge
|
|
229
|
+
|
|
230
|
+
async def request_permission(
|
|
231
|
+
self,
|
|
232
|
+
options: list,
|
|
233
|
+
session_id: str,
|
|
234
|
+
tool_call: Any,
|
|
235
|
+
**kwargs: Any,
|
|
236
|
+
) -> Any:
|
|
237
|
+
"""Handle permission requests."""
|
|
238
|
+
# Auto-approve for bridge
|
|
239
|
+
from acp.schema import RequestPermissionResponse
|
|
240
|
+
|
|
241
|
+
return RequestPermissionResponse(granted=True)
|
|
242
|
+
|
|
243
|
+
async def write_text_file(
|
|
244
|
+
self,
|
|
245
|
+
content: str,
|
|
246
|
+
path: str,
|
|
247
|
+
session_id: str,
|
|
248
|
+
**kwargs: Any,
|
|
249
|
+
) -> Any:
|
|
250
|
+
"""Handle file write requests."""
|
|
251
|
+
# Allow file writes, but could add quality checks here
|
|
252
|
+
from acp.schema import WriteTextFileResponse
|
|
253
|
+
|
|
254
|
+
return WriteTextFileResponse()
|
|
255
|
+
|
|
256
|
+
async def read_text_file(
|
|
257
|
+
self,
|
|
258
|
+
path: str,
|
|
259
|
+
session_id: str,
|
|
260
|
+
limit: int | None = None,
|
|
261
|
+
line: int | None = None,
|
|
262
|
+
**kwargs: Any,
|
|
263
|
+
) -> Any:
|
|
264
|
+
"""Handle file read requests."""
|
|
265
|
+
from acp.schema import ReadTextFileResponse
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
with open(path, encoding="utf-8") as f:
|
|
269
|
+
if line is not None:
|
|
270
|
+
lines = f.readlines()
|
|
271
|
+
if 0 <= line < len(lines):
|
|
272
|
+
content = lines[line]
|
|
273
|
+
else:
|
|
274
|
+
content = ""
|
|
275
|
+
elif limit is not None:
|
|
276
|
+
content = f.read(limit)
|
|
277
|
+
else:
|
|
278
|
+
content = f.read()
|
|
279
|
+
return ReadTextFileResponse(content=content)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Error reading file {path}: {e}")
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
async def create_terminal(self, *args: Any, **kwargs: Any) -> Any:
|
|
285
|
+
"""Handle terminal creation."""
|
|
286
|
+
from acp.exceptions import RequestError
|
|
287
|
+
|
|
288
|
+
raise RequestError.method_not_found("terminal/create")
|
|
289
|
+
|
|
290
|
+
async def terminal_output(self, *args: Any, **kwargs: Any) -> Any:
|
|
291
|
+
"""Handle terminal output."""
|
|
292
|
+
from acp.exceptions import RequestError
|
|
293
|
+
|
|
294
|
+
raise RequestError.method_not_found("terminal/output")
|
|
295
|
+
|
|
296
|
+
async def release_terminal(self, *args: Any, **kwargs: Any) -> Any:
|
|
297
|
+
"""Handle terminal release."""
|
|
298
|
+
from acp.exceptions import RequestError
|
|
299
|
+
|
|
300
|
+
raise RequestError.method_not_found("terminal/release")
|
|
301
|
+
|
|
302
|
+
async def wait_for_terminal_exit(self, *args: Any, **kwargs: Any) -> Any:
|
|
303
|
+
"""Handle terminal exit wait."""
|
|
304
|
+
from acp.exceptions import RequestError
|
|
305
|
+
|
|
306
|
+
raise RequestError.method_not_found("terminal/wait_for_exit")
|
|
307
|
+
|
|
308
|
+
async def kill_terminal(self, *args: Any, **kwargs: Any) -> Any:
|
|
309
|
+
"""Handle terminal kill."""
|
|
310
|
+
from acp.exceptions import RequestError
|
|
311
|
+
|
|
312
|
+
raise RequestError.method_not_found("terminal/kill")
|
|
313
|
+
|
|
314
|
+
async def session_update(
|
|
315
|
+
self,
|
|
316
|
+
session_id: str,
|
|
317
|
+
update: Any,
|
|
318
|
+
**kwargs: Any,
|
|
319
|
+
) -> None:
|
|
320
|
+
"""Handle session updates from agent."""
|
|
321
|
+
# Collect updates for code extraction and evaluation
|
|
322
|
+
self._collected_updates.append(update)
|
|
323
|
+
|
|
324
|
+
# Intercept agent messages for quality evaluation
|
|
325
|
+
if isinstance(update, AgentMessageChunk):
|
|
326
|
+
content = update.content
|
|
327
|
+
if isinstance(content, TextContentBlock):
|
|
328
|
+
logger.debug(f"Agent message: {content.text[:100]}...")
|
|
329
|
+
|
|
330
|
+
# Extract code immediately for real-time feedback
|
|
331
|
+
if self.bridge.auto_eval and self.bridge.evaluation_engine:
|
|
332
|
+
code_blocks = extract_code_from_message(update)
|
|
333
|
+
if code_blocks:
|
|
334
|
+
# Quick evaluation for real-time feedback
|
|
335
|
+
await self.bridge._quick_evaluate_code(code_blocks, session_id)
|
|
336
|
+
|
|
337
|
+
async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
338
|
+
"""Handle extension methods."""
|
|
339
|
+
from acp.exceptions import RequestError
|
|
340
|
+
|
|
341
|
+
raise RequestError.method_not_found(method)
|
|
342
|
+
|
|
343
|
+
async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
|
|
344
|
+
"""Handle extension notifications."""
|
|
345
|
+
logger.debug(f"Extension notification: {method}")
|
|
346
|
+
|
|
347
|
+
def _evaluate_code_block(self, code_content: str, code_block: dict[str, str]) -> dict[str, Any]:
|
|
348
|
+
"""Evaluate a single code block.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
code_content: Code content to evaluate
|
|
352
|
+
code_block: Code block metadata
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Evaluation results dictionary
|
|
356
|
+
"""
|
|
357
|
+
if not self.evaluation_engine:
|
|
358
|
+
return {}
|
|
359
|
+
|
|
360
|
+
# Use evaluation engine's evaluate_behaviors (synchronous)
|
|
361
|
+
try:
|
|
362
|
+
results = self.evaluation_engine.evaluate_behaviors(
|
|
363
|
+
behavior_names=self.behaviors,
|
|
364
|
+
context={"code": code_content, "source": "acp_bridge", "code_block": code_block},
|
|
365
|
+
)
|
|
366
|
+
return results
|
|
367
|
+
except Exception as e:
|
|
368
|
+
logger.error(f"Error in code block evaluation: {e}")
|
|
369
|
+
return {"error": str(e)}
|
|
370
|
+
|
|
371
|
+
async def _quick_evaluate_code(
|
|
372
|
+
self, code_blocks: list[dict[str, str]], session_id: str
|
|
373
|
+
) -> None:
|
|
374
|
+
"""Perform quick evaluation on code blocks for real-time feedback.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
code_blocks: List of extracted code blocks
|
|
378
|
+
session_id: ACP session ID
|
|
379
|
+
"""
|
|
380
|
+
if not self._connection:
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
# Quick check for obvious issues (can be expanded)
|
|
384
|
+
for code_block in code_blocks:
|
|
385
|
+
code = code_block.get("content", "")
|
|
386
|
+
if not code:
|
|
387
|
+
continue
|
|
388
|
+
|
|
389
|
+
# Quick security check
|
|
390
|
+
security_keywords = ["password", "secret", "api_key", "token", "credential"]
|
|
391
|
+
if any(keyword in code.lower() for keyword in security_keywords):
|
|
392
|
+
await self._connection.session_update(
|
|
393
|
+
session_id=session_id,
|
|
394
|
+
update=update_agent_message(
|
|
395
|
+
text_block("⚠️ CodeOptiX: Potential security issue detected in code")
|
|
396
|
+
),
|
|
397
|
+
source="codeoptix",
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
def on_connect(self, conn: Any) -> None:
|
|
401
|
+
"""Called when client connects to agent."""
|
|
402
|
+
logger.debug("Bridge connected to ACP agent")
|