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

@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import re
3
3
  import glob
4
- from typing import Dict, Any, Optional
4
+ from typing import Dict, Any, Optional, List, Union
5
5
  from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
6
6
  from autocoder.common.v2.agent.agentic_edit_types import SearchFilesTool, ToolResult # Import ToolResult from types
7
7
  from loguru import logger
@@ -20,14 +20,54 @@ class SearchFilesToolResolver(BaseToolResolver):
20
20
  self.tool: SearchFilesTool = tool
21
21
  self.shadow_manager = self.agent.shadow_manager if self.agent else None
22
22
 
23
- def resolve(self) -> ToolResult:
24
- search_path_str = self.tool.path
25
- regex_pattern = self.tool.regex
26
- file_pattern = self.tool.file_pattern or "*"
27
- source_dir = self.args.source_dir or "."
28
- absolute_source_dir = os.path.abspath(source_dir)
29
- absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
23
+ def search_in_dir(self, base_dir: str, regex_pattern: str, file_pattern: str, source_dir: str, is_shadow: bool = False, compiled_regex: Optional[re.Pattern] = None) -> List[Dict[str, Any]]:
24
+ """Helper function to search in a directory"""
25
+ search_results = []
26
+ search_glob_pattern = os.path.join(base_dir, "**", file_pattern)
27
+
28
+ logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{base_dir}' (shadow: {is_shadow}) with ignore rules applied.")
29
+
30
+ if compiled_regex is None:
31
+ compiled_regex = re.compile(regex_pattern)
32
+
33
+ for filepath in glob.glob(search_glob_pattern, recursive=True):
34
+ abs_path = os.path.abspath(filepath)
35
+ if should_ignore(abs_path):
36
+ continue
30
37
 
38
+ if os.path.isfile(filepath):
39
+ try:
40
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
41
+ lines = f.readlines()
42
+ for i, line in enumerate(lines):
43
+ if compiled_regex.search(line):
44
+ context_start = max(0, i - 2)
45
+ context_end = min(len(lines), i + 3)
46
+ context = "".join([f"{j+1}: {lines[j]}" for j in range(context_start, context_end)])
47
+
48
+ if is_shadow and self.shadow_manager:
49
+ try:
50
+ abs_project_path = self.shadow_manager.from_shadow_path(filepath)
51
+ relative_path = os.path.relpath(abs_project_path, source_dir)
52
+ except Exception:
53
+ relative_path = os.path.relpath(filepath, source_dir)
54
+ else:
55
+ relative_path = os.path.relpath(filepath, source_dir)
56
+
57
+ search_results.append({
58
+ "path": relative_path,
59
+ "line_number": i + 1,
60
+ "match_line": line.strip(),
61
+ "context": context.strip()
62
+ })
63
+ except Exception as e:
64
+ logger.warning(f"Could not read or process file {filepath}: {e}")
65
+ continue
66
+
67
+ return search_results
68
+
69
+ def search_files_with_shadow(self, search_path_str: str, regex_pattern: str, file_pattern: str, source_dir: str, absolute_source_dir: str, absolute_search_path: str) -> Union[ToolResult, List[Dict[str, Any]]]:
70
+ """Search files using shadow manager for path translation"""
31
71
  # Security check
32
72
  if not absolute_search_path.startswith(absolute_source_dir):
33
73
  return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
@@ -54,58 +94,15 @@ class SearchFilesToolResolver(BaseToolResolver):
54
94
  try:
55
95
  compiled_regex = re.compile(regex_pattern)
56
96
 
57
- # Helper function to search in a directory
58
- def search_in_dir(base_dir, is_shadow=False):
59
- search_results = []
60
- search_glob_pattern = os.path.join(base_dir, "**", file_pattern)
61
-
62
- logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{base_dir}' (shadow: {is_shadow}) with ignore rules applied.")
63
-
64
- for filepath in glob.glob(search_glob_pattern, recursive=True):
65
- abs_path = os.path.abspath(filepath)
66
- if should_ignore(abs_path):
67
- continue
68
-
69
- if os.path.isfile(filepath):
70
- try:
71
- with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
72
- lines = f.readlines()
73
- for i, line in enumerate(lines):
74
- if compiled_regex.search(line):
75
- context_start = max(0, i - 2)
76
- context_end = min(len(lines), i + 3)
77
- context = "".join([f"{j+1}: {lines[j]}" for j in range(context_start, context_end)])
78
-
79
- if is_shadow and self.shadow_manager:
80
- try:
81
- abs_project_path = self.shadow_manager.from_shadow_path(filepath)
82
- relative_path = os.path.relpath(abs_project_path, source_dir)
83
- except Exception:
84
- relative_path = os.path.relpath(filepath, source_dir)
85
- else:
86
- relative_path = os.path.relpath(filepath, source_dir)
87
-
88
- search_results.append({
89
- "path": relative_path,
90
- "line_number": i + 1,
91
- "match_line": line.strip(),
92
- "context": context.strip()
93
- })
94
- except Exception as e:
95
- logger.warning(f"Could not read or process file {filepath}: {e}")
96
- continue
97
-
98
- return search_results
99
-
100
97
  # Search in both directories and merge results
