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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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
|
+
}
|