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,812 @@
|
|
|
1
|
+
"""Configuration loader for SuperQode."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from .schema import (
|
|
9
|
+
Config,
|
|
10
|
+
SuperQodeConfig,
|
|
11
|
+
TeamConfig,
|
|
12
|
+
ModeConfig,
|
|
13
|
+
RoleConfig,
|
|
14
|
+
ProviderConfig,
|
|
15
|
+
ResolvedMode,
|
|
16
|
+
ResolvedRole,
|
|
17
|
+
MCPServerConfigYAML,
|
|
18
|
+
HandoffConfig,
|
|
19
|
+
CrossValidationConfig,
|
|
20
|
+
AgentConfigBlock,
|
|
21
|
+
GatewayConfig,
|
|
22
|
+
CostTrackingConfig,
|
|
23
|
+
ErrorConfig,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConfigError(Exception):
|
|
28
|
+
"""Configuration loading error."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_config_file() -> Optional[Path]:
|
|
34
|
+
"""Find the superqode.yaml configuration file."""
|
|
35
|
+
# Check current directory first
|
|
36
|
+
config_path = Path.cwd() / "superqode.yaml"
|
|
37
|
+
if config_path.exists():
|
|
38
|
+
return config_path
|
|
39
|
+
|
|
40
|
+
# Check user home directory
|
|
41
|
+
home_config = Path.home() / ".superqode.yaml"
|
|
42
|
+
if home_config.exists():
|
|
43
|
+
return home_config
|
|
44
|
+
|
|
45
|
+
# Check system config directory
|
|
46
|
+
system_config = Path("/etc/superqode/superqode.yaml")
|
|
47
|
+
if system_config.exists():
|
|
48
|
+
return system_config
|
|
49
|
+
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_config_from_file(config_path: Optional[Path] = None) -> Dict[str, Any]:
|
|
54
|
+
"""Load configuration from YAML file."""
|
|
55
|
+
if config_path is None:
|
|
56
|
+
config_path = find_config_file()
|
|
57
|
+
|
|
58
|
+
if config_path is None or not config_path.exists():
|
|
59
|
+
# Return default empty config
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
64
|
+
return yaml.safe_load(f) or {}
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise ConfigError(f"Failed to load config from {config_path}: {e}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_provider_config(data: Dict[str, Any]) -> ProviderConfig:
|
|
70
|
+
"""Parse provider configuration."""
|
|
71
|
+
return ProviderConfig(
|
|
72
|
+
api_key_env=data.get("api_key_env", data.get("api_key", "")),
|
|
73
|
+
description=data.get("description", ""),
|
|
74
|
+
base_url=data.get("base_url", data.get("endpoint")),
|
|
75
|
+
recommended_models=data.get("recommended_models", data.get("models", [])),
|
|
76
|
+
custom_models_allowed=data.get("custom_models_allowed", True),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_handoff_config(data: Dict[str, Any]) -> HandoffConfig:
|
|
81
|
+
"""Parse handoff configuration."""
|
|
82
|
+
return HandoffConfig(
|
|
83
|
+
to=data.get("to", ""),
|
|
84
|
+
when=data.get("when", "task_complete"),
|
|
85
|
+
include=data.get("include", ["summary", "files_modified"]),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def parse_cross_validation_config(data: Dict[str, Any]) -> CrossValidationConfig:
|
|
90
|
+
"""Parse cross-validation configuration."""
|
|
91
|
+
return CrossValidationConfig(
|
|
92
|
+
enabled=data.get("enabled", True),
|
|
93
|
+
exclude_same_model=data.get("exclude_same_model", True),
|
|
94
|
+
exclude_same_provider=data.get("exclude_same_provider", False),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def parse_agent_config_block(data: Dict[str, Any]) -> AgentConfigBlock:
|
|
99
|
+
"""Parse agent config block for ACP mode."""
|
|
100
|
+
return AgentConfigBlock(
|
|
101
|
+
provider=data.get("provider"),
|
|
102
|
+
model=data.get("model"),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def parse_role_config(data: Dict[str, Any]) -> RoleConfig:
|
|
107
|
+
"""Parse role configuration.
|
|
108
|
+
|
|
109
|
+
Supports three execution modes:
|
|
110
|
+
- BYOK (mode="byok"): Direct LLM API calls via gateway
|
|
111
|
+
- ACP (mode="acp"): Full coding agent via Agent Client Protocol
|
|
112
|
+
- LOCAL (mode="local"): Local/self-hosted models (no API keys)
|
|
113
|
+
"""
|
|
114
|
+
handoff = None
|
|
115
|
+
if "handoff" in data:
|
|
116
|
+
handoff = parse_handoff_config(data["handoff"])
|
|
117
|
+
|
|
118
|
+
cross_validation = None
|
|
119
|
+
if "cross_validation" in data:
|
|
120
|
+
cross_validation = parse_cross_validation_config(data["cross_validation"])
|
|
121
|
+
|
|
122
|
+
# Parse agent_config if present
|
|
123
|
+
agent_config = None
|
|
124
|
+
if "agent_config" in data:
|
|
125
|
+
agent_config = parse_agent_config_block(data["agent_config"])
|
|
126
|
+
|
|
127
|
+
# Determine execution mode
|
|
128
|
+
# Explicit mode takes precedence
|
|
129
|
+
mode = data.get("mode", "").lower()
|
|
130
|
+
|
|
131
|
+
# If no explicit mode, infer from config
|
|
132
|
+
if not mode:
|
|
133
|
+
# New explicit 'agent' field = ACP
|
|
134
|
+
if data.get("agent"):
|
|
135
|
+
mode = "acp"
|
|
136
|
+
# Legacy: coding_agent that's a known ACP agent = ACP
|
|
137
|
+
elif data.get("coding_agent") and data.get("coding_agent") not in ("superqode", "byok"):
|
|
138
|
+
# If coding_agent is set to something like "opencode", "claude-code", etc.
|
|
139
|
+
# This is the OLD way of specifying ACP agents
|
|
140
|
+
mode = "acp"
|
|
141
|
+
# Has provider but no coding_agent = BYOK
|
|
142
|
+
elif data.get("provider") and not data.get("coding_agent"):
|
|
143
|
+
mode = "byok"
|
|
144
|
+
# Default to BYOK only if explicitly superqode
|
|
145
|
+
elif data.get("coding_agent") == "superqode":
|
|
146
|
+
mode = "byok"
|
|
147
|
+
else:
|
|
148
|
+
# Default: if coding_agent is set to an agent name, it's ACP
|
|
149
|
+
mode = "acp" if data.get("coding_agent") else "byok"
|
|
150
|
+
|
|
151
|
+
# Parse expert prompt configuration
|
|
152
|
+
expert_prompt_enabled = data.get("expert_prompt_enabled", False) # Default: OSS off
|
|
153
|
+
expert_prompt = data.get("expert_prompt", None) # Optional override
|
|
154
|
+
|
|
155
|
+
# Validate mode - accept "local" as well
|
|
156
|
+
valid_mode = mode if mode in ("byok", "acp", "local") else "byok"
|
|
157
|
+
|
|
158
|
+
# If mode is "local", validate provider is local
|
|
159
|
+
if valid_mode == "local":
|
|
160
|
+
from ..providers.registry import PROVIDERS, ProviderCategory
|
|
161
|
+
|
|
162
|
+
provider_id = data.get("provider")
|
|
163
|
+
if provider_id:
|
|
164
|
+
provider_def = PROVIDERS.get(provider_id)
|
|
165
|
+
if provider_def and provider_def.category != ProviderCategory.LOCAL:
|
|
166
|
+
# Provider is not local, fall back to byok
|
|
167
|
+
valid_mode = "byok"
|
|
168
|
+
|
|
169
|
+
return RoleConfig(
|
|
170
|
+
description=data.get("description", ""),
|
|
171
|
+
mode=valid_mode,
|
|
172
|
+
# BYOK/LOCAL mode settings
|
|
173
|
+
provider=data.get("provider"),
|
|
174
|
+
model=data.get("model"),
|
|
175
|
+
# ACP mode settings
|
|
176
|
+
agent=data.get("agent"),
|
|
177
|
+
agent_config=agent_config,
|
|
178
|
+
# Common settings
|
|
179
|
+
job_description=data.get("job_description", data.get("persona", "")),
|
|
180
|
+
enabled=data.get("enabled", True),
|
|
181
|
+
mcp_servers=data.get("mcp_servers", data.get("mcp", [])),
|
|
182
|
+
handoff=handoff,
|
|
183
|
+
cross_validation=cross_validation,
|
|
184
|
+
# Expert prompt configuration
|
|
185
|
+
expert_prompt_enabled=expert_prompt_enabled,
|
|
186
|
+
expert_prompt=expert_prompt,
|
|
187
|
+
# Legacy field (for backward compatibility) - use 'agent' if 'coding_agent' not set
|
|
188
|
+
coding_agent=data.get("coding_agent") or data.get("agent") or "superqode",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def parse_mode_config(data: Dict[str, Any]) -> ModeConfig:
|
|
193
|
+
"""Parse mode configuration."""
|
|
194
|
+
mode = ModeConfig(
|
|
195
|
+
description=data.get("description", ""),
|
|
196
|
+
enabled=data.get("enabled", True),
|
|
197
|
+
mcp_servers=data.get("mcp_servers", []),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Handle roles if present
|
|
201
|
+
if "roles" in data:
|
|
202
|
+
for role_name, role_data in data["roles"].items():
|
|
203
|
+
mode.roles[role_name] = parse_role_config(role_data)
|
|
204
|
+
else:
|
|
205
|
+
# Direct mode configuration
|
|
206
|
+
mode.coding_agent = data.get("coding_agent")
|
|
207
|
+
mode.provider = data.get("provider")
|
|
208
|
+
mode.model = data.get("model")
|
|
209
|
+
mode.job_description = data.get("job_description")
|
|
210
|
+
|
|
211
|
+
return mode
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def parse_mcp_server_config(data: Dict[str, Any]) -> MCPServerConfigYAML:
|
|
215
|
+
"""Parse MCP server configuration from YAML."""
|
|
216
|
+
return MCPServerConfigYAML(
|
|
217
|
+
transport=data.get("transport", "stdio"),
|
|
218
|
+
enabled=data.get("enabled", not data.get("disabled", False)),
|
|
219
|
+
auto_connect=data.get("auto_connect", data.get("autoConnect", True)),
|
|
220
|
+
command=data.get("command"),
|
|
221
|
+
args=data.get("args", []),
|
|
222
|
+
env=data.get("env", {}),
|
|
223
|
+
cwd=data.get("cwd"),
|
|
224
|
+
url=data.get("url"),
|
|
225
|
+
headers=data.get("headers", {}),
|
|
226
|
+
timeout=data.get("timeout", 30.0),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def parse_gateway_config(data: Dict[str, Any]) -> GatewayConfig:
|
|
231
|
+
"""Parse gateway configuration."""
|
|
232
|
+
return GatewayConfig(
|
|
233
|
+
type=data.get("type", "litellm"),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def parse_cost_tracking_config(data: Dict[str, Any]) -> CostTrackingConfig:
|
|
238
|
+
"""Parse cost tracking configuration."""
|
|
239
|
+
return CostTrackingConfig(
|
|
240
|
+
enabled=data.get("enabled", True),
|
|
241
|
+
show_after_task=data.get("show_after_task", True),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def parse_error_config(data: Dict[str, Any]) -> ErrorConfig:
|
|
246
|
+
"""Parse error handling configuration."""
|
|
247
|
+
return ErrorConfig(
|
|
248
|
+
surface_rate_limits=data.get("surface_rate_limits", True),
|
|
249
|
+
surface_auth_errors=data.get("surface_auth_errors", True),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def parse_config(data: Dict[str, Any]) -> Config:
|
|
254
|
+
"""Parse the complete configuration."""
|
|
255
|
+
config = Config()
|
|
256
|
+
|
|
257
|
+
# Parse superqode metadata
|
|
258
|
+
if "superqode" in data:
|
|
259
|
+
sq_data = data["superqode"]
|
|
260
|
+
config.superqode = SuperQodeConfig(
|
|
261
|
+
version=sq_data.get("version", "1.0"),
|
|
262
|
+
team_name=sq_data.get("team_name", sq_data.get("name", "My Development Team")),
|
|
263
|
+
description=sq_data.get("description", "Multi-agent software development team"),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Parse gateway config
|
|
267
|
+
if "gateway" in sq_data:
|
|
268
|
+
config.superqode.gateway = parse_gateway_config(sq_data["gateway"])
|
|
269
|
+
|
|
270
|
+
# Parse cost tracking config
|
|
271
|
+
if "cost_tracking" in sq_data:
|
|
272
|
+
config.superqode.cost_tracking = parse_cost_tracking_config(sq_data["cost_tracking"])
|
|
273
|
+
|
|
274
|
+
# Parse error config
|
|
275
|
+
if "errors" in sq_data:
|
|
276
|
+
config.superqode.errors = parse_error_config(sq_data["errors"])
|
|
277
|
+
|
|
278
|
+
# Parse default configuration
|
|
279
|
+
if "default" in data:
|
|
280
|
+
config.default = parse_role_config(data["default"])
|
|
281
|
+
|
|
282
|
+
# Parse team configuration
|
|
283
|
+
if "team" in data:
|
|
284
|
+
team_data = data["team"]
|
|
285
|
+
|
|
286
|
+
# Handle both flat structure (mode_name: config) and nested structure (modes: {mode_name: config})
|
|
287
|
+
if "modes" in team_data and isinstance(team_data["modes"], dict):
|
|
288
|
+
# Nested structure: team.modes.{mode_name}
|
|
289
|
+
for mode_name, mode_data in team_data["modes"].items():
|
|
290
|
+
config.team.modes[mode_name] = parse_mode_config(mode_data)
|
|
291
|
+
else:
|
|
292
|
+
# Flat structure: team.{mode_name}
|
|
293
|
+
for mode_name, mode_data in team_data.items():
|
|
294
|
+
if mode_name != "modes": # Skip the nested modes key if it exists
|
|
295
|
+
config.team.modes[mode_name] = parse_mode_config(mode_data)
|
|
296
|
+
|
|
297
|
+
# Parse providers
|
|
298
|
+
if "providers" in data:
|
|
299
|
+
for provider_name, provider_data in data["providers"].items():
|
|
300
|
+
config.providers[provider_name] = parse_provider_config(provider_data)
|
|
301
|
+
|
|
302
|
+
# Parse MCP servers
|
|
303
|
+
if "mcp_servers" in data:
|
|
304
|
+
for server_id, server_data in data["mcp_servers"].items():
|
|
305
|
+
config.mcp_servers[server_id] = parse_mcp_server_config(server_data)
|
|
306
|
+
# Also support mcpServers format (for compatibility)
|
|
307
|
+
if "mcpServers" in data:
|
|
308
|
+
for server_id, server_data in data["mcpServers"].items():
|
|
309
|
+
config.mcp_servers[server_id] = parse_mcp_server_config(server_data)
|
|
310
|
+
|
|
311
|
+
# Parse other sections
|
|
312
|
+
config.agents = data.get("agents", {})
|
|
313
|
+
config.code_agents = data.get("code_agents", [])
|
|
314
|
+
config.custom_models = data.get("custom_models", {})
|
|
315
|
+
config.model_aliases = data.get("model_aliases", {})
|
|
316
|
+
config.legacy = data.get("legacy", {})
|
|
317
|
+
|
|
318
|
+
return config
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def resolve_model_spec(model_spec: str, config: Config) -> tuple[str, str]:
|
|
322
|
+
"""Resolve a model specification to (provider, model) tuple."""
|
|
323
|
+
# Check aliases first
|
|
324
|
+
if model_spec in config.model_aliases:
|
|
325
|
+
model_spec = config.model_aliases[model_spec]
|
|
326
|
+
|
|
327
|
+
# Check custom models
|
|
328
|
+
if model_spec in config.custom_models:
|
|
329
|
+
custom_def = config.custom_models[model_spec]
|
|
330
|
+
return custom_def["provider"], custom_def["model"]
|
|
331
|
+
|
|
332
|
+
# Auto-detect provider from model name patterns
|
|
333
|
+
provider_patterns = {
|
|
334
|
+
"claude-": "anthropic",
|
|
335
|
+
"gpt-": "openai",
|
|
336
|
+
"gemini-": "google",
|
|
337
|
+
"glm-": "zhipuai",
|
|
338
|
+
"DeepSeek-": "deepseek",
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for pattern, provider in provider_patterns.items():
|
|
342
|
+
if model_spec.startswith(pattern):
|
|
343
|
+
return provider, model_spec
|
|
344
|
+
|
|
345
|
+
# Check for Ollama model patterns (name:tag format)
|
|
346
|
+
if ":" in model_spec and not model_spec.startswith(("http://", "https://")):
|
|
347
|
+
# Common Ollama model patterns
|
|
348
|
+
ollama_indicators = [
|
|
349
|
+
"llama",
|
|
350
|
+
"mistral",
|
|
351
|
+
"codellama",
|
|
352
|
+
"code",
|
|
353
|
+
"qwen",
|
|
354
|
+
"phi",
|
|
355
|
+
"gemma",
|
|
356
|
+
"orca",
|
|
357
|
+
"vicuna",
|
|
358
|
+
"wizard",
|
|
359
|
+
"openchat",
|
|
360
|
+
"zephyr",
|
|
361
|
+
"neural",
|
|
362
|
+
"dolphin",
|
|
363
|
+
]
|
|
364
|
+
model_base = model_spec.split(":")[0].lower()
|
|
365
|
+
|
|
366
|
+
# Check if it matches known Ollama model patterns
|
|
367
|
+
if any(indicator in model_base for indicator in ollama_indicators):
|
|
368
|
+
return "ollama", model_spec
|
|
369
|
+
|
|
370
|
+
# If it has the name:tag format and no other provider matches, assume Ollama
|
|
371
|
+
# This covers custom or less common models that follow Ollama naming
|
|
372
|
+
return "ollama", model_spec
|
|
373
|
+
|
|
374
|
+
# Default to unknown provider
|
|
375
|
+
return "unknown", model_spec
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def resolve_role(
|
|
379
|
+
mode_name: str, role_name: Optional[str], config: Config
|
|
380
|
+
) -> Optional[ResolvedRole]:
|
|
381
|
+
"""Resolve a mode/role combination to a ResolvedRole."""
|
|
382
|
+
if mode_name not in config.team.modes:
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
mode_config = config.team.modes[mode_name]
|
|
386
|
+
|
|
387
|
+
if not mode_config.enabled:
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
if role_name:
|
|
391
|
+
# Hierarchical mode with specific role
|
|
392
|
+
if role_name not in mode_config.roles:
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
role_config = mode_config.roles[role_name]
|
|
396
|
+
if not role_config.enabled:
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
# Resolve provider/model if needed
|
|
400
|
+
provider = role_config.provider
|
|
401
|
+
model = role_config.model
|
|
402
|
+
|
|
403
|
+
if role_config.coding_agent == "superqode" and (not provider or not model):
|
|
404
|
+
# Use default if not specified
|
|
405
|
+
if config.default:
|
|
406
|
+
provider = provider or config.default.provider
|
|
407
|
+
model = model or config.default.model
|
|
408
|
+
|
|
409
|
+
# Determine agent_type based on execution mode
|
|
410
|
+
execution_mode = role_config.mode
|
|
411
|
+
if execution_mode == "acp":
|
|
412
|
+
agent_type = "acp"
|
|
413
|
+
elif execution_mode == "local":
|
|
414
|
+
# Local mode uses BYOK execution but with local provider identification
|
|
415
|
+
agent_type = "byok"
|
|
416
|
+
elif execution_mode == "byok":
|
|
417
|
+
agent_type = "byok"
|
|
418
|
+
else:
|
|
419
|
+
# Legacy fallback
|
|
420
|
+
agent_type = "acp" if role_config.coding_agent != "superqode" else "superqode"
|
|
421
|
+
|
|
422
|
+
# Combine MCP servers from mode and role
|
|
423
|
+
mcp_servers = list(mode_config.mcp_servers) + list(role_config.mcp_servers)
|
|
424
|
+
|
|
425
|
+
return ResolvedRole(
|
|
426
|
+
mode=mode_name,
|
|
427
|
+
role=role_name,
|
|
428
|
+
description=role_config.description,
|
|
429
|
+
coding_agent=role_config.coding_agent,
|
|
430
|
+
agent_type=agent_type,
|
|
431
|
+
provider=provider,
|
|
432
|
+
model=model,
|
|
433
|
+
job_description=role_config.job_description,
|
|
434
|
+
enabled=role_config.enabled,
|
|
435
|
+
mcp_servers=mcp_servers,
|
|
436
|
+
# New execution mode fields
|
|
437
|
+
execution_mode=execution_mode if execution_mode in ("byok", "acp", "local") else "byok",
|
|
438
|
+
agent_id=role_config.agent,
|
|
439
|
+
agent_config=role_config.agent_config,
|
|
440
|
+
# Expert prompt configuration
|
|
441
|
+
expert_prompt_enabled=role_config.expert_prompt_enabled,
|
|
442
|
+
expert_prompt=role_config.expert_prompt,
|
|
443
|
+
)
|
|
444
|
+
else:
|
|
445
|
+
# Direct mode
|
|
446
|
+
if not mode_config.coding_agent:
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
provider = mode_config.provider
|
|
450
|
+
model = mode_config.model
|
|
451
|
+
|
|
452
|
+
if mode_config.coding_agent == "superqode" and (not provider or not model):
|
|
453
|
+
# Use default if not specified
|
|
454
|
+
if config.default:
|
|
455
|
+
provider = provider or config.default.provider
|
|
456
|
+
model = model or config.default.model
|
|
457
|
+
|
|
458
|
+
agent_type = "acp" if mode_config.coding_agent != "superqode" else "superqode"
|
|
459
|
+
|
|
460
|
+
return ResolvedRole(
|
|
461
|
+
mode=mode_name,
|
|
462
|
+
role=None,
|
|
463
|
+
description=mode_config.description,
|
|
464
|
+
coding_agent=mode_config.coding_agent,
|
|
465
|
+
agent_type=agent_type,
|
|
466
|
+
provider=provider,
|
|
467
|
+
model=model,
|
|
468
|
+
job_description=mode_config.job_description or "",
|
|
469
|
+
enabled=mode_config.enabled,
|
|
470
|
+
mcp_servers=list(mode_config.mcp_servers),
|
|
471
|
+
# Default to byok for direct modes
|
|
472
|
+
execution_mode="byok",
|
|
473
|
+
# Expert prompts not applicable for direct modes (no role)
|
|
474
|
+
expert_prompt_enabled=False,
|
|
475
|
+
expert_prompt=None,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def load_enabled_modes(config: Optional[Config] = None) -> Dict[str, ResolvedMode]:
|
|
480
|
+
"""Load only enabled modes and roles from configuration."""
|
|
481
|
+
if config is None:
|
|
482
|
+
config = load_config()
|
|
483
|
+
|
|
484
|
+
enabled_modes = {}
|
|
485
|
+
|
|
486
|
+
for mode_name, mode_config in config.team.modes.items():
|
|
487
|
+
if not mode_config.enabled:
|
|
488
|
+
continue
|
|
489
|
+
|
|
490
|
+
resolved_mode = ResolvedMode(
|
|
491
|
+
name=mode_name,
|
|
492
|
+
description=mode_config.description,
|
|
493
|
+
enabled=mode_config.enabled,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if mode_config.roles:
|
|
497
|
+
# Hierarchical mode - load enabled roles
|
|
498
|
+
for role_name, role_config in mode_config.roles.items():
|
|
499
|
+
resolved_role = resolve_role(mode_name, role_name, config)
|
|
500
|
+
if resolved_role:
|
|
501
|
+
resolved_mode.roles[role_name] = resolved_role
|
|
502
|
+
else:
|
|
503
|
+
# Direct mode
|
|
504
|
+
resolved_role = resolve_role(mode_name, None, config)
|
|
505
|
+
if resolved_role:
|
|
506
|
+
resolved_mode.direct_role = resolved_role
|
|
507
|
+
|
|
508
|
+
# Only include modes that have at least one enabled role
|
|
509
|
+
if resolved_mode.roles or resolved_mode.direct_role:
|
|
510
|
+
enabled_modes[mode_name] = resolved_mode
|
|
511
|
+
|
|
512
|
+
return enabled_modes
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def load_config(config_path: Optional[Path] = None) -> Config:
|
|
516
|
+
"""Load and parse the complete SuperQode configuration."""
|
|
517
|
+
try:
|
|
518
|
+
data = load_config_from_file(config_path)
|
|
519
|
+
return parse_config(data)
|
|
520
|
+
except Exception as e:
|
|
521
|
+
raise ConfigError(f"Failed to load configuration: {e}")
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def save_config(config: Config, config_path: Optional[Path] = None) -> None:
|
|
525
|
+
"""Save configuration to YAML file."""
|
|
526
|
+
if config_path is None:
|
|
527
|
+
config_path = find_config_file() or Path.cwd() / "superqode.yaml"
|
|
528
|
+
|
|
529
|
+
# Convert config back to dict for YAML serialization
|
|
530
|
+
config_dict = {
|
|
531
|
+
"superqode": {
|
|
532
|
+
"version": config.superqode.version,
|
|
533
|
+
"team_name": config.superqode.team_name,
|
|
534
|
+
"description": config.superqode.description,
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if config.default:
|
|
539
|
+
config_dict["default"] = {
|
|
540
|
+
"description": config.default.description,
|
|
541
|
+
"coding_agent": config.default.coding_agent,
|
|
542
|
+
"provider": config.default.provider,
|
|
543
|
+
"model": config.default.model,
|
|
544
|
+
"job_description": config.default.job_description,
|
|
545
|
+
"enabled": config.default.enabled,
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if config.team.modes:
|
|
549
|
+
config_dict["team"] = {}
|
|
550
|
+
for mode_name, mode_config in config.team.modes.items():
|
|
551
|
+
mode_dict = {
|
|
552
|
+
"description": mode_config.description,
|
|
553
|
+
"enabled": mode_config.enabled,
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if mode_config.roles:
|
|
557
|
+
mode_dict["roles"] = {}
|
|
558
|
+
for role_name, role_config in mode_config.roles.items():
|
|
559
|
+
mode_dict["roles"][role_name] = {
|
|
560
|
+
"description": role_config.description,
|
|
561
|
+
"mode": role_config.mode,
|
|
562
|
+
"coding_agent": role_config.coding_agent,
|
|
563
|
+
"provider": role_config.provider,
|
|
564
|
+
"model": role_config.model,
|
|
565
|
+
"job_description": role_config.job_description,
|
|
566
|
+
"enabled": role_config.enabled,
|
|
567
|
+
}
|
|
568
|
+
elif mode_config.coding_agent:
|
|
569
|
+
mode_dict["coding_agent"] = mode_config.coding_agent
|
|
570
|
+
mode_dict["provider"] = mode_config.provider
|
|
571
|
+
mode_dict["model"] = mode_config.model
|
|
572
|
+
mode_dict["job_description"] = mode_config.job_description
|
|
573
|
+
|
|
574
|
+
config_dict["team"][mode_name] = mode_dict
|
|
575
|
+
|
|
576
|
+
if config.providers:
|
|
577
|
+
config_dict["providers"] = {}
|
|
578
|
+
for provider_name, provider_config in config.providers.items():
|
|
579
|
+
config_dict["providers"][provider_name] = {
|
|
580
|
+
"api_key_env": provider_config.api_key_env,
|
|
581
|
+
"description": provider_config.description,
|
|
582
|
+
"base_url": provider_config.base_url,
|
|
583
|
+
"recommended_models": provider_config.recommended_models,
|
|
584
|
+
"custom_models_allowed": provider_config.custom_models_allowed,
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
# Add other sections
|
|
588
|
+
if config.agents:
|
|
589
|
+
config_dict["agents"] = config.agents
|
|
590
|
+
if config.code_agents:
|
|
591
|
+
config_dict["code_agents"] = config.code_agents
|
|
592
|
+
if config.custom_models:
|
|
593
|
+
config_dict["custom_models"] = config.custom_models
|
|
594
|
+
if config.model_aliases:
|
|
595
|
+
config_dict["model_aliases"] = config.model_aliases
|
|
596
|
+
if config.legacy:
|
|
597
|
+
config_dict["legacy"] = config.legacy
|
|
598
|
+
|
|
599
|
+
try:
|
|
600
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
601
|
+
yaml.safe_dump(config_dict, f, default_flow_style=False, sort_keys=False)
|
|
602
|
+
except Exception as e:
|
|
603
|
+
raise ConfigError(f"Failed to save configuration: {e}")
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def create_default_config() -> Config:
|
|
607
|
+
"""Create a default configuration with simplified ACP-only setup."""
|
|
608
|
+
config = Config()
|
|
609
|
+
|
|
610
|
+
# Simplified team configuration - only dev and qe modes
|
|
611
|
+
config.team.modes = {
|
|
612
|
+
"dev": ModeConfig(
|
|
613
|
+
description="Development and coding",
|
|
614
|
+
roles={
|
|
615
|
+
"fullstack": RoleConfig(
|
|
616
|
+
description="Full-stack development and implementation",
|
|
617
|
+
coding_agent="claude-code", # ACP Claude Code agent
|
|
618
|
+
job_description="""You are a full-stack developer using Claude Code.
|
|
619
|
+
Focus on clean, maintainable code across frontend and backend.
|
|
620
|
+
Build complete features and hand off to QA for review.""",
|
|
621
|
+
),
|
|
622
|
+
},
|
|
623
|
+
),
|
|
624
|
+
"qa": ModeConfig(
|
|
625
|
+
description="Quality assurance and testing",
|
|
626
|
+
roles={
|
|
627
|
+
"api_tester": RoleConfig(
|
|
628
|
+
description="API testing and validation",
|
|
629
|
+
coding_agent="gemini-cli", # ACP Gemini CLI agent
|
|
630
|
+
job_description="""You are a QA specialist using Gemini CLI.
|
|
631
|
+
Focus on testing, validation, and code review.
|
|
632
|
+
Critique and improve code quality from development.""",
|
|
633
|
+
),
|
|
634
|
+
},
|
|
635
|
+
),
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
# Available ACP agents for direct connection (14 Official ACP Agents)
|
|
639
|
+
config.code_agents = [
|
|
640
|
+
"gemini", # Google's reference ACP implementation
|
|
641
|
+
"claude-code", # Anthropic's Claude via Zed SDK adapter
|
|
642
|
+
"codex", # OpenAI's code generation agent
|
|
643
|
+
"junie", # JetBrains' AI agent for IDE ecosystem
|
|
644
|
+
"goose", # Square's open-source agent
|
|
645
|
+
"kimi", # CLI AI agent with ACP support
|
|
646
|
+
"opencode", # Open-source coding agent
|
|
647
|
+
"stakpak", # ACP-compatible code assistance
|
|
648
|
+
"vtcode", # Versatile coding agent
|
|
649
|
+
"auggie", # Agentic code capabilities
|
|
650
|
+
"code-assistant", # AI coding assistant in Rust
|
|
651
|
+
"cagent", # Multi-agent runtime
|
|
652
|
+
"fast-agent", # Sophisticated agent workflows
|
|
653
|
+
"llmling-agent", # LLM-powered agent framework
|
|
654
|
+
]
|
|
655
|
+
|
|
656
|
+
# Provider configurations
|
|
657
|
+
config.providers = {
|
|
658
|
+
"anthropic": ProviderConfig(
|
|
659
|
+
api_key_env="ANTHROPIC_API_KEY",
|
|
660
|
+
description="Anthropic Claude models via API",
|
|
661
|
+
recommended_models=["claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-4-5"],
|
|
662
|
+
custom_models_allowed=True,
|
|
663
|
+
),
|
|
664
|
+
"openai": ProviderConfig(
|
|
665
|
+
api_key_env="OPENAI_API_KEY",
|
|
666
|
+
description="OpenAI GPT models via API",
|
|
667
|
+
recommended_models=["gpt-4o", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano"],
|
|
668
|
+
custom_models_allowed=True,
|
|
669
|
+
),
|
|
670
|
+
"google": ProviderConfig(
|
|
671
|
+
api_key_env="GOOGLE_API_KEY",
|
|
672
|
+
description="Google Gemini models via Vertex AI",
|
|
673
|
+
recommended_models=["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash"],
|
|
674
|
+
custom_models_allowed=True,
|
|
675
|
+
),
|
|
676
|
+
"zhipuai": ProviderConfig(
|
|
677
|
+
api_key_env="ZHIPUAI_API_KEY",
|
|
678
|
+
description="ZhipuAI GLM models (智谱AI)",
|
|
679
|
+
recommended_models=["glm-4.7", "glm-4.6", "glm-4.6v", "glm-4.6v-flash"],
|
|
680
|
+
custom_models_allowed=True,
|
|
681
|
+
),
|
|
682
|
+
"deepseek": ProviderConfig(
|
|
683
|
+
api_key_env="DEEPSEEK_API_KEY",
|
|
684
|
+
description="DeepSeek models",
|
|
685
|
+
recommended_models=["DeepSeek-V3.2-Exp", "DeepSeek-V3.2-Exp-Think"],
|
|
686
|
+
custom_models_allowed=True,
|
|
687
|
+
),
|
|
688
|
+
"groq": ProviderConfig(
|
|
689
|
+
api_key_env="GROQ_API_KEY",
|
|
690
|
+
description="Groq ultra-fast inference",
|
|
691
|
+
recommended_models=[
|
|
692
|
+
"llama-3.1-70b-versatile",
|
|
693
|
+
"llama-3.1-8b-instant",
|
|
694
|
+
"mixtral-8x7b-32768",
|
|
695
|
+
],
|
|
696
|
+
custom_models_allowed=True,
|
|
697
|
+
),
|
|
698
|
+
"ollama": ProviderConfig(
|
|
699
|
+
base_url="http://localhost:11434",
|
|
700
|
+
description="Local Ollama models",
|
|
701
|
+
recommended_models=["llama3.1:70b", "qwen2.5:72b", "codellama:34b", "mistral:7b"],
|
|
702
|
+
custom_models_allowed=True,
|
|
703
|
+
),
|
|
704
|
+
"openrouter": ProviderConfig(
|
|
705
|
+
api_key_env="OPENROUTER_API_KEY",
|
|
706
|
+
description="OpenRouter - unified API for 100+ models",
|
|
707
|
+
recommended_models=[
|
|
708
|
+
"anthropic/claude-sonnet-4-5",
|
|
709
|
+
"openai/gpt-4o",
|
|
710
|
+
"google/gemini-3-pro-preview",
|
|
711
|
+
"zhipuai/glm-4.7",
|
|
712
|
+
],
|
|
713
|
+
custom_models_allowed=True,
|
|
714
|
+
),
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
# ACP Agents (14 Official ACP Agents)
|
|
718
|
+
config.agents = {
|
|
719
|
+
"acp": {
|
|
720
|
+
"gemini": {
|
|
721
|
+
"enabled": True,
|
|
722
|
+
"description": "Gemini CLI - Google's reference ACP implementation",
|
|
723
|
+
"install_command": "npm install -g @anthropic-ai/gemini-cli",
|
|
724
|
+
"api_key_env": "GEMINI_API_KEY",
|
|
725
|
+
},
|
|
726
|
+
"claude-code": {
|
|
727
|
+
"enabled": True,
|
|
728
|
+
"description": "Claude Code - Anthropic's Claude via Zed SDK adapter",
|
|
729
|
+
"install_command": "npm install -g @anthropic-ai/claude-code",
|
|
730
|
+
"api_key_env": "ANTHROPIC_API_KEY",
|
|
731
|
+
},
|
|
732
|
+
"codex": {
|
|
733
|
+
"enabled": True,
|
|
734
|
+
"description": "Codex - OpenAI's code generation agent",
|
|
735
|
+
"install_command": "npm install -g @openai/codex",
|
|
736
|
+
"api_key_env": "OPENAI_API_KEY",
|
|
737
|
+
},
|
|
738
|
+
"junie": {
|
|
739
|
+
"enabled": True,
|
|
740
|
+
"description": "JetBrains Junie - AI agent for IDE ecosystem",
|
|
741
|
+
"install_command": "npm install -g @jetbrains/junie",
|
|
742
|
+
},
|
|
743
|
+
"goose": {
|
|
744
|
+
"enabled": True,
|
|
745
|
+
"description": "Goose - Square's open-source agent",
|
|
746
|
+
"install_command": "curl -fsSL https://github.com/block/goose/releases/latest/download/install.sh | bash",
|
|
747
|
+
},
|
|
748
|
+
"kimi": {
|
|
749
|
+
"enabled": True,
|
|
750
|
+
"description": "Kimi CLI - CLI AI agent with ACP support",
|
|
751
|
+
"install_command": "npm install -g @anthropic-ai/kimi-cli",
|
|
752
|
+
"api_key_env": "MOONSHOT_API_KEY",
|
|
753
|
+
},
|
|
754
|
+
"opencode": {
|
|
755
|
+
"enabled": True,
|
|
756
|
+
"description": "OpenCode - Open-source coding agent",
|
|
757
|
+
"install_command": "go install github.com/opencode-ai/opencode@latest",
|
|
758
|
+
},
|
|
759
|
+
"stakpak": {
|
|
760
|
+
"enabled": True,
|
|
761
|
+
"description": "Stakpak - ACP-compatible code assistance",
|
|
762
|
+
"install_command": "npm install -g stakpak",
|
|
763
|
+
},
|
|
764
|
+
"vtcode": {
|
|
765
|
+
"enabled": True,
|
|
766
|
+
"description": "VT Code - Versatile coding agent",
|
|
767
|
+
"install_command": "npm install -g vtcode",
|
|
768
|
+
},
|
|
769
|
+
"auggie": {
|
|
770
|
+
"enabled": True,
|
|
771
|
+
"description": "Augment Code - Agentic code capabilities",
|
|
772
|
+
"install_command": "npm install -g @anthropic-ai/auggie",
|
|
773
|
+
"api_key_env": "AUGMENT_API_KEY",
|
|
774
|
+
},
|
|
775
|
+
"code-assistant": {
|
|
776
|
+
"enabled": True,
|
|
777
|
+
"description": "Code Assistant - AI coding assistant in Rust",
|
|
778
|
+
"install_command": "cargo install code-assistant",
|
|
779
|
+
},
|
|
780
|
+
"cagent": {
|
|
781
|
+
"enabled": True,
|
|
782
|
+
"description": "cagent - Multi-agent runtime orchestration",
|
|
783
|
+
"install_command": "npm install -g cagent",
|
|
784
|
+
},
|
|
785
|
+
"fast-agent": {
|
|
786
|
+
"enabled": True,
|
|
787
|
+
"description": "fast-agent - Sophisticated agent workflows",
|
|
788
|
+
"install_command": "pip install fast-agent",
|
|
789
|
+
},
|
|
790
|
+
"llmling-agent": {
|
|
791
|
+
"enabled": True,
|
|
792
|
+
"description": "LLMling-Agent - LLM-powered agent framework",
|
|
793
|
+
"install_command": "pip install llmling-agent",
|
|
794
|
+
},
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
# Model aliases
|
|
799
|
+
config.model_aliases = {
|
|
800
|
+
"latest-sonnet": "claude-sonnet-4-5",
|
|
801
|
+
"latest-gpt": "gpt-4o",
|
|
802
|
+
"latest-gemini": "gemini-3-pro-preview",
|
|
803
|
+
"latest-glm": "glm-4.7",
|
|
804
|
+
"fast": "claude-haiku-4-5",
|
|
805
|
+
"balanced": "claude-sonnet-4-5",
|
|
806
|
+
"powerful": "claude-opus-4-1",
|
|
807
|
+
"thinking": "DeepSeek-V3.2-Exp-Think",
|
|
808
|
+
"vision": "glm-4.6v",
|
|
809
|
+
"coding": "codellama:34b",
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return config
|