101
98
  shadow_results = []
102
99
  source_results = []
103
100
 
104
101
  if shadow_exists:
105
- shadow_results = search_in_dir(shadow_dir_path, is_shadow=True)
102
+ shadow_results = self.search_in_dir(shadow_dir_path, regex_pattern, file_pattern, source_dir, is_shadow=True, compiled_regex=compiled_regex)
106
103
 
107
104
  if os.path.exists(absolute_search_path) and os.path.isdir(absolute_search_path):
108
- source_results = search_in_dir(absolute_search_path, is_shadow=False)
105
+ source_results = self.search_in_dir(absolute_search_path, regex_pattern, file_pattern, source_dir, is_shadow=False, compiled_regex=compiled_regex)
109
106
 
110
107
  # Merge results, prioritizing shadow results
111
108
  # Create a dictionary for quick lookup
@@ -122,9 +119,34 @@ class SearchFilesToolResolver(BaseToolResolver):
122
119
  # Convert back to list
123
120
  merged_results = list(results_dict.values())
124
121
 
125
- message = f"Search completed. Found {len(merged_results)} matches."
126
- logger.info(message)
127
- return ToolResult(success=True, message=message, content=merged_results)
122
+ return merged_results
123
+
124
+ except re.error as e:
125
+ logger.error(f"Invalid regex pattern '{regex_pattern}': {e}")
126
+ return ToolResult(success=False, message=f"Invalid regex pattern: {e}")
127
+ except Exception as e:
128
+ logger.error(f"Error during file search: {str(e)}")
129
+ return ToolResult(success=False, message=f"An unexpected error occurred during search: {str(e)}")
130
+
131
+ def search_files_normal(self, search_path_str: str, regex_pattern: str, file_pattern: str, source_dir: str, absolute_source_dir: str, absolute_search_path: str) -> Union[ToolResult, List[Dict[str, Any]]]:
132
+ """Search files directly without using shadow manager"""
133
+ # Security check
134
+ if not absolute_search_path.startswith(absolute_source_dir):
135
+ return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
136
+
137
+ # Validate that the directory exists
138
+ if not os.path.exists(absolute_search_path):
139
+ return ToolResult(success=False, message=f"Error: Search path not found: {search_path_str}")
140
+ if not os.path.isdir(absolute_search_path):
141
+ return ToolResult(success=False, message=f"Error: Search path is not a directory: {search_path_str}")
142
+
143
+ try:
144
+ compiled_regex = re.compile(regex_pattern)
145
+
146
+ # Search in the directory
147
+ search_results = self.search_in_dir(absolute_search_path, regex_pattern, file_pattern, source_dir, is_shadow=False, compiled_regex=compiled_regex)
148
+
149
+ return search_results
128
150
 
129
151
  except re.error as e:
130
152
  logger.error(f"Invalid regex pattern '{regex_pattern}': {e}")
@@ -132,3 +154,26 @@ class SearchFilesToolResolver(BaseToolResolver):
132
154
  except Exception as e:
133
155
  logger.error(f"Error during file search: {str(e)}")
134
156
  return ToolResult(success=False, message=f"An unexpected error occurred during search: {str(e)}")
