auto-coder 0.1.335__py3-none-any.whl → 0.1.336__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 (34) hide show
  1. {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/RECORD +34 -16
  3. autocoder/auto_coder.py +34 -17
  4. autocoder/auto_coder_rag.py +18 -9
  5. autocoder/auto_coder_runner.py +45 -4
  6. autocoder/common/__init__.py +1 -0
  7. autocoder/common/v2/agent/__init__.py +0 -0
  8. autocoder/common/v2/agent/agentic_edit.py +1302 -0
  9. autocoder/common/v2/agent/agentic_edit_tools/__init__.py +28 -0
  10. autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +70 -0
  11. autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +35 -0
  12. autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py +33 -0
  13. autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +88 -0
  14. autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +80 -0
  15. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +105 -0
  16. autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +35 -0
  17. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +51 -0
  18. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +144 -0
  19. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +99 -0
  20. autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py +46 -0
  21. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +58 -0
  22. autocoder/common/v2/agent/agentic_edit_types.py +162 -0
  23. autocoder/common/v2/agent/agentic_tool_display.py +184 -0
  24. autocoder/common/v2/code_agentic_editblock_manager.py +812 -0
  25. autocoder/events/event_manager.py +4 -4
  26. autocoder/events/event_types.py +1 -0
  27. autocoder/memory/active_context_manager.py +2 -29
  28. autocoder/models.py +10 -2
  29. autocoder/utils/llms.py +4 -2
  30. autocoder/version.py +1 -1
  31. {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/LICENSE +0 -0
  32. {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/WHEEL +0 -0
  33. {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/entry_points.txt +0 -0
  34. {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,144 @@
1
+ import os
2
+ import re
3
+ from typing import Dict, Any, Optional, List, Tuple
4
+ import typing
5
+ from autocoder.common import AutoCoderArgs
6
+ from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
7
+ from autocoder.common.v2.agent.agentic_edit_types import ReplaceInFileTool, ToolResult # Import ToolResult from types
8
+ from loguru import logger
9
+ if typing.TYPE_CHECKING:
10
+ from autocoder.common.v2.agent.agentic_edit import AgenticEdit
11
+
12
+ class ReplaceInFileToolResolver(BaseToolResolver):
13
+ def __init__(self, agent: Optional['AgenticEdit'], tool: ReplaceInFileTool, args: AutoCoderArgs):
14
+ super().__init__(agent, tool, args)
15
+ self.tool: ReplaceInFileTool = tool # For type hinting
16
+ self.shadow_manager = self.agent.shadow_manager if self.agent else None
17
+
18
+ def parse_diff(self, diff_content: str) -> List[Tuple[str, str]]:
19
+ """
20
+ Parses the diff content into a list of (search_block, replace_block) tuples.
21
+ """
22
+ blocks = []
23
+ lines = diff_content.splitlines(keepends=True)
24
+ i = 0
25
+ n = len(lines)
26
+
27
+ while i < n:
28
+ line = lines[i]
29
+ if line.strip() == "<<<<<<< SEARCH":
30
+ i += 1
31
+ search_lines = []
32
+ # Accumulate search block
33
+ while i < n and lines[i].strip() != "=======":
34
+ search_lines.append(lines[i])
35
+ i += 1
36
+ if i >= n:
37
+ logger.warning("Unterminated SEARCH block found in diff content.")
38
+ break
39
+ i += 1 # skip '======='
40
+ replace_lines = []
41
+ # Accumulate replace block
42
+ while i < n and lines[i].strip() != ">>>>>>> REPLACE":
43
+ replace_lines.append(lines[i])
44
+ i += 1
45
+ if i >= n:
46
+ logger.warning("Unterminated REPLACE block found in diff content.")
47
+ break
48
+ i += 1 # skip '>>>>>>> REPLACE'
49
+
50
+ search_block = ''.join(search_lines)
51
+ replace_block = ''.join(replace_lines)
52
+ blocks.append((search_block, replace_block))
53
+ else:
54
+ i += 1
55
+
56
+ if not blocks and diff_content.strip():
57
+ logger.warning(f"Could not parse any SEARCH/REPLACE blocks from diff: {diff_content}")
58
+ return blocks
59
+
60
+ def resolve(self) -> ToolResult:
61
+ file_path = self.tool.path
62
+ diff_content = self.tool.diff
63
+ source_dir = self.args.source_dir or "."
64
+ abs_project_dir = os.path.abspath(source_dir)
65
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
66
+
67
+ # Security check
68
+ if not abs_file_path.startswith(abs_project_dir):
69
+ return ToolResult(success=False, message=f"Error: Access denied. Attempted to modify file outside the project directory: {file_path}")
70
+
71
+ # Determine target path: shadow file if shadow_manager exists
72
+ target_path = abs_file_path
73
+ if self.shadow_manager:
74
+ target_path = self.shadow_manager.to_shadow_path(abs_file_path)
75
+
76
+ # If shadow file does not exist yet, but original file exists, copy original content into shadow first? No, just treat as normal file.
77
+ # For now, read from shadow if exists, else fallback to original file
78
+ try:
79
+ if os.path.exists(target_path) and os.path.isfile(target_path):
80
+ with open(target_path, 'r', encoding='utf-8', errors='replace') as f:
81
+ original_content = f.read()
82
+ elif self.shadow_manager and os.path.exists(abs_file_path) and os.path.isfile(abs_file_path):
83
+ # If shadow doesn't exist, but original exists, read original content (create shadow implicitly later)
84
+ with open(abs_file_path, 'r', encoding='utf-8', errors='replace') as f:
85
+ original_content = f.read()
86
+ # create parent dirs of shadow if needed
87
+ os.makedirs(os.path.dirname(target_path), exist_ok=True)
88
+ # write original content into shadow file as baseline
89
+ with open(target_path, 'w', encoding='utf-8') as f:
90
+ f.write(original_content)
91
+ logger.info(f"[Shadow] Initialized shadow file from original: {target_path}")
92
+ else:
93
+ return ToolResult(success=False, message=f"Error: File not found at path: {file_path}")
94
+ except Exception as e:
95
+ logger.error(f"Error reading file for replace '{file_path}': {str(e)}")
96
+ return ToolResult(success=False, message=f"An error occurred while reading the file for replacement: {str(e)}")
97
+
98
+ parsed_blocks = self.parse_diff(diff_content)
99
+ if not parsed_blocks:
100
+ return ToolResult(success=False, message="Error: No valid SEARCH/REPLACE blocks found in the provided diff.")
101
+
102
+ current_content = original_content
103
+ applied_count = 0
104
+ errors = []
105
+
106
+ # Apply blocks sequentially
107
+ for i, (search_block, replace_block) in enumerate(parsed_blocks):
108
+ start_index = current_content.find(search_block)
109
+
110
+ if start_index != -1:
111
+ current_content = current_content[:start_index] + replace_block + current_content[start_index + len(search_block):]
112
+ applied_count += 1
113
+ logger.info(f"Applied SEARCH/REPLACE block {i+1} in file {file_path}")
114
+ else:
115
+ error_message = f"SEARCH block {i+1} not found in the current file content. Content to search:\n---\n{search_block}\n---"
116
+ logger.warning(error_message)
117
+ context_start = max(0, original_content.find(search_block[:20]) - 100)
118
+ context_end = min(len(original_content), context_start + 200 + len(search_block[:20]))
119
+ logger.warning(f"Approximate context in file:\n---\n{original_content[context_start:context_end]}\n---")
120
+ errors.append(error_message)
121
+ # continue applying remaining blocks
122
+
123
+ if applied_count == 0 and errors:
124
+ return ToolResult(success=False, message=f"Failed to apply any changes. Errors:\n" + "\n".join(errors))
125
+
126
+ try:
127
+ os.makedirs(os.path.dirname(target_path), exist_ok=True)
128
+ with open(target_path, 'w', encoding='utf-8') as f:
129
+ f.write(current_content)
130
+ logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
131
+
132
+ message = f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}."
133
+ if errors:
134
+ message += "\nWarnings:\n" + "\n".join(errors)
135
+
136
+ # 变更跟踪,回调AgenticEdit
137
+ if self.agent:
138
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
139
+ self.agent.record_file_change(rel_path, "modified", diff=diff_content, content=current_content)
140
+
141
+ return ToolResult(success=True, message=message, content=current_content)
142
+ except Exception as e:
143
+ logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
144
+ return ToolResult(success=False, message=f"An error occurred while writing the modified file: {str(e)}")
@@ -0,0 +1,99 @@
1
+ import os
2
+ import re
3
+ import glob
4
+ from typing import Dict, Any, Optional
5
+ from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
6
+ from autocoder.common.v2.agent.agentic_edit_types import SearchFilesTool, ToolResult # Import ToolResult from types
7
+ from loguru import logger
8
+ from autocoder.common import AutoCoderArgs
9
+ import typing
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from autocoder.common.v2.agent.agentic_edit import AgenticEdit
13
+
14
+
15
+ class SearchFilesToolResolver(BaseToolResolver):
16
+ def __init__(self, agent: Optional['AgenticEdit'], tool: SearchFilesTool, args: AutoCoderArgs):
17
+ super().__init__(agent, tool, args)
18
+ self.tool: SearchFilesTool = tool # For type hinting
19
+ self.shadow_manager = self.agent.shadow_manager if self.agent else None
20
+
21
+ def resolve(self) -> ToolResult:
22
+ search_path_str = self.tool.path
23
+ regex_pattern = self.tool.regex
24
+ file_pattern = self.tool.file_pattern or "*" # Default to all files
25
+ source_dir = self.args.source_dir or "."
26
+ absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
27
+
28
+ # Security check
29
+ if not absolute_search_path.startswith(os.path.abspath(source_dir)):
30
+ return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
31
+
32
+ # Determine search base directory: prefer shadow if exists
33
+ search_base_path = absolute_search_path
34
+ shadow_exists = False
35
+ if self.shadow_manager:
36
+ try:
37
+ shadow_dir_path = self.shadow_manager.to_shadow_path(absolute_search_path)
38
+ if os.path.exists(shadow_dir_path) and os.path.isdir(shadow_dir_path):
39
+ search_base_path = shadow_dir_path
40
+ shadow_exists = True
41
+ except Exception as e:
42
+ logger.warning(f"Error checking shadow path for {absolute_search_path}: {e}")
43
+
44
+ # Validate that at least one of the directories exists
45
+ if not os.path.exists(absolute_search_path) and not shadow_exists:
46
+ return ToolResult(success=False, message=f"Error: Search path not found: {search_path_str}")
47
+ if os.path.exists(absolute_search_path) and not os.path.isdir(absolute_search_path):
48
+ return ToolResult(success=False, message=f"Error: Search path is not a directory: {search_path_str}")
49
+ if shadow_exists and not os.path.isdir(search_base_path):
50
+ return ToolResult(success=False, message=f"Error: Shadow search path is not a directory: {search_base_path}")
51
+
52
+ results = []
53
+ try:
54
+ compiled_regex = re.compile(regex_pattern)
55
+ search_glob_pattern = os.path.join(search_base_path, "**", file_pattern)
56
+
57
+ logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{search_base_path}' (shadow: {shadow_exists})")
58
+
59
+ for filepath in glob.glob(search_glob_pattern, recursive=True):
60
+ if os.path.isfile(filepath):
61
+ try:
62
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
63
+ lines = f.readlines()
64
+ for i, line in enumerate(lines):
65
+ if compiled_regex.search(line):
66
+ # Provide context (e.g., line number and surrounding lines)
67
+ context_start = max(0, i - 2)
68
+ context_end = min(len(lines), i + 3)
69
+ context = "".join([f"{j+1}: {lines[j]}" for j in range(context_start, context_end)])
70
+ # For shadow files, convert to project-relative path
71
+ if shadow_exists and self.shadow_manager:
72
+ try:
73
+ abs_project_path = self.shadow_manager.from_shadow_path(filepath)
74
+ relative_path = os.path.relpath(abs_project_path, source_dir)
75
+ except Exception:
76
+ relative_path = os.path.relpath(filepath, source_dir)
77
+ else:
78
+ relative_path = os.path.relpath(filepath, source_dir)
79
+ results.append({
80
+ "path": relative_path,
81
+ "line_number": i + 1,
82
+ "match_line": line.strip(),
83
+ "context": context.strip()
84
+ })
85
+ # Limit results per file? Or overall? For now, collect all.
86
+ except Exception as e:
87
+ logger.warning(f"Could not read or process file {filepath}: {e}")
88
+ continue # Skip files that can't be read
89
+
90
+ message = f"Search completed. Found {len(results)} matches."
91
+ logger.info(message)
92
+ return ToolResult(success=True, message=message, content=results)
93
+
94
+ except re.error as e:
95
+ logger.error(f"Invalid regex pattern '{regex_pattern}': {e}")
96
+ return ToolResult(success=False, message=f"Invalid regex pattern: {e}")
97
+ except Exception as e:
98
+ logger.error(f"Error during file search: {str(e)}")
99
+ return ToolResult(success=False, message=f"An unexpected error occurred during search: {str(e)}")
@@ -0,0 +1,46 @@
1
+ from typing import Dict, Any, Optional
2
+ import typing
3
+ from autocoder.common import AutoCoderArgs
4
+ from autocoder.common.mcp_server_types import McpRequest
5
+ from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
6
+ from autocoder.common.v2.agent.agentic_edit_types import UseMcpTool, ToolResult # Import ToolResult from types
7
+ from autocoder.common.mcp_server import get_mcp_server
8
+ from loguru import logger
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from autocoder.common.v2.agent.agentic_edit import AgenticEdit
12
+
13
+
14
+ class UseMcpToolResolver(BaseToolResolver):
15
+ def __init__(self, agent: Optional['AgenticEdit'], tool: UseMcpTool, args: AutoCoderArgs):
16
+ super().__init__(agent, tool, args)
17
+ self.tool: UseMcpTool = tool # For type hinting
18
+
19
+ def resolve(self) -> ToolResult:
20
+ """
21
+ Executes a tool via the Model Context Protocol (MCP) server.
22
+ """
23
+ final_query = ""
24
+ server_name = self.tool.server_name
25
+ tool_name = self.tool.tool_name
26
+
27
+ if server_name:
28
+ final_query += f"{server_name}\n"
29
+
30
+ if tool_name:
31
+ final_query += f"{tool_name} is recommended for the following query:\n"
32
+
33
+ final_query += f"{self.tool.query}"
34
+
35
+ logger.info(f"Resolving UseMcpTool: Server='{server_name}', Tool='{tool_name}', Query='{final_query}'")
36
+
37
+ mcp_server = get_mcp_server()
38
+ response = mcp_server.send_request(
39
+ McpRequest(
40
+ query=final_query,
41
+ model=self.args.inference_model or self.args.model,
42
+ product_mode=self.args.product_mode
43
+ )
44
+ )
45
+ return ToolResult(success=True, message=response.result)
46
+
@@ -0,0 +1,58 @@
1
+ import os
2
+ from typing import Dict, Any, Optional
3
+ from autocoder.common.v2.agent.agentic_edit_types import WriteToFileTool, ToolResult # Import ToolResult from types
4
+ from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
+ from loguru import logger
6
+ from autocoder.common import AutoCoderArgs
7
+ import typing
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from autocoder.common.v2.agent.agentic_edit import AgenticEdit
11
+
12
+ class WriteToFileToolResolver(BaseToolResolver):
13
+ def __init__(self, agent: Optional['AgenticEdit'], tool: WriteToFileTool, args: AutoCoderArgs):
14
+ super().__init__(agent, tool, args)
15
+ self.tool: WriteToFileTool = tool # For type hinting
16
+ self.shadow_manager = self.agent.shadow_manager if self.agent else None
17
+
18
+ def resolve(self) -> ToolResult:
19
+ file_path = self.tool.path
20
+ content = self.tool.content
21
+ source_dir = self.args.source_dir or "."
22
+ abs_project_dir = os.path.abspath(source_dir)
23
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
24
+
25
+ # Security check: ensure the path is within the source directory
26
+ if not abs_file_path.startswith(abs_project_dir):
27
+ return ToolResult(success=False, message=f"Error: Access denied. Attempted to write file outside the project directory: {file_path}")
28
+
29
+ try:
30
+ if self.shadow_manager:
31
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
32
+ # Ensure shadow directory exists
33
+ os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
34
+ with open(shadow_path, 'w', encoding='utf-8') as f:
35
+ f.write(content)
36
+ logger.info(f"[Shadow] Successfully wrote shadow file: {shadow_path}")
37
+
38
+ # 回调AgenticEdit,记录变更
39
+ if self.agent:
40
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
41
+ self.agent.record_file_change(rel_path, "added", diff=None, content=content)
42
+
43
+ return ToolResult(success=True, message=f"Successfully wrote to file (shadow): {file_path}", content=content)
44
+ else:
45
+ # No shadow manager fallback to original file
46
+ os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
47
+ with open(abs_file_path, 'w', encoding='utf-8') as f:
48
+ f.write(content)
49
+ logger.info(f"Successfully wrote to file: {file_path}")
50
+
51
+ if self.agent:
52
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
53
+ self.agent.record_file_change(rel_path, "added", diff=None, content=content)
54
+
55
+ return ToolResult(success=True, message=f"Successfully wrote to file: {file_path}", content=content)
56
+ except Exception as e:
57
+ logger.error(f"Error writing to file '{file_path}': {str(e)}")
58
+ return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
@@ -0,0 +1,162 @@
1
+ from pydantic import BaseModel
2
+ from typing import List, Dict, Any, Callable, Optional, Type
3
+ from pydantic import SkipValidation
4
+
5
+
6
+ # Result class used by Tool Resolvers
7
+ class ToolResult(BaseModel):
8
+ success: bool
9
+ message: str
10
+ content: Any = None # Can store file content, command output, etc.
11
+
12
+ # Pydantic Models for Tools
13
+ class BaseTool(BaseModel):
14
+ pass
15
+
16
+ class ExecuteCommandTool(BaseTool):
17
+ command: str
18
+ requires_approval: bool
19
+
20
+ class ReadFileTool(BaseTool):
21
+ path: str
22
+
23
+ class WriteToFileTool(BaseTool):
24
+ path: str
25
+ content: str
26
+
27
+ class ReplaceInFileTool(BaseTool):
28
+ path: str
29
+ diff: str
30
+
31
+ class SearchFilesTool(BaseTool):
32
+ path: str
33
+ regex: str
34
+ file_pattern: Optional[str] = None
35
+
36
+ class ListFilesTool(BaseTool):
37
+ path: str
38
+ recursive: Optional[bool] = False
39
+
40
+ class ListCodeDefinitionNamesTool(BaseTool):
41
+ path: str
42
+
43
+ class AskFollowupQuestionTool(BaseTool):
44
+ question: str
45
+ options: Optional[List[str]] = None
46
+
47
+ class AttemptCompletionTool(BaseTool):
48
+ result: str
49
+ command: Optional[str] = None
50
+
51
+ class PlanModeRespondTool(BaseTool):
52
+ response: str
53
+ options: Optional[List[str]] = None
54
+
55
+ class UseMcpTool(BaseTool):
56
+ server_name: str
57
+ tool_name: str
58
+ query:str
59
+
60
+ # Event Types for Rich Output Streaming
61
+ class LLMOutputEvent(BaseModel):
62
+ """Represents plain text output from the LLM."""
63
+ text: str
64
+
65
+ class LLMThinkingEvent(BaseModel):
66
+ """Represents text within <thinking> tags from the LLM."""
67
+ text: str
68
+
69
+ class ToolCallEvent(BaseModel):
70
+ """Represents the LLM deciding to call a tool."""
71
+ tool: SkipValidation[BaseTool] # Use SkipValidation as BaseTool itself is complex
72
+ tool_xml: str
73
+
74
+ class ToolResultEvent(BaseModel):
75
+ """Represents the result of executing a tool."""
76
+ tool_name: str
77
+ result: ToolResult
78
+
79
+ class TokenUsageEvent(BaseModel):
80
+ """Represents the result of executing a tool."""
81
+ usage: Any
82
+
83
+ class CompletionEvent(BaseModel):
84
+ """Represents the LLM attempting to complete the task."""
85
+ completion: SkipValidation[AttemptCompletionTool] # Skip validation
86
+ completion_xml: str
87
+
88
+ class ErrorEvent(BaseModel):
89
+ """Represents an error during the process."""
90
+ message: str
91
+
92
+ # Deprecated: Will be replaced by specific Event types
93
+ # class PlainTextOutput(BaseModel):
94
+ # text: str
95
+
96
+
97
+ # Mapping from tool tag names to Pydantic models
98
+ TOOL_MODEL_MAP: Dict[str, Type[BaseTool]] = {
99
+ "execute_command": ExecuteCommandTool,
100
+ "read_file": ReadFileTool,
101
+ "write_to_file": WriteToFileTool,
102
+ "replace_in_file": ReplaceInFileTool,
103
+ "search_files": SearchFilesTool,
104
+ "list_files": ListFilesTool,
105
+ "list_code_definition_names": ListCodeDefinitionNamesTool,
106
+ "ask_followup_question": AskFollowupQuestionTool,
107
+ "attempt_completion": AttemptCompletionTool,
108
+ "plan_mode_respond": PlanModeRespondTool,
109
+ "use_mcp_tool": UseMcpTool,
110
+ }
111
+
112
+ class FileChangeEntry(BaseModel):
113
+ type: str # 'added' or 'modified'
114
+ diffs: List[str] = []
115
+ content: Optional[str] = None
116
+
117
+
118
+ class AgenticEditRequest(BaseModel):
119
+ user_input: str
120
+
121
+
122
+ class FileOperation(BaseModel):
123
+ path: str
124
+ operation: str # e.g., "MODIFY", "REFERENCE", "ADD", "REMOVE"
125
+ class MemoryConfig(BaseModel):
126
+ """
127
+ A model to encapsulate memory configuration and operations.
128
+ """
129
+
130
+ memory: Dict[str, Any]
131
+ save_memory_func: SkipValidation[Callable]
132
+
133
+ class Config:
134
+ arbitrary_types_allowed = True
135
+
136
+
137
+ class CommandConfig(BaseModel):
138
+ coding: SkipValidation[Callable]
139
+ chat: SkipValidation[Callable]
140
+ add_files: SkipValidation[Callable]
141
+ remove_files: SkipValidation[Callable]
142
+ index_build: SkipValidation[Callable]
143
+ index_query: SkipValidation[Callable]
144
+ list_files: SkipValidation[Callable]
145
+ ask: SkipValidation[Callable]
146
+ revert: SkipValidation[Callable]
147
+ commit: SkipValidation[Callable]
148
+ help: SkipValidation[Callable]
149
+ exclude_dirs: SkipValidation[Callable]
150
+ summon: SkipValidation[Callable]
151
+ design: SkipValidation[Callable]
152
+ mcp: SkipValidation[Callable]
153
+ models: SkipValidation[Callable]
154
+ lib: SkipValidation[Callable]
155
+ execute_shell_command: SkipValidation[Callable]
156
+ generate_shell_command: SkipValidation[Callable]
157
+ conf_export: SkipValidation[Callable]
158
+ conf_import: SkipValidation[Callable]
159
+ index_export: SkipValidation[Callable]
160
+ index_import: SkipValidation[Callable]
161
+ exclude_files: SkipValidation[Callable]
162
+