Skip to content

Commit a4355b6

Browse files
authored
Merge pull request #14 from zhujian0805/main
fix: update MCP test expectations to include opencode client
2 parents f1b295f + 323bc90 commit a4355b6

File tree

4 files changed

+195
-0
lines changed

4 files changed

+195
-0
lines changed

code_assistant_manager/tools.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,22 @@ tools:
325325
touched:
326326
- "~/.local/share/opencode/auth.json (API key storage)"
327327
- "~/opencode.jsonc (provider and MCP server configuration)"
328+
329+
continue:
330+
enabled: true
331+
install_cmd: npm i -g @continuedev/cli
332+
cli_command: cn
333+
description: "Continue.dev CLI"
334+
env:
335+
managed:
336+
NODE_TLS_REJECT_UNAUTHORIZED: "0"
337+
configuration:
338+
notes:
339+
- "Continue.dev is an open-source AI coding assistant"
340+
- "Configuration is stored in ~/.continue/config.yaml"
341+
- "Supports multiple AI providers and models"
342+
cli_parameters:
343+
injected: []
344+
filesystem:
345+
generated:
346+
- "~/.continue/config.yaml (Continue configuration file)"

code_assistant_manager/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
claude,
1313
codebuddy,
1414
codex,
15+
continue_tool,
1516
copilot,
1617
crush,
1718
cursor,
@@ -62,6 +63,7 @@ def select_model(
6263
from .claude import ClaudeTool # noqa: F401,E402
6364
from .codebuddy import CodeBuddyTool # noqa: F401,E402
6465
from .codex import CodexTool # noqa: F401,E402
66+
from .continue_tool import ContinueTool # noqa: F401,E402
6567
from .copilot import CopilotTool # noqa: F401,E402
6668
from .crush import CrushTool # noqa: F401,E402
6769
from .cursor import CursorTool # noqa: F401,E402
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import os
2+
from pathlib import Path
3+
from typing import Dict, List, Optional
4+
5+
import yaml
6+
7+
from .base import CLITool
8+
9+
10+
class ContinueTool(CLITool):
11+
"""Continue.dev CLI wrapper."""
12+
13+
command_name = "cn"
14+
tool_key = "continue"
15+
install_description = "Continue.dev CLI"
16+
17+
def _get_filtered_endpoints(self) -> List[str]:
18+
"""Collect endpoints that support the continue client."""
19+
endpoints = self.config.get_sections(exclude_common=True)
20+
return [
21+
ep
22+
for ep in endpoints
23+
if self.endpoint_manager._is_client_supported(ep, "continue")
24+
]
25+
26+
def _process_endpoint(self, endpoint_name: str) -> Optional[str]:
27+
"""Process a single endpoint and return selected model if successful."""
28+
success, endpoint_config = self.endpoint_manager.get_endpoint_config(
29+
endpoint_name
30+
)
31+
if not success:
32+
return None
33+
34+
# Get models from list_models_cmd
35+
models = []
36+
if "list_models_cmd" in endpoint_config:
37+
try:
38+
import subprocess
39+
result = subprocess.run(
40+
endpoint_config["list_models_cmd"],
41+
shell=True,
42+
capture_output=True,
43+
text=True,
44+
timeout=30
45+
)
46+
if result.returncode == 0 and result.stdout.strip():
47+
models = [line.strip() for line in result.stdout.split('\n') if line.strip()]
48+
except Exception as e:
49+
print(f"Warning: Failed to execute list_models_cmd for {endpoint_name}: {e}")
50+
return None
51+
else:
52+
# Fallback if no list_models_cmd
53+
models = [endpoint_name.replace(":", "-").replace("_", "-")]
54+
55+
if not models:
56+
print(f"Warning: No models found for {endpoint_name}\n")
57+
return None
58+
59+
ep_url = endpoint_config.get("endpoint", "")
60+
ep_desc = endpoint_config.get("description", "") or ep_url
61+
endpoint_info = f"{endpoint_name} -> {ep_url} -> {ep_desc}"
62+
63+
# Import package-level helper so tests can patch code_assistant_manager.tools.select_model
64+
from . import select_model
65+
66+
success, model = select_model(
67+
models, f"Select model from {endpoint_info} (or skip):"
68+
)
69+
if success and model:
70+
return model
71+
else:
72+
print(f"Skipped {endpoint_name}\n")
73+
return None
74+
75+
def _write_continue_config(self, selected_models: List[tuple]) -> Path:
76+
"""Write Continue.dev configuration to ~/.continue/config.yaml."""
77+
# Create config structure
78+
continue_config = {
79+
"name": "Code Assistant Manager Config",
80+
"version": "0.0.1",
81+
"schema": "v1",
82+
"models": []
83+
}
84+
85+
# Create models from selected endpoints
86+
for endpoint_name, model_name in selected_models:
87+
success, endpoint_config = self.endpoint_manager.get_endpoint_config(endpoint_name)
88+
if not success:
89+
continue
90+
91+
# Create model entry
92+
model_entry = {
93+
"name": f"{endpoint_name} - {model_name}",
94+
"provider": "openai",
95+
"model": model_name,
96+
"apiBase": endpoint_config["endpoint"],
97+
"requestOptions": {
98+
"verifySsl": False
99+
}
100+
}
101+
102+
# Handle API key configuration - use actual resolved key
103+
api_key = endpoint_config.get("actual_api_key", "")
104+
if api_key:
105+
model_entry["apiKey"] = api_key
106+
107+
continue_config["models"].append(model_entry)
108+
109+
# Write the config
110+
config_file = Path.home() / ".continue" / "config.yaml"
111+
config_file.parent.mkdir(parents=True, exist_ok=True)
112+
with open(config_file, "w") as f:
113+
yaml.dump(continue_config, f, default_flow_style=False, sort_keys=False)
114+
115+
return config_file
116+
117+
def run(self, args: List[str] = None) -> int:
118+
"""
119+
Configure and launch the Continue.dev CLI.
120+
121+
Args:
122+
args: List of arguments to pass to the Continue CLI
123+
124+
Returns:
125+
Exit code of the Continue CLI process
126+
"""
127+
args = args or []
128+
129+
# Load environment variables first
130+
self._load_environment()
131+
132+
# Check if Continue.dev is installed
133+
if not self._ensure_tool_installed(
134+
self.command_name, self.tool_key, self.install_description
135+
):
136+
return 1
137+
138+
# Get filtered endpoints that support continue
139+
filtered_endpoints = self._get_filtered_endpoints()
140+
141+
if not filtered_endpoints:
142+
print("Warning: No endpoints configured for continue client.")
143+
print("Continue.dev will use its default configuration.")
144+
else:
145+
print("\nConfiguring Continue.dev with models from all endpoints...\n")
146+
147+
# Process each endpoint to collect selected models
148+
selected_models: List[tuple] = [] # (endpoint_name, model_name)
149+
for endpoint_name in filtered_endpoints:
150+
model = self._process_endpoint(endpoint_name)
151+
if model:
152+
selected_models.append((endpoint_name, model))
153+
154+
if not selected_models:
155+
print("No models selected")
156+
return 1
157+
158+
print(f"Total models selected: {len(selected_models)}\n")
159+
160+
# Generate Continue.dev config file
161+
config_file = self._write_continue_config(selected_models)
162+
print(f"Continue.dev config written to {config_file}")
163+
164+
# Set up environment for Continue
165+
env = os.environ.copy()
166+
# Set TLS environment for Node.js
167+
self._set_node_tls_env(env)
168+
169+
# Execute the Continue CLI with the configured environment
170+
command = [self.command_name, *args]
171+
return self._run_tool_with_env(command, env, self.command_name, interactive=True)

tests/test_mcp_tool.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def sample_new_config():
6363
"neovate",
6464
"crush",
6565
"cursor-agent",
66+
"opencode",
6667
],
6768
},
6869
"servers": {
@@ -113,6 +114,7 @@ def test_manager_initialization(mock_config):
113114
"neovate",
114115
"crush",
115116
"cursor-agent",
117+
"opencode",
116118
]
117119
assert len(manager.clients) == len(expected_clients)
118120

@@ -164,6 +166,7 @@ def test_get_available_tools(sample_new_config):
164166
"neovate",
165167
"crush",
166168
"cursor-agent",
169+
"opencode",
167170
]
168171
assert set(tools) == set(expected_tools)
169172
assert tools == sorted(expected_tools) # Should be sorted

0 commit comments

Comments
 (0)