157
+
158
+ def resolve(self) -> ToolResult:
159
+ """Resolve the search files tool by calling the appropriate implementation"""
160
+ search_path_str = self.tool.path
161
+ regex_pattern = self.tool.regex
162
+ file_pattern = self.tool.file_pattern or "*"
163
+ source_dir = self.args.source_dir or "."
164
+ absolute_source_dir = os.path.abspath(source_dir)
165
+ absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
166
+
167
+ # Choose the appropriate implementation based on whether shadow_manager is available
168
+ if self.shadow_manager:
169
+ result = self.search_files_with_shadow(search_path_str, regex_pattern, file_pattern, source_dir, absolute_source_dir, absolute_search_path)
170
+ else:
171
+ result = self.search_files_normal(search_path_str, regex_pattern, file_pattern, source_dir, absolute_source_dir, absolute_search_path)
172
+
173
+ # Handle the case where the implementation returns a list instead of a ToolResult
174
+ if isinstance(result, list):
175
+ message = f"Search completed. Found {len(result)} matches."
176
+ logger.info(message)
177
+ return ToolResult(success=True, message=message, content=result)
178
+ else:
179
+ return result
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from typing import Dict, Any, Optional
3
- from autocoder.common.v2.agent.agentic_edit_types import WriteToFileTool, ToolResult # Import ToolResult from types
3
+ from autocoder.common.v2.agent.agentic_edit_types import WriteToFileTool, ToolResult
4
4
  from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
5
  from loguru import logger
6
6
  from autocoder.common import AutoCoderArgs
@@ -44,82 +44,77 @@ class WriteToFileToolResolver(BaseToolResolver):
44
44
 
45
45
  return "\n".join(formatted_issues)
46
46
 
47
- def resolve(self) -> ToolResult:
48
- file_path = self.tool.path
49
- content = self.tool.content
50
- source_dir = self.args.source_dir or "."
51
- abs_project_dir = os.path.abspath(source_dir)
52
- abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
53
-
54
- # Security check: ensure the path is within the source directory
55
- if not abs_file_path.startswith(abs_project_dir):
56
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to write file outside the project directory: {file_path}")
57
-
47
+ def write_file_with_shadow(self, file_path: str, content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
48
+ """Write file using shadow manager for path translation"""
58
49
  try:
59
- if self.shadow_manager:
60
- shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
61
- # Ensure shadow directory exists
62
- os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
63
- with open(shadow_path, 'w', encoding='utf-8') as f:
64
- f.write(content)
65
- logger.info(f"[Shadow] Successfully wrote shadow file: {shadow_path}")
50
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
51
+ # Ensure shadow directory exists
52
+ os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
53
+ with open(shadow_path, 'w', encoding='utf-8') as f:
54
+ f.write(content)
55
+ logger.info(f"[Shadow] Successfully wrote shadow file: {shadow_path}")
66
56
 
67
- # 回调AgenticEdit,记录变更
68
- if self.agent:
69
- rel_path = os.path.relpath(abs_file_path, abs_project_dir)
70
- self.agent.record_file_change(rel_path, "added", diff=None, content=content)
71
-
72
- # 新增:执行代码质量检查
73
- lint_results = None
74
- lint_message = ""
75
- formatted_issues = ""
76
- has_lint_issues = False
77
-
78
- # 检查是否启用了Lint功能
79
- enable_lint = self.args.enable_auto_fix_lint
80
-
81
- if enable_lint:
82
- try:
83
- if self.shadow_linter and self.shadow_manager:
84
- # 对新创建的文件进行 lint 检查
85
- shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
86
- lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
87
-
88
- if lint_results and lint_results.issues:
89
- has_lint_issues = True
90
- # 格式化 lint 问题
91
- formatted_issues = self._format_lint_issues(lint_results)
92
- lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
93
- else:
94
- lint_message = "\n\n代码质量检查通过,未发现问题。"
95
- except Exception as e:
96
- logger.error(f"Lint 检查失败: {str(e)}")
97
- lint_message = "\n\n尝试进行代码质量检查时出错。"
98
- else:
99
- logger.info("代码质量检查已禁用")
100
-
101
- # 构建包含 lint 结果的返回消息
102
- message = f"Successfully wrote to file (shadow): {file_path}"
103
-
104
- # 将 lint 消息添加到结果中,如果启用了Lint
105
- if enable_lint:
106
- message += lint_message
107
-
108
- # 附加 lint 结果到返回内容
109
- result_content = {
110
- "content": content,
111
- }
112
-
113
- # 只有在启用Lint时才添加Lint结果
114
- if enable_lint:
115
- result_content["lint_results"] = {
116
- "has_issues": has_lint_issues,
117
- "issues": formatted_issues if has_lint_issues else None
118
- }
119
-
120
- return ToolResult(success=True, message=message, content=result_content)
57
+ # 回调AgenticEdit,记录变更
58
+ if self.agent:
59
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
60
+ self.agent.record_file_change(rel_path, "added", diff=None, content=content)
61
+
62
+ # 新增:执行代码质量检查
63
+ lint_results = None
64
+ lint_message = ""
65
+ formatted_issues = ""
66
+ has_lint_issues = False
67
+
68
+ # 检查是否启用了Lint功能
69
+ enable_lint = self.args.enable_auto_fix_lint
70
+
71
+ if enable_lint:
72
+ try:
73
+ if self.shadow_linter and self.shadow_manager:
74
+ # 对新创建的文件进行 lint 检查
75
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
76
+ lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
121
77
 
122
- # No shadow manager fallback to original file
78
+ if lint_results and lint_results.issues:
79
+ has_lint_issues = True
80
+ # 格式化 lint 问题
81
+ formatted_issues = self._format_lint_issues(lint_results)
82
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
83
+ else:
84
+ lint_message = "\n\n代码质量检查通过,未发现问题。"
85
+ except Exception as e:
86
+ logger.error(f"Lint 检查失败: {str(e)}")
87
+ lint_message = "\n\n尝试进行代码质量检查时出错。"
88
+ else:
89
+ logger.info("代码质量检查已禁用")
90
+
91
+ # 构建包含 lint 结果的返回消息
92
+ message = f"Successfully wrote to file (shadow): {file_path}"
93
+
94
+ # 将 lint 消息添加到结果中,如果启用了Lint
95
+ if enable_lint:
96
+ message += lint_message
97
+
98
+ # 附加 lint 结果到返回内容
99
+ result_content = {
100
+ "content": content,
101
+ }
102
+
103
+ # 只有在启用Lint时才添加Lint结果
104
+ if enable_lint:
105
+ result_content["lint_results"] = {
106
+ "has_issues": has_lint_issues,
107
+ "issues": formatted_issues if has_lint_issues else None
108
+ }
109
+
110
+ return ToolResult(success=True, message=message, content=result_content)
111
+ except Exception as e:
112
+ logger.error(f"Error writing to shadow file '{file_path}': {str(e)}")
113
+ return ToolResult(success=False, message=f"An error occurred while writing to the shadow file: {str(e)}")
114
+
115
+ def write_file_normal(self, file_path: str, content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
116
+ """Write file directly without using shadow manager"""
117
+ try:
123
118
  os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
124
119
 
125
120
  if self.agent:
@@ -136,7 +131,13 @@ class WriteToFileToolResolver(BaseToolResolver):
136
131
  )
137
132
  }
