auto-coder 0.1.334__py3-none-any.whl → 0.1.335__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (43) hide show
  1. {auto_coder-0.1.334.dist-info → auto_coder-0.1.335.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.334.dist-info → auto_coder-0.1.335.dist-info}/RECORD +43 -26
  3. autocoder/agent/agentic_edit.py +833 -0
  4. autocoder/agent/agentic_edit_tools/__init__.py +28 -0
  5. autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +32 -0
  6. autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +29 -0
  7. autocoder/agent/agentic_edit_tools/base_tool_resolver.py +29 -0
  8. autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +84 -0
  9. autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +75 -0
  10. autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +62 -0
  11. autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +30 -0
  12. autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +36 -0
  13. autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +95 -0
  14. autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +70 -0
  15. autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +55 -0
  16. autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +98 -0
  17. autocoder/agent/agentic_edit_types.py +124 -0
  18. autocoder/auto_coder.py +5 -1
  19. autocoder/auto_coder_runner.py +5 -1
  20. autocoder/chat_auto_coder_lang.py +18 -2
  21. autocoder/commands/tools.py +5 -1
  22. autocoder/common/__init__.py +1 -0
  23. autocoder/common/auto_coder_lang.py +40 -8
  24. autocoder/common/code_auto_generate_diff.py +1 -1
  25. autocoder/common/code_auto_generate_editblock.py +1 -1
  26. autocoder/common/code_auto_generate_strict_diff.py +1 -1
  27. autocoder/common/mcp_hub.py +185 -2
  28. autocoder/common/mcp_server.py +243 -306
  29. autocoder/common/mcp_server_install.py +269 -0
  30. autocoder/common/mcp_server_types.py +169 -0
  31. autocoder/common/stream_out_type.py +3 -0
  32. autocoder/common/v2/code_auto_generate.py +1 -1
  33. autocoder/common/v2/code_auto_generate_diff.py +1 -1
  34. autocoder/common/v2/code_auto_generate_editblock.py +1 -1
  35. autocoder/common/v2/code_auto_generate_strict_diff.py +1 -1
  36. autocoder/common/v2/code_editblock_manager.py +151 -178
  37. autocoder/compilers/provided_compiler.py +3 -2
  38. autocoder/shadows/shadow_manager.py +1 -1
  39. autocoder/version.py +1 -1
  40. {auto_coder-0.1.334.dist-info → auto_coder-0.1.335.dist-info}/LICENSE +0 -0
  41. {auto_coder-0.1.334.dist-info → auto_coder-0.1.335.dist-info}/WHEEL +0 -0
  42. {auto_coder-0.1.334.dist-info → auto_coder-0.1.335.dist-info}/entry_points.txt +0 -0
  43. {auto_coder-0.1.334.dist-info → auto_coder-0.1.335.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,269 @@
1
+ import json
2
+ import sys
3
+ import os
4
+ import time
5
+ from typing import Dict, Any, Tuple, List
6
+ from loguru import logger
7
+ import asyncio
8
+
9
+ from autocoder.common.mcp_hub import McpHub, MCP_BUILD_IN_SERVERS
10
+ from autocoder.common.mcp_server_types import McpResponse, McpInstallRequest, InstallResult, ServerConfig, McpExternalServer
11
+ from autocoder.chat_auto_coder_lang import get_message_with_format
12
+
13
+
14
+ class McpServerInstaller:
15
+ """Class responsible for installing and configuring MCP servers"""
16
+
17
+ def __init__(self):
18
+ pass
19
+
20
+ def install_python_package(self, package_name: str) -> None:
21
+ """Install a Python package using pip"""
22
+ try:
23
+ import importlib
24
+ importlib.import_module(package_name.replace("-", "_"))
25
+ except ImportError:
26
+ import subprocess
27
+ try:
28
+ subprocess.run([sys.executable, "-m", "pip", "install", package_name], check=True)
29
+ except subprocess.CalledProcessError:
30
+ print(f"\n\033[93mFailed to automatically install {package_name}. Please manually install it using:\n")
31
+ print(f" pip install {package_name}\n")
32
+ print(f"We have already updated the server configuration in ~/.autocoder/mcp/settings.json.\n")
33
+ print(f"After installation, you can restart the auto-coder.chat using the server.\033[0m\n")
34
+
35
+ def install_node_package(self, package_name: str) -> None:
36
+ """Install a Node.js package using npm"""
37
+ import subprocess
38
+ try:
39
+ subprocess.run(["npx", package_name, "--version"], check=True)
40
+ except:
41
+ try:
42
+ subprocess.run(["npm", "install", "-y", "-g", package_name], check=True)
43
+ except subprocess.CalledProcessError:
44
+ print(f"\n\033[93mFailed to automatically install {package_name}. Please manually install it using:\n")
45
+ print(f" npm install -g {package_name}\n")
46
+ print(f"We have already updated the server configuration in ~/.autocoder/mcp/settings.json.\n")
47
+ print(f"After installation, you can restart the auto-coder.chat using the server.\033[0m\n")
48
+
49
+ def deep_merge_dicts(self, dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]:
50
+ """
51
+ 深度合并两个字典,包括嵌套的字典
52
+ dict1是基础字典,dict2是优先字典(当有冲突时保留dict2的值)
53
+ """
54
+ result = dict1.copy()
55
+ for key, value in dict2.items():
56
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
57
+ # 如果两个字典都包含相同的键,并且两个值都是字典,则递归合并
58
+ result[key] = self.deep_merge_dicts(result[key], value)
59
+ else:
60
+ # 否则,使用dict2的值覆盖或添加到结果
61
+ result[key] = value
62
+ return result
63
+
64
+ def get_mcp_external_servers(self) -> List[McpExternalServer]:
65
+ """Get external MCP servers list from GitHub"""
66
+ cache_dir = os.path.join(".auto-coder", "tmp")
67
+ os.makedirs(cache_dir, exist_ok=True)
68
+ cache_file = os.path.join(cache_dir, "mcp_external_servers.json")
69
+
70
+ # Check cache first
71
+ if os.path.exists(cache_file):
72
+ cache_time = os.path.getmtime(cache_file)
73
+ if time.time() - cache_time < 3600: # 1 hour cache
74
+ with open(cache_file, "r", encoding="utf-8") as f:
75
+ raw_data = json.load(f)
76
+ return [McpExternalServer(**item) for item in raw_data]
77
+
78
+ # Fetch from GitHub
79
+ url = "https://raw.githubusercontent.com/michaellatman/mcp-get/refs/heads/main/packages/package-list.json"
80
+ try:
81
+ import requests
82
+ response = requests.get(url)
83
+ if response.status_code == 200:
84
+ raw_data = response.json()
85
+ with open(cache_file, "w", encoding="utf-8") as f:
86
+ json.dump(raw_data, f)
87
+ return [McpExternalServer(**item) for item in raw_data]
88
+ return []
89
+ except Exception as e:
90
+ logger.error(f"Failed to fetch external MCP servers: {e}")
91
+ return []
92
+
93
+ def parse_command_line_args(self, server_name_or_config: str) -> Tuple[str, Dict[str, Any]]:
94
+ """Parse command-line style arguments into name and config"""
95
+ name = ""
96
+ config = {}
97
+ args = server_name_or_config.strip().split()
98
+ i = 0
99
+ while i < len(args):
100
+ if args[i] == "--name" and i + 1 < len(args):
101
+ name = args[i + 1]
102
+ i += 2
103
+ elif args[i] == "--command" and i + 1 < len(args):
104
+ config["command"] = args[i + 1]
105
+ i += 2
106
+ elif args[i] == "--args":
107
+ config["args"] = []
108
+ i += 1
109
+ while i < len(args) and not args[i].startswith("--"):
110
+ config["args"].append(args[i])
111
+ i += 1
112
+ elif args[i] == "--env":
113
+ config["env"] = {}
114
+ i += 1
115
+ while i < len(args) and not args[i].startswith("--"):
116
+ if "=" in args[i]:
117
+ key, value = args[i].split("=", 1)
118
+ config["env"][key] = value
119
+ i += 1
120
+ else:
121
+ i += 1
122
+
123
+ template_config = {}
124
+
125
+ if name in MCP_BUILD_IN_SERVERS:
126
+ template_config = MCP_BUILD_IN_SERVERS[name]
127
+ else:
128
+ # 查找外部server
129
+ external_servers = self.get_mcp_external_servers()
130
+ for s in external_servers:
131
+ if s.name == name:
132
+ if s.runtime == "python":
133
+ # self.install_python_package(name)
134
+ template_config = {
135
+ "command": "python",
136
+ "args": [
137
+ "-m", name.replace("-", "_")
138
+ ],
139
+ }
140
+ elif s.runtime == "node":
141
+ # self.install_node_package(name)
142
+ template_config = {
143
+ "command": "npx",
144
+ "args": [
145
+ "-y",
146
+ "-g",
147
+ name
148
+ ]
149
+ }
150
+ break
151
+
152
+ # 深度合并两个配置,以用户配置为主
153
+ config = self.deep_merge_dicts(template_config, config)
154
+
155
+ if not config.get("args") and (name.startswith("@") or config.get("command") in ["npx", "npm"]):
156
+ config["args"] = ["-y", "-g", name]
157
+
158
+ ## 如果有模板,则无需再次安装,处理模板的时候会自动安装
159
+ # if not template_config:
160
+ # # Install package if needed
161
+ # if name.startswith("@") or config.get("command") in ["npx", "npm"]:
162
+ # self.install_node_package(name)
163
+ # else:
164
+ # self.install_python_package(name)
165
+
166
+ return name, config
167
+
168
+ def parse_json_config(self, server_name_or_config: str) -> Tuple[str, Dict[str, Any]]:
169
+ """Parse JSON configuration into name and config"""
170
+ raw_config = json.loads(server_name_or_config)
171
+ # 用户给了一个完整的配置
172
+ if "mcpServers" in raw_config:
173
+ raw_config = raw_config["mcpServers"]
174
+
175
+ # 取第一个server 配置
176
+ config = list(raw_config.values())[0]
177
+ name = list(raw_config.keys())[0]
178
+ # if name.startswith("@") or config["command"] in ["npx", "npm"]:
179
+ # for item in config["args"]:
180
+ # if name in item:
181
+ # self.install_node_package(item)
182
+ # else:
183
+ # self.install_python_package(name)
184
+
185
+ return name, config
186
+
187
+ def process_market_install_item(self, market_item) -> Tuple[str, Dict[str, Any]]:
188
+ """Process a MarketplaceMCPServerItem into name and config"""
189
+ name = market_item.name
190
+ config = {
191
+ "command": market_item.command,
192
+ "args": market_item.args,
193
+ "env": market_item.env
194
+ }
195
+
196
+ # Install package if needed
197
+ # if name.startswith("@") or market_item.command in ["npx", "npm"]:
198
+ # for item in market_item.args:
199
+ # if name in item:
200
+ # self.install_node_package(item)
201
+ # elif market_item.command not in ["python", "node"]:
202
+ # self.install_python_package(name)
203
+
204
+ return name, config
205
+
206
+ async def install_server(self, request: McpInstallRequest, hub: McpHub) -> McpResponse:
207
+ """Install an MCP server with module dependency check"""
208
+ name = ""
209
+ config = {}
210
+ try:
211
+ # Check if market_install_item is provided
212
+ logger.info(f"Installing MCP server: {request.market_install_item}")
213
+ if request.market_install_item:
214
+ name, config = self.process_market_install_item(request.market_install_item)
215
+ display_result = request.market_install_item.name
216
+ else:
217
+ server_name_or_config = request.server_name_or_config
218
+ display_result = server_name_or_config
219
+
220
+ # Try different parsing methods
221
+ if server_name_or_config.strip().startswith("--"):
222
+ # Command-line style arguments
223
+ name, config = self.parse_command_line_args(server_name_or_config)
224
+ else:
225
+ try:
226
+ # Try parsing as JSON
227
+ name, config = self.parse_json_config(server_name_or_config)
228
+ except json.JSONDecodeError:
229
+ logger.error(f"Failed to parse JSON config: {server_name_or_config}")
230
+ pass
231
+
232
+ if not name:
233
+ raise ValueError(
234
+ "MCP server name is not available in MCP_BUILD_IN_SERVERS or external servers")
235
+
236
+ logger.info(f"Installing MCP server: {name} with config: {config}")
237
+ if not config:
238
+ raise ValueError(f"MCP server {name} config is not available")
239
+
240
+ is_success = await hub.add_server_config(name, config)
241
+ if is_success:
242
+ return McpResponse(
243
+ result=get_message_with_format("mcp_install_success", result=display_result),
244
+ raw_result=InstallResult(
245
+ success=True,
246
+ server_name=name,
247
+ config=ServerConfig(**config)
248
+ )
249
+ )
250
+ else:
251
+ return McpResponse(
252
+ result="",
253
+ error=get_message_with_format("mcp_install_error", error="Failed to establish connection"),
254
+ raw_result=InstallResult(
255
+ success=False,
256
+ server_name=name,
257
+ config=ServerConfig(**config),
258
+ error="Failed to establish connection"
259
+ )
260
+ )
261
+ except Exception as e:
262
+ return McpResponse(
263
+ result="",
264
+ error=get_message_with_format("mcp_install_error", error=str(e)),
265
+ raw_result=InstallResult(
266
+ success=False,
267
+ error=str(e)
268
+ )
269
+ )
@@ -0,0 +1,169 @@
1
+ from typing import List, Dict, Any, Optional, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class MarketplaceMCPServerItem(BaseModel):
6
+ """Represents an MCP server item"""
7
+
8
+ name: str
9
+ description: Optional[str] = ""
10
+ mcp_type: str = "command" # command/sse
11
+ command: str = "" # npm/uvx/python/node/...
12
+ args: List[str] = Field(default_factory=list)
13
+ env: Dict[str, str] = Field(default_factory=dict)
14
+ url: str = "" # sse url
15
+
16
+ class McpRequest(BaseModel):
17
+ query: str
18
+ model: Optional[str] = None
19
+ product_mode: Optional[str] = None
20
+
21
+
22
+ class McpInstallRequest(BaseModel):
23
+ server_name_or_config: Optional[str] = None
24
+ market_install_item:Optional[MarketplaceMCPServerItem] = None
25
+
26
+
27
+ class McpRemoveRequest(BaseModel):
28
+ server_name: str
29
+
30
+
31
+ class McpListRequest(BaseModel):
32
+ """Request to list all builtin MCP servers"""
33
+ path:str="/list"
34
+
35
+
36
+ class McpListRunningRequest(BaseModel):
37
+ """Request to list all running MCP servers"""
38
+ path:str="/list/running"
39
+
40
+
41
+ # Pydantic models for raw_result
42
+ class ServerConfig(BaseModel):
43
+ command: str
44
+ args: List[str] = Field(default_factory=list)
45
+ env: Dict[str, str] = Field(default_factory=dict)
46
+
47
+
48
+ class InstallResult(BaseModel):
49
+ success: bool
50
+ server_name: Optional[str] = None
51
+ config: Optional[ServerConfig] = None
52
+ error: Optional[str] = None
53
+
54
+
55
+ class RemoveResult(BaseModel):
56
+ success: bool
57
+ server_name: Optional[str] = None
58
+ error: Optional[str] = None
59
+
60
+
61
+ class ExternalServerInfo(BaseModel):
62
+ name: str
63
+ description: str
64
+
65
+
66
+
67
+
68
+
69
+ class ListResult(BaseModel):
70
+ builtin_servers: List[MarketplaceMCPServerItem] = Field(
71
+ default_factory=list)
72
+ external_servers: List[MarketplaceMCPServerItem] = Field(
73
+ default_factory=list)
74
+ marketplace_items: List[MarketplaceMCPServerItem] = Field(
75
+ default_factory=list)
76
+ error: Optional[str] = None
77
+
78
+
79
+ class ServerInfo(BaseModel):
80
+ name: str
81
+
82
+
83
+ class ListRunningResult(BaseModel):
84
+ servers: List[ServerInfo] = Field(default_factory=list)
85
+ error: Optional[str] = None
86
+
87
+
88
+ class RefreshResult(BaseModel):
89
+ success: bool
90
+ name: Optional[str] = None
91
+ error: Optional[str] = None
92
+
93
+
94
+ class QueryResult(BaseModel):
95
+ success: bool
96
+ results: Optional[List[Any]] = None
97
+ error: Optional[str] = None
98
+
99
+
100
+ class ErrorResult(BaseModel):
101
+ success: bool = False
102
+ error: str
103
+
104
+ class StringResult(BaseModel):
105
+ success: bool = True
106
+ result: str
107
+
108
+ class McpRefreshRequest(BaseModel):
109
+ """Request to refresh MCP server connections"""
110
+ name: Optional[str] = None
111
+
112
+
113
+ class McpServerInfoRequest(BaseModel):
114
+ """Request to get MCP server info"""
115
+ model: Optional[str] = None
116
+ product_mode: Optional[str] = None
117
+
118
+
119
+ class McpExternalServer(BaseModel):
120
+ """Represents an external MCP server configuration"""
121
+ name: str
122
+ description: str
123
+ vendor: str
124
+ sourceUrl: str
125
+ homepage: str
126
+ license: str
127
+ runtime: str
128
+
129
+
130
+ class MarketplaceAddRequest(BaseModel):
131
+ """Request to add a new marketplace item"""
132
+ name: str
133
+ description: Optional[str] = ""
134
+ mcp_type: str = "command" # command/sse
135
+ command: Optional[str] = "" # npm/uvx/python/node/...
136
+ args: Optional[List[str]] = Field(default_factory=list)
137
+ env: Optional[Dict[str, str]] = Field(default_factory=dict)
138
+ url: Optional[str] = "" # sse url
139
+
140
+
141
+ class MarketplaceAddResult(BaseModel):
142
+ """Result for marketplace add operation"""
143
+ success: bool
144
+ name: str
145
+ error: Optional[str] = None
146
+
147
+
148
+ class MarketplaceUpdateRequest(BaseModel):
149
+ """Request to update an existing marketplace item"""
150
+ name: str
151
+ description: Optional[str] = ""
152
+ mcp_type: str = "command" # command/sse
153
+ command: Optional[str] = "" # npm/uvx/python/node/...
154
+ args: Optional[List[str]] = Field(default_factory=list)
155
+ env: Optional[Dict[str, str]] = Field(default_factory=dict)
156
+ url: Optional[str] = "" # sse url
157
+
158
+
159
+ class MarketplaceUpdateResult(BaseModel):
160
+ """Result for marketplace update operation"""
161
+ success: bool
162
+ name: str
163
+ error: Optional[str] = None
164
+
165
+ class McpResponse(BaseModel):
166
+ result: str
167
+ error: Optional[str] = None
168
+ raw_result: Optional[Union[InstallResult, MarketplaceAddResult, MarketplaceUpdateResult, RemoveResult,
169
+ ListResult, ListRunningResult, RefreshResult, QueryResult, ErrorResult,StringResult]] = None
@@ -24,5 +24,8 @@ class UnmergedBlocksStreamOutType(Enum):
24
24
  class CompileStreamOutType(Enum):
25
25
  COMPILE = "compile"
26
26
 
27
+ class ContextMissingCheckStreamOutType(Enum):
28
+ CONTEXT_MISSING_CHECK = "context_missing_check"
29
+
27
30
  class IndexStreamOutType(Enum):
28
31
  INDEX_BUILD = "index_build"
@@ -65,7 +65,7 @@ class CodeAutoGenerate:
65
65
  # 获取包上下文信息
66
66
  package_context = ""
67
67
 
68
- if self.args.enable_active_context:
68
+ if self.args.enable_active_context and self.args.enable_active_context_in_generate:
69
69
  # 获取活动上下文信息
70
70
  result = active_context_manager.load_active_contexts_for_files(
71
71
  [source.module_name for source in source_code_list.sources]
@@ -227,7 +227,7 @@ class CodeAutoGenerateDiff:
227
227
  # 获取包上下文信息
228
228
  package_context = ""
229
229
 
230
- if self.args.enable_active_context:
230
+ if self.args.enable_active_context and self.args.enable_active_context_in_generate:
231
231
  # 获取活动上下文信息
232
232
  result = active_context_manager.load_active_contexts_for_files(
233
233
  [source.module_name for source in source_code_list.sources]
@@ -245,7 +245,7 @@ class CodeAutoGenerateEditBlock:
245
245
  # 获取包上下文信息
246
246
  package_context = ""
247
247
 
248
- if self.args.enable_active_context:
248
+ if self.args.enable_active_context and self.args.enable_active_context_in_generate:
249
249
  # 获取活动上下文信息
250
250
  result = active_context_manager.load_active_contexts_for_files(
251
251
  [source.module_name for source in source_code_list.sources]
@@ -299,7 +299,7 @@ class CodeAutoGenerateStrictDiff:
299
299
  # 获取包上下文信息
300
300
  package_context = ""
301
301
 
302
- if self.args.enable_active_context:
302
+ if self.args.enable_active_context and self.args.enable_active_context_in_generate:
303
303
  # 初始化活动上下文管理器
304
304
  active_context_manager = ActiveContextManager(self.llm, self.args.source_dir)
305
305
  # 获取活动上下文信息