superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,347 @@
1
+ """
2
+ Execution Modes for SuperQode.
3
+
4
+ Defines the primary execution modes:
5
+ - BYOK: Direct LLM API calls (user provides API keys)
6
+ - ACP: Agent Client Protocol (full coding agent capabilities)
7
+ - LOCAL: Local/self-hosted models (no API keys required)
8
+
9
+ QE Modes (Perception & Usability):
10
+ - Quick Scan: Time-boxed, shallow exploration, pre-commit/fast CI
11
+ - Deep QE: Full sandbox, destructive testing, pre-release/nightly CI
12
+
13
+ SECURITY PRINCIPLE:
14
+ - BYOK: Keys read from user's environment, never stored by SuperQode
15
+ - ACP: Agent manages its own auth, SuperQode just connects
16
+ - LOCAL: No API keys needed, runs on local machine
17
+ """
18
+
19
+ from dataclasses import dataclass, field
20
+ from enum import Enum
21
+ from typing import Any, Dict, List, Optional
22
+
23
+
24
+ class ExecutionMode(Enum):
25
+ """Execution mode for a role."""
26
+
27
+ BYOK = "byok" # Bring Your Own Key - Direct LLM API
28
+ ACP = "acp" # Agent Client Protocol - Full agent
29
+ LOCAL = "local" # Local/self-hosted models - No API key required
30
+
31
+
32
+ class QEMode(Enum):
33
+ """QE execution mode."""
34
+
35
+ QUICK_SCAN = "quick_scan" # Fast, shallow, time-boxed
36
+ DEEP_QE = "deep_qe" # Full exploration, destructive allowed
37
+
38
+
39
+ class GatewayType(Enum):
40
+ """Gateway type for BYOK mode."""
41
+
42
+ LITELLM = "litellm" # Default: LiteLLM unified API
43
+ DIRECT = "direct" # Future: Direct API calls
44
+
45
+
46
+ @dataclass
47
+ class BYOKConfig:
48
+ """Configuration for BYOK (Bring Your Own Key) mode.
49
+
50
+ In BYOK mode:
51
+ - SuperQode makes direct LLM API calls via a gateway (LiteLLM)
52
+ - User provides API keys via environment variables
53
+ - Capabilities: Chat completion, streaming, tool calling (if supported)
54
+ - No agent features (no file editing, no shell commands)
55
+ """
56
+
57
+ provider: str
58
+ model: str
59
+ gateway: GatewayType = GatewayType.LITELLM
60
+
61
+ # Optional overrides
62
+ base_url: Optional[str] = None
63
+ extra_headers: Dict[str, str] = field(default_factory=dict)
64
+
65
+ # Cost tracking
66
+ track_costs: bool = True
67
+
68
+ def get_litellm_model(self) -> str:
69
+ """Get the model string for LiteLLM."""
70
+ from ..providers.registry import PROVIDERS
71
+
72
+ provider_def = PROVIDERS.get(self.provider)
73
+ if provider_def and provider_def.litellm_prefix:
74
+ # Don't double-prefix
75
+ if self.model.startswith(provider_def.litellm_prefix):
76
+ return self.model
77
+ return f"{provider_def.litellm_prefix}{self.model}"
78
+ return self.model
79
+
80
+
81
+ @dataclass
82
+ class ACPConfig:
83
+ """Configuration for ACP (Agent Client Protocol) mode.
84
+
85
+ In ACP mode:
86
+ - SuperQode connects to an ACP-compatible coding agent
87
+ - Agent manages its own LLM authentication
88
+ - Capabilities: Full agent (files, shell, MCP, reasoning)
89
+ - Agent handles all LLM interactions internally
90
+ """
91
+
92
+ agent: str # Agent ID (e.g., "opencode")
93
+
94
+ # Agent's internal LLM config (passed to agent)
95
+ agent_provider: Optional[str] = None
96
+ agent_model: Optional[str] = None
97
+
98
+ # Connection settings
99
+ connection_type: str = "stdio" # "stdio" | "http"
100
+ command: Optional[str] = None # Override agent command
101
+ host: Optional[str] = None # For HTTP connections
102
+ port: Optional[int] = None
103
+
104
+
105
+ @dataclass
106
+ class ExecutionConfig:
107
+ """Complete execution configuration for a role."""
108
+
109
+ mode: ExecutionMode
110
+
111
+ # Mode-specific config (one will be set based on mode)
112
+ byok: Optional[BYOKConfig] = None
113
+ acp: Optional[ACPConfig] = None
114
+
115
+ # Common settings
116
+ job_description: str = ""
117
+ enabled: bool = True
118
+
119
+ @classmethod
120
+ def from_byok(
121
+ cls, provider: str, model: str, job_description: str = "", **kwargs
122
+ ) -> "ExecutionConfig":
123
+ """Create a BYOK execution config."""
124
+ return cls(
125
+ mode=ExecutionMode.BYOK,
126
+ byok=BYOKConfig(provider=provider, model=model, **kwargs),
127
+ job_description=job_description,
128
+ )
129
+
130
+ @classmethod
131
+ def from_acp(
132
+ cls,
133
+ agent: str,
134
+ agent_provider: Optional[str] = None,
135
+ agent_model: Optional[str] = None,
136
+ job_description: str = "",
137
+ **kwargs,
138
+ ) -> "ExecutionConfig":
139
+ """Create an ACP execution config."""
140
+ return cls(
141
+ mode=ExecutionMode.ACP,
142
+ acp=ACPConfig(
143
+ agent=agent, agent_provider=agent_provider, agent_model=agent_model, **kwargs
144
+ ),
145
+ job_description=job_description,
146
+ )
147
+
148
+ def get_mode_info(self) -> Dict[str, Any]:
149
+ """Get human-readable info about the execution mode."""
150
+ if self.mode == ExecutionMode.BYOK:
151
+ return {
152
+ "mode": "BYOK (Bring Your Own Key)",
153
+ "description": "Direct LLM API calls via gateway",
154
+ "provider": self.byok.provider if self.byok else None,
155
+ "model": self.byok.model if self.byok else None,
156
+ "gateway": self.byok.gateway.value if self.byok else None,
157
+ "capabilities": [
158
+ "Chat completion",
159
+ "Streaming responses",
160
+ "Tool calling (if model supports)",
161
+ ],
162
+ "limitations": [
163
+ "No file editing",
164
+ "No shell commands",
165
+ "No MCP tools",
166
+ ],
167
+ "auth_info": "API key from your environment variables",
168
+ }
169
+ else: # ACP
170
+ return {
171
+ "mode": "ACP (Agent Client Protocol)",
172
+ "description": "Full coding agent capabilities",
173
+ "agent": self.acp.agent if self.acp else None,
174
+ "agent_provider": self.acp.agent_provider if self.acp else None,
175
+ "agent_model": self.acp.agent_model if self.acp else None,
176
+ "capabilities": [
177
+ "File reading/writing",
178
+ "Shell command execution",
179
+ "MCP tool integration",
180
+ "Multi-step reasoning",
181
+ "Context management",
182
+ ],
183
+ "auth_info": "Managed by the agent (not SuperQode)",
184
+ }
185
+
186
+
187
+ # =============================================================================
188
+ # QE Mode Configurations
189
+ # =============================================================================
190
+
191
+
192
+ @dataclass
193
+ class QuickScanConfig:
194
+ """
195
+ Quick Scan Mode Configuration.
196
+
197
+ Use cases:
198
+ - Pre-commit hooks
199
+ - Developer laptop testing
200
+ - Fast CI feedback
201
+
202
+ Characteristics:
203
+ - Time-boxed (seconds, not minutes)
204
+ - Shallow exploration
205
+ - High-risk paths only
206
+ - Minimal QIRs
207
+ """
208
+
209
+ timeout_seconds: int = 60
210
+ depth: str = "shallow"
211
+
212
+ # Execution constraints
213
+ fail_fast: bool = True
214
+ max_tests: int = 50 # Limit number of tests to run
215
+
216
+ # Test selection
217
+ run_smoke: bool = True
218
+ run_sanity: bool = True
219
+ run_regression: bool = False # Skip full regression
220
+
221
+ # Generation constraints
222
+ generate_tests: bool = False
223
+ generate_patches: bool = False
224
+
225
+ # Destructive testing
226
+ destructive_allowed: bool = False
227
+
228
+ # QIR settings
229
+ minimal_qir: bool = True # Short summary only
230
+
231
+ def to_dict(self) -> Dict[str, Any]:
232
+ return {
233
+ "mode": "quick_scan",
234
+ "timeout_seconds": self.timeout_seconds,
235
+ "depth": self.depth,
236
+ "fail_fast": self.fail_fast,
237
+ "max_tests": self.max_tests,
238
+ "run_smoke": self.run_smoke,
239
+ "run_sanity": self.run_sanity,
240
+ "run_regression": self.run_regression,
241
+ "generate_tests": self.generate_tests,
242
+ "generate_patches": self.generate_patches,
243
+ "destructive_allowed": self.destructive_allowed,
244
+ "minimal_qir": self.minimal_qir,
245
+ }
246
+
247
+
248
+ @dataclass
249
+ class DeepQEConfig:
250
+ """
251
+ Deep QE Mode Configuration.
252
+
253
+ Use cases:
254
+ - Pre-release validation
255
+ - Nightly CI runs
256
+ - Compliance evidence gathering
257
+
258
+ Characteristics:
259
+ - Full sandbox environment
260
+ - Destructive testing allowed
261
+ - Failure simulation hooks
262
+ - Full Investigation Reports
263
+ """
264
+
265
+ timeout_seconds: int = 1800 # 30 minutes
266
+ depth: str = "full"
267
+
268
+ # Execution constraints
269
+ fail_fast: bool = False
270
+ max_tests: int = 0 # No limit
271
+
272
+ # Test selection
273
+ run_smoke: bool = True
274
+ run_sanity: bool = True
275
+ run_regression: bool = True
276
+
277
+ # Generation enabled
278
+ generate_tests: bool = True
279
+ generate_patches: bool = True
280
+
281
+ # Test generation types
282
+ generate_unit_tests: bool = True
283
+ generate_integration_tests: bool = True
284
+ generate_api_tests: bool = True
285
+ generate_fuzz_tests: bool = True
286
+ generate_security_tests: bool = True
287
+
288
+ # Destructive testing
289
+ destructive_allowed: bool = True
290
+ simulate_failures: bool = True
291
+ stress_testing: bool = True
292
+ chaos_testing: bool = False # Advanced - disabled by default
293
+
294
+ # QIR settings
295
+ minimal_qir: bool = False # Full detailed report
296
+ include_evidence: bool = True
297
+ include_metrics: bool = True
298
+
299
+ # Flake detection
300
+ detect_flakes: bool = True
301
+ retry_count: int = 2
302
+
303
+ def to_dict(self) -> Dict[str, Any]:
304
+ return {
305
+ "mode": "deep_qe",
306
+ "timeout_seconds": self.timeout_seconds,
307
+ "depth": self.depth,
308
+ "fail_fast": self.fail_fast,
309
+ "max_tests": self.max_tests,
310
+ "run_smoke": self.run_smoke,
311
+ "run_sanity": self.run_sanity,
312
+ "run_regression": self.run_regression,
313
+ "generate_tests": self.generate_tests,
314
+ "generate_patches": self.generate_patches,
315
+ "test_generation": {
316
+ "unit": self.generate_unit_tests,
317
+ "integration": self.generate_integration_tests,
318
+ "api": self.generate_api_tests,
319
+ "fuzz": self.generate_fuzz_tests,
320
+ "security": self.generate_security_tests,
321
+ },
322
+ "destructive_testing": {
323
+ "allowed": self.destructive_allowed,
324
+ "simulate_failures": self.simulate_failures,
325
+ "stress_testing": self.stress_testing,
326
+ "chaos_testing": self.chaos_testing,
327
+ },
328
+ "qr": {
329
+ "minimal": self.minimal_qir,
330
+ "include_evidence": self.include_evidence,
331
+ "include_metrics": self.include_metrics,
332
+ },
333
+ "flake_detection": {
334
+ "enabled": self.detect_flakes,
335
+ "retry_count": self.retry_count,
336
+ },
337
+ }
338
+
339
+
340
+ def get_qe_mode_config(mode: QEMode) -> QuickScanConfig | DeepQEConfig:
341
+ """Get the configuration for a QE mode."""
342
+ if mode == QEMode.QUICK_SCAN:
343
+ return QuickScanConfig()
344
+ elif mode == QEMode.DEEP_QE:
345
+ return DeepQEConfig()
346
+ else:
347
+ raise ValueError(f"Unknown QE mode: {mode}")
@@ -0,0 +1,283 @@
1
+ """
2
+ Execution Resolver for SuperQode.
3
+
4
+ Resolves role configurations into execution configs, determining
5
+ whether to use BYOK or ACP mode based on the configuration.
6
+ """
7
+
8
+ import os
9
+ from typing import Any, Dict, Optional, Tuple
10
+
11
+ from .modes import (
12
+ ACPConfig,
13
+ BYOKConfig,
14
+ ExecutionConfig,
15
+ ExecutionMode,
16
+ GatewayType,
17
+ )
18
+ from ..providers.registry import PROVIDERS, ProviderDef
19
+ from ..agents.registry import AGENTS, AgentDef, AgentStatus
20
+
21
+
22
+ class ExecutionResolverError(Exception):
23
+ """Base error for execution resolver."""
24
+
25
+ pass
26
+
27
+
28
+ class ProviderNotFoundError(ExecutionResolverError):
29
+ """Provider not found in registry."""
30
+
31
+ def __init__(self, provider_id: str):
32
+ self.provider_id = provider_id
33
+ super().__init__(f"Provider '{provider_id}' not found in registry")
34
+
35
+
36
+ class AgentNotFoundError(ExecutionResolverError):
37
+ """Agent not found in registry."""
38
+
39
+ def __init__(self, agent_id: str):
40
+ self.agent_id = agent_id
41
+ super().__init__(f"Agent '{agent_id}' not found in registry")
42
+
43
+
44
+ class AgentNotSupportedError(ExecutionResolverError):
45
+ """Agent is not yet supported."""
46
+
47
+ def __init__(self, agent_id: str, status: AgentStatus):
48
+ self.agent_id = agent_id
49
+ self.status = status
50
+ super().__init__(f"Agent '{agent_id}' is not yet supported (status: {status.value})")
51
+
52
+
53
+ class MissingEnvVarError(ExecutionResolverError):
54
+ """Required environment variable is missing."""
55
+
56
+ def __init__(self, provider_id: str, env_vars: list, docs_url: str):
57
+ self.provider_id = provider_id
58
+ self.env_vars = env_vars
59
+ self.docs_url = docs_url
60
+ env_list = " or ".join(env_vars)
61
+ super().__init__(
62
+ f"Missing API key for provider '{provider_id}'. "
63
+ f"Set {env_list} environment variable. "
64
+ f"Get your key at: {docs_url}"
65
+ )
66
+
67
+
68
+ class ExecutionResolver:
69
+ """Resolves role configurations into execution configs."""
70
+
71
+ def __init__(self, gateway: GatewayType = GatewayType.LITELLM):
72
+ self.gateway = gateway
73
+
74
+ def resolve_role(
75
+ self,
76
+ role_config: Dict[str, Any],
77
+ validate_env: bool = True,
78
+ ) -> ExecutionConfig:
79
+ """Resolve a role configuration into an execution config.
80
+
81
+ Args:
82
+ role_config: Role configuration dictionary from YAML
83
+ validate_env: Whether to validate environment variables
84
+
85
+ Returns:
86
+ ExecutionConfig with the appropriate mode
87
+
88
+ Raises:
89
+ ExecutionResolverError: If configuration is invalid
90
+ """
91
+ mode = role_config.get("mode", "").lower()
92
+
93
+ # Explicit mode declaration
94
+ if mode == "byok":
95
+ return self._resolve_byok(role_config, validate_env)
96
+ elif mode == "acp":
97
+ return self._resolve_acp(role_config, validate_env)
98
+
99
+ # Implicit mode detection (backward compatibility)
100
+ if "agent" in role_config:
101
+ return self._resolve_acp(role_config, validate_env)
102
+ elif "provider" in role_config:
103
+ return self._resolve_byok(role_config, validate_env)
104
+
105
+ # Default to BYOK if provider is specified
106
+ if role_config.get("provider"):
107
+ return self._resolve_byok(role_config, validate_env)
108
+
109
+ raise ExecutionResolverError(
110
+ "Role must specify either 'mode: byok' with 'provider' or 'mode: acp' with 'agent'"
111
+ )
112
+
113
+ def _resolve_byok(
114
+ self,
115
+ role_config: Dict[str, Any],
116
+ validate_env: bool,
117
+ ) -> ExecutionConfig:
118
+ """Resolve BYOK mode configuration."""
119
+ provider_id = role_config.get("provider")
120
+ model = role_config.get("model")
121
+
122
+ if not provider_id:
123
+ raise ExecutionResolverError("BYOK mode requires 'provider'")
124
+ if not model:
125
+ raise ExecutionResolverError("BYOK mode requires 'model'")
126
+
127
+ # Get provider definition
128
+ provider_def = PROVIDERS.get(provider_id)
129
+
130
+ # Validate environment variables if provider is known
131
+ if provider_def and validate_env:
132
+ self._validate_provider_env(provider_def)
133
+
134
+ # Build BYOK config
135
+ byok_config = BYOKConfig(
136
+ provider=provider_id,
137
+ model=model,
138
+ gateway=self.gateway,
139
+ base_url=role_config.get("base_url"),
140
+ extra_headers=role_config.get("headers", {}),
141
+ track_costs=role_config.get("track_costs", True),
142
+ )
143
+
144
+ return ExecutionConfig(
145
+ mode=ExecutionMode.BYOK,
146
+ byok=byok_config,
147
+ job_description=role_config.get("job_description", ""),
148
+ enabled=role_config.get("enabled", True),
149
+ )
150
+
151
+ def _resolve_acp(
152
+ self,
153
+ role_config: Dict[str, Any],
154
+ validate_env: bool,
155
+ ) -> ExecutionConfig:
156
+ """Resolve ACP mode configuration."""
157
+ agent_id = role_config.get("agent")
158
+
159
+ if not agent_id:
160
+ raise ExecutionResolverError("ACP mode requires 'agent'")
161
+
162
+ # Get agent definition
163
+ agent_def = AGENTS.get(agent_id)
164
+ if not agent_def:
165
+ raise AgentNotFoundError(agent_id)
166
+
167
+ # Check agent status
168
+ if agent_def.status != AgentStatus.SUPPORTED:
169
+ raise AgentNotSupportedError(agent_id, agent_def.status)
170
+
171
+ # Get agent config (provider/model for the agent to use)
172
+ agent_config = role_config.get("agent_config", {})
173
+
174
+ # Build ACP config
175
+ acp_config = ACPConfig(
176
+ agent=agent_id,
177
+ agent_provider=agent_config.get("provider"),
178
+ agent_model=agent_config.get("model"),
179
+ connection_type=role_config.get("connection_type", "stdio"),
180
+ command=role_config.get("command", agent_def.command),
181
+ host=role_config.get("host"),
182
+ port=role_config.get("port"),
183
+ )
184
+
185
+ return ExecutionConfig(
186
+ mode=ExecutionMode.ACP,
187
+ acp=acp_config,
188
+ job_description=role_config.get("job_description", ""),
189
+ enabled=role_config.get("enabled", True),
190
+ )
191
+
192
+ def _validate_provider_env(self, provider_def: ProviderDef) -> None:
193
+ """Validate that required environment variables are set."""
194
+ if not provider_def.env_vars:
195
+ # No env vars required (e.g., local providers)
196
+ return
197
+
198
+ # Check if any of the env vars are set
199
+ for env_var in provider_def.env_vars:
200
+ if os.environ.get(env_var):
201
+ return
202
+
203
+ # None of the env vars are set
204
+ raise MissingEnvVarError(
205
+ provider_def.id,
206
+ provider_def.env_vars,
207
+ provider_def.docs_url,
208
+ )
209
+
210
+ def check_provider_status(self, provider_id: str) -> Dict[str, Any]:
211
+ """Check the status of a provider (env vars, availability).
212
+
213
+ Returns:
214
+ Dictionary with status information
215
+ """
216
+ provider_def = PROVIDERS.get(provider_id)
217
+
218
+ if not provider_def:
219
+ return {
220
+ "provider_id": provider_id,
221
+ "found": False,
222
+ "configured": False,
223
+ "error": f"Provider '{provider_id}' not in registry",
224
+ }
225
+
226
+ # Check env vars
227
+ configured = False
228
+ env_var_status = {}
229
+
230
+ for env_var in provider_def.env_vars:
231
+ value = os.environ.get(env_var)
232
+ env_var_status[env_var] = bool(value)
233
+ if value:
234
+ configured = True
235
+
236
+ # For local providers with no env vars, check base URL
237
+ if not provider_def.env_vars:
238
+ base_url = os.environ.get(
239
+ provider_def.base_url_env or "", provider_def.default_base_url
240
+ )
241
+ configured = bool(base_url)
242
+
243
+ return {
244
+ "provider_id": provider_id,
245
+ "found": True,
246
+ "name": provider_def.name,
247
+ "tier": provider_def.tier.name,
248
+ "category": provider_def.category.value,
249
+ "configured": configured,
250
+ "env_vars": env_var_status,
251
+ "docs_url": provider_def.docs_url,
252
+ "notes": provider_def.notes,
253
+ }
254
+
255
+ def check_agent_status(self, agent_id: str) -> Dict[str, Any]:
256
+ """Check the status of an agent.
257
+
258
+ Returns:
259
+ Dictionary with status information
260
+ """
261
+ agent_def = AGENTS.get(agent_id)
262
+
263
+ if not agent_def:
264
+ return {
265
+ "agent_id": agent_id,
266
+ "found": False,
267
+ "available": False,
268
+ "error": f"Agent '{agent_id}' not in registry",
269
+ }
270
+
271
+ return {
272
+ "agent_id": agent_id,
273
+ "found": True,
274
+ "name": agent_def.name,
275
+ "protocol": agent_def.protocol.value,
276
+ "status": agent_def.status.value,
277
+ "available": agent_def.status == AgentStatus.SUPPORTED,
278
+ "description": agent_def.description,
279
+ "auth_info": agent_def.auth_info,
280
+ "setup_command": agent_def.setup_command,
281
+ "docs_url": agent_def.docs_url,
282
+ "capabilities": agent_def.capabilities,
283
+ }