138
133
  change_group_id = self.args.event_file
139
- self.agent.checkpoint_manager.apply_changes(changes,change_group_id)
134
+
135
+ self.agent.checkpoint_manager.apply_changes_with_conversation(
136
+ changes=changes,
137
+ conversations=self.agent.current_conversations,
138
+ change_group_id=change_group_id,
139
+ metadata={"event_file": self.args.event_file}
140
+ )
140
141
  else:
141
142
  with open(abs_file_path, 'w', encoding='utf-8') as f:
142
143
  f.write(content)
@@ -205,4 +206,22 @@ class WriteToFileToolResolver(BaseToolResolver):
205
206
  return ToolResult(success=True, message=message, content=result_content)
206
207
  except Exception as e:
207
208
  logger.error(f"Error writing to file '{file_path}': {str(e)}")
208
- return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
209
+ return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
210
+
211
+ def resolve(self) -> ToolResult:
212
+ """Resolve the write file tool by calling the appropriate implementation"""
213
+ file_path = self.tool.path
214
+ content = self.tool.content
215
+ source_dir = self.args.source_dir or "."
216
+ abs_project_dir = os.path.abspath(source_dir)
217
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
218
+
219
+ # Security check: ensure the path is within the source directory
220
+ if not abs_file_path.startswith(abs_project_dir):
221
+ return ToolResult(success=False, message=f"Error: Access denied. Attempted to write file outside the project directory: {file_path}")
222
+
223
+ # Choose the appropriate implementation based on whether shadow_manager is available
224
+ if self.shadow_manager:
225
+ return self.write_file_with_shadow(file_path, content, source_dir, abs_project_dir, abs_file_path)
226
+ else:
227
+ return self.write_file_normal(file_path, content, source_dir, abs_project_dir, abs_file_path)
@@ -96,6 +96,10 @@ class ErrorEvent(BaseModel):
96
96
  """Represents an error during the process."""
97
97
  message: str
98
98
 
99
+ class WindowLengthChangeEvent(BaseModel):
100
+ """Represents the token usage in the conversation window."""
101
+ tokens_used: int
102
+
99
103
  # Deprecated: Will be replaced by specific Event types
100
104
  # class PlainTextOutput(BaseModel):
101
105
  # text: str
autocoder/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.364"
1
+ __version__ = "0.1.365"