auto-coder 0.1.399__py3-none-any.whl → 1.0.0__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.
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/METADATA +1 -1
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/RECORD +71 -35
- autocoder/agent/agentic_filter.py +1 -1
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +1 -1
- autocoder/auto_coder_runner.py +121 -26
- autocoder/chat_auto_coder.py +81 -22
- autocoder/commands/auto_command.py +1 -1
- autocoder/common/__init__.py +2 -2
- autocoder/common/ac_style_command_parser/parser.py +27 -12
- autocoder/common/auto_coder_lang.py +78 -0
- autocoder/common/command_completer_v2.py +1 -1
- autocoder/common/file_monitor/test_file_monitor.py +307 -0
- autocoder/common/git_utils.py +7 -2
- autocoder/common/pruner/__init__.py +0 -0
- autocoder/common/pruner/agentic_conversation_pruner.py +197 -0
- autocoder/common/pruner/context_pruner.py +574 -0
- autocoder/common/pruner/conversation_pruner.py +132 -0
- autocoder/common/pruner/test_agentic_conversation_pruner.py +342 -0
- autocoder/common/pruner/test_context_pruner.py +546 -0
- autocoder/common/pull_requests/__init__.py +256 -0
- autocoder/common/pull_requests/base_provider.py +191 -0
- autocoder/common/pull_requests/config.py +66 -0
- autocoder/common/pull_requests/example.py +1 -0
- autocoder/common/pull_requests/exceptions.py +46 -0
- autocoder/common/pull_requests/manager.py +201 -0
- autocoder/common/pull_requests/models.py +164 -0
- autocoder/common/pull_requests/providers/__init__.py +23 -0
- autocoder/common/pull_requests/providers/gitcode_provider.py +19 -0
- autocoder/common/pull_requests/providers/gitee_provider.py +20 -0
- autocoder/common/pull_requests/providers/github_provider.py +214 -0
- autocoder/common/pull_requests/providers/gitlab_provider.py +29 -0
- autocoder/common/pull_requests/test_module.py +1 -0
- autocoder/common/pull_requests/utils.py +344 -0
- autocoder/common/tokens/__init__.py +77 -0
- autocoder/common/tokens/counter.py +231 -0
- autocoder/common/tokens/file_detector.py +105 -0
- autocoder/common/tokens/filters.py +111 -0
- autocoder/common/tokens/models.py +28 -0
- autocoder/common/v2/agent/agentic_edit.py +538 -590
- autocoder/common/v2/agent/agentic_edit_tools/__init__.py +8 -1
- autocoder/common/v2/agent/agentic_edit_tools/ac_mod_read_tool_resolver.py +40 -0
- autocoder/common/v2/agent/agentic_edit_tools/ac_mod_write_tool_resolver.py +43 -0
- autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +8 -0
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +1 -1
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +1 -1
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +33 -88
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +8 -8
- autocoder/common/v2/agent/agentic_edit_tools/todo_read_tool_resolver.py +118 -0
- autocoder/common/v2/agent/agentic_edit_tools/todo_write_tool_resolver.py +324 -0
- autocoder/common/v2/agent/agentic_edit_types.py +47 -4
- autocoder/common/v2/agent/runner/__init__.py +31 -0
- autocoder/common/v2/agent/runner/base_runner.py +106 -0
- autocoder/common/v2/agent/runner/event_runner.py +216 -0
- autocoder/common/v2/agent/runner/sdk_runner.py +40 -0
- autocoder/common/v2/agent/runner/terminal_runner.py +283 -0
- autocoder/common/v2/agent/runner/tool_display.py +191 -0
- autocoder/index/entry.py +1 -1
- autocoder/plugins/token_helper_plugin.py +107 -7
- autocoder/run_context.py +9 -0
- autocoder/sdk/__init__.py +114 -81
- autocoder/sdk/cli/handlers.py +2 -1
- autocoder/sdk/cli/main.py +9 -2
- autocoder/sdk/cli/options.py +4 -3
- autocoder/sdk/core/auto_coder_core.py +7 -152
- autocoder/sdk/core/bridge.py +5 -4
- autocoder/sdk/models/options.py +8 -6
- autocoder/version.py +1 -1
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional, List
|
|
2
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_types import TodoWriteTool, ToolResult
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import typing
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TodoWriteToolResolver(BaseToolResolver):
|
|
17
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: TodoWriteTool, args: AutoCoderArgs):
|
|
18
|
+
super().__init__(agent, tool, args)
|
|
19
|
+
self.tool: TodoWriteTool = tool # For type hinting
|
|
20
|
+
|
|
21
|
+
def _get_todo_file_path(self) -> str:
|
|
22
|
+
"""Get the path to the todo file for this session."""
|
|
23
|
+
source_dir = self.args.source_dir or "."
|
|
24
|
+
todo_dir = os.path.join(source_dir, ".auto-coder", "todos")
|
|
25
|
+
os.makedirs(todo_dir, exist_ok=True)
|
|
26
|
+
return os.path.join(todo_dir, "current_session.json")
|
|
27
|
+
|
|
28
|
+
def _load_todos(self) -> Dict[str, Any]:
|
|
29
|
+
"""Load todos from the session file."""
|
|
30
|
+
todo_file = self._get_todo_file_path()
|
|
31
|
+
if not os.path.exists(todo_file):
|
|
32
|
+
return {
|
|
33
|
+
"created_at": datetime.now().isoformat(),
|
|
34
|
+
"updated_at": datetime.now().isoformat(),
|
|
35
|
+
"todos": []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
with open(todo_file, 'r', encoding='utf-8') as f:
|
|
40
|
+
return json.load(f)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.warning(f"Failed to load todos: {e}")
|
|
43
|
+
return {
|
|
44
|
+
"created_at": datetime.now().isoformat(),
|
|
45
|
+
"updated_at": datetime.now().isoformat(),
|
|
46
|
+
"todos": []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def _save_todos(self, data: Dict[str, Any]) -> bool:
|
|
50
|
+
"""Save todos to the session file."""
|
|
51
|
+
try:
|
|
52
|
+
todo_file = self._get_todo_file_path()
|
|
53
|
+
data["updated_at"] = datetime.now().isoformat()
|
|
54
|
+
|
|
55
|
+
with open(todo_file, 'w', encoding='utf-8') as f:
|
|
56
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
57
|
+
return True
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Failed to save todos: {e}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def _generate_todo_id(self) -> str:
|
|
63
|
+
"""Generate a unique ID for a todo item."""
|
|
64
|
+
return str(uuid.uuid4())[:8]
|
|
65
|
+
|
|
66
|
+
def _find_todo_by_id(self, todos: List[Dict[str, Any]], task_id: str) -> Optional[Dict[str, Any]]:
|
|
67
|
+
"""Find a todo item by ID."""
|
|
68
|
+
for todo in todos:
|
|
69
|
+
if todo.get('id') == task_id:
|
|
70
|
+
return todo
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
def _create_todo_list(self, content: str) -> List[Dict[str, Any]]:
|
|
74
|
+
"""Create a new todo list from content."""
|
|
75
|
+
import re
|
|
76
|
+
|
|
77
|
+
todos = []
|
|
78
|
+
|
|
79
|
+
# First, try to parse <task> tags
|
|
80
|
+
task_pattern = r'<task>(.*?)</task>'
|
|
81
|
+
task_matches = re.findall(task_pattern, content, re.DOTALL)
|
|
82
|
+
|
|
83
|
+
if task_matches:
|
|
84
|
+
# Found <task> tags, use them
|
|
85
|
+
for task_content in task_matches:
|
|
86
|
+
task_content = task_content.strip()
|
|
87
|
+
if task_content:
|
|
88
|
+
todo = {
|
|
89
|
+
"id": self._generate_todo_id(),
|
|
90
|
+
"content": task_content,
|
|
91
|
+
"status": "pending",
|
|
92
|
+
"priority": self.tool.priority or "medium",
|
|
93
|
+
"created_at": datetime.now().isoformat(),
|
|
94
|
+
"updated_at": datetime.now().isoformat()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if self.tool.notes:
|
|
98
|
+
todo["notes"] = self.tool.notes
|
|
99
|
+
|
|
100
|
+
todos.append(todo)
|
|
101
|
+
else:
|
|
102
|
+
# Fallback to original line-by-line parsing
|
|
103
|
+
lines = content.strip().split('\n')
|
|
104
|
+
|
|
105
|
+
for line in lines:
|
|
106
|
+
line = line.strip()
|
|
107
|
+
if not line:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
# Remove common prefixes like "1.", "- ", "* ", etc.
|
|
111
|
+
line = line.lstrip('0123456789.- *\t')
|
|
112
|
+
|
|
113
|
+
if line:
|
|
114
|
+
todo = {
|
|
115
|
+
"id": self._generate_todo_id(),
|
|
116
|
+
"content": line,
|
|
117
|
+
"status": "pending",
|
|
118
|
+
"priority": self.tool.priority or "medium",
|
|
119
|
+
"created_at": datetime.now().isoformat(),
|
|
120
|
+
"updated_at": datetime.now().isoformat()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if self.tool.notes:
|
|
124
|
+
todo["notes"] = self.tool.notes
|
|
125
|
+
|
|
126
|
+
todos.append(todo)
|
|
127
|
+
|
|
128
|
+
return todos
|
|
129
|
+
|
|
130
|
+
def _add_single_task(self, todos: List[Dict[str, Any]], content: str) -> Dict[str, Any]:
|
|
131
|
+
"""Add a single task to the existing todo list."""
|
|
132
|
+
import re
|
|
133
|
+
|
|
134
|
+
# Check if content contains <task> tags
|
|
135
|
+
task_pattern = r'<task>(.*?)</task>'
|
|
136
|
+
task_matches = re.findall(task_pattern, content, re.DOTALL)
|
|
137
|
+
|
|
138
|
+
if task_matches:
|
|
139
|
+
# If <task> tags found, use the first one
|
|
140
|
+
task_content = task_matches[0].strip()
|
|
141
|
+
else:
|
|
142
|
+
# Use the content as-is
|
|
143
|
+
task_content = content.strip()
|
|
144
|
+
|
|
145
|
+
todo = {
|
|
146
|
+
"id": self._generate_todo_id(),
|
|
147
|
+
"content": task_content,
|
|
148
|
+
"status": self.tool.status or "pending",
|
|
149
|
+
"priority": self.tool.priority or "medium",
|
|
150
|
+
"created_at": datetime.now().isoformat(),
|
|
151
|
+
"updated_at": datetime.now().isoformat()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if self.tool.notes:
|
|
155
|
+
todo["notes"] = self.tool.notes
|
|
156
|
+
|
|
157
|
+
todos.append(todo)
|
|
158
|
+
return todo
|
|
159
|
+
|
|
160
|
+
def _update_task(self, todo: Dict[str, Any]) -> None:
|
|
161
|
+
"""Update an existing task."""
|
|
162
|
+
if self.tool.content:
|
|
163
|
+
todo["content"] = self.tool.content
|
|
164
|
+
if self.tool.status:
|
|
165
|
+
todo["status"] = self.tool.status
|
|
166
|
+
if self.tool.priority:
|
|
167
|
+
todo["priority"] = self.tool.priority
|
|
168
|
+
if self.tool.notes:
|
|
169
|
+
todo["notes"] = self.tool.notes
|
|
170
|
+
|
|
171
|
+
todo["updated_at"] = datetime.now().isoformat()
|
|
172
|
+
|
|
173
|
+
def _format_todo_response(self, todos: List[Dict[str, Any]], action_performed: str) -> str:
|
|
174
|
+
"""Format the response message after todo operations."""
|
|
175
|
+
if not todos:
|
|
176
|
+
return f"Operation completed: {action_performed}"
|
|
177
|
+
|
|
178
|
+
# Show the latest todos
|
|
179
|
+
recent_todos = todos[-3:] if len(todos) > 3 else todos
|
|
180
|
+
|
|
181
|
+
output = [f"✅ Operation completed: {action_performed}\n"]
|
|
182
|
+
|
|
183
|
+
if action_performed.startswith("Created"):
|
|
184
|
+
output.append("📝 Newly created todos:")
|
|
185
|
+
for todo in recent_todos:
|
|
186
|
+
priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(todo.get('priority', 'medium'), "⚪")
|
|
187
|
+
status_icon = {"pending": "⏳", "in_progress": "🔄", "completed": "✅"}.get(todo.get('status', 'pending'), "⏳")
|
|
188
|
+
output.append(f" {priority_icon} {status_icon} [{todo['id']}] {todo['content']}")
|
|
189
|
+
|
|
190
|
+
elif action_performed.startswith("Updated") or action_performed.startswith("Marked"):
|
|
191
|
+
output.append("📝 Updated todos:")
|
|
192
|
+
for todo in recent_todos:
|
|
193
|
+
priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(todo.get('priority', 'medium'), "⚪")
|
|
194
|
+
status_icon = {"pending": "⏳", "in_progress": "🔄", "completed": "✅"}.get(todo.get('status', 'pending'), "⏳")
|
|
195
|
+
output.append(f" {priority_icon} {status_icon} [{todo['id']}] {todo['content']}")
|
|
196
|
+
|
|
197
|
+
total_todos = len(todos)
|
|
198
|
+
pending_count = len([t for t in todos if t.get('status') == 'pending'])
|
|
199
|
+
in_progress_count = len([t for t in todos if t.get('status') == 'in_progress'])
|
|
200
|
+
completed_count = len([t for t in todos if t.get('status') == 'completed'])
|
|
201
|
+
|
|
202
|
+
output.append(f"\n📊 Current summary: Total {total_todos} items | Pending {pending_count} | In Progress {in_progress_count} | Completed {completed_count}")
|
|
203
|
+
|
|
204
|
+
return "\n".join(output)
|
|
205
|
+
|
|
206
|
+
def resolve(self) -> ToolResult:
|
|
207
|
+
"""
|
|
208
|
+
Create and manage a structured task list based on the action specified.
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
action = self.tool.action.lower()
|
|
212
|
+
logger.info(f"Performing todo action: {action}")
|
|
213
|
+
|
|
214
|
+
# Load existing todos
|
|
215
|
+
data = self._load_todos()
|
|
216
|
+
todos = data["todos"]
|
|
217
|
+
|
|
218
|
+
if action == "create":
|
|
219
|
+
if not self.tool.content:
|
|
220
|
+
return ToolResult(
|
|
221
|
+
success=False,
|
|
222
|
+
message="Error: Content is required for creating todos.",
|
|
223
|
+
content=None
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Clear existing todos and create new ones
|
|
227
|
+
new_todos = self._create_todo_list(self.tool.content)
|
|
228
|
+
data["todos"] = new_todos
|
|
229
|
+
|
|
230
|
+
if self._save_todos(data):
|
|
231
|
+
response = self._format_todo_response(new_todos, f"Created {len(new_todos)} new todo items")
|
|
232
|
+
return ToolResult(
|
|
233
|
+
success=True,
|
|
234
|
+
message="Todo list created successfully.",
|
|
235
|
+
content=response
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
return ToolResult(
|
|
239
|
+
success=False,
|
|
240
|
+
message="Failed to save todo list.",
|
|
241
|
+
content=None
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
elif action == "add_task":
|
|
245
|
+
if not self.tool.content:
|
|
246
|
+
return ToolResult(
|
|
247
|
+
success=False,
|
|
248
|
+
message="Error: Content is required for adding a task.",
|
|
249
|
+
content=None
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
new_todo = self._add_single_task(todos, self.tool.content)
|
|
253
|
+
|
|
254
|
+
if self._save_todos(data):
|
|
255
|
+
response = self._format_todo_response([new_todo], f"Added new task: {new_todo['content']}")
|
|
256
|
+
return ToolResult(
|
|
257
|
+
success=True,
|
|
258
|
+
message="Task added successfully.",
|
|
259
|
+
content=response
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
return ToolResult(
|
|
263
|
+
success=False,
|
|
264
|
+
message="Failed to save new task.",
|
|
265
|
+
content=None
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
elif action in ["update", "mark_progress", "mark_completed"]:
|
|
269
|
+
if not self.tool.task_id:
|
|
270
|
+
return ToolResult(
|
|
271
|
+
success=False,
|
|
272
|
+
message="Error: Task ID is required for update operations.",
|
|
273
|
+
content=None
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
todo = self._find_todo_by_id(todos, self.tool.task_id)
|
|
277
|
+
if not todo:
|
|
278
|
+
return ToolResult(
|
|
279
|
+
success=False,
|
|
280
|
+
message=f"Error: Task with ID '{self.tool.task_id}' not found.",
|
|
281
|
+
content=None
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Apply specific action
|
|
285
|
+
if action == "mark_progress":
|
|
286
|
+
todo["status"] = "in_progress"
|
|
287
|
+
todo["updated_at"] = datetime.now().isoformat()
|
|
288
|
+
action_msg = f"Marked task as in progress: {todo['content']}"
|
|
289
|
+
elif action == "mark_completed":
|
|
290
|
+
todo["status"] = "completed"
|
|
291
|
+
todo["updated_at"] = datetime.now().isoformat()
|
|
292
|
+
action_msg = f"Marked task as completed: {todo['content']}"
|
|
293
|
+
else: # update
|
|
294
|
+
self._update_task(todo)
|
|
295
|
+
action_msg = f"Updated task: {todo['content']}"
|
|
296
|
+
|
|
297
|
+
if self._save_todos(data):
|
|
298
|
+
response = self._format_todo_response([todo], action_msg)
|
|
299
|
+
return ToolResult(
|
|
300
|
+
success=True,
|
|
301
|
+
message="Task updated successfully.",
|
|
302
|
+
content=response
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
return ToolResult(
|
|
306
|
+
success=False,
|
|
307
|
+
message="Failed to save task update.",
|
|
308
|
+
content=None
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
else:
|
|
312
|
+
return ToolResult(
|
|
313
|
+
success=False,
|
|
314
|
+
message=f"Error: Unknown action '{action}'. Supported actions: create, add_task, update, mark_progress, mark_completed.",
|
|
315
|
+
content=None
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error(f"Error in todo write operation: {e}")
|
|
320
|
+
return ToolResult(
|
|
321
|
+
success=False,
|
|
322
|
+
message=f"Failed to perform todo operation: {str(e)}",
|
|
323
|
+
content=None
|
|
324
|
+
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
|
-
from typing import List, Dict, Any, Callable, Optional, Type
|
|
2
|
+
from typing import List, Dict, Any, Callable, Optional, Type, Union
|
|
3
3
|
from pydantic import SkipValidation
|
|
4
4
|
|
|
5
5
|
# Result class used by Tool Resolvers
|
|
@@ -60,9 +60,34 @@ class UseRAGTool(BaseTool):
|
|
|
60
60
|
server_name: str
|
|
61
61
|
query: str
|
|
62
62
|
|
|
63
|
-
class
|
|
63
|
+
class ACModReadTool(BaseTool):
|
|
64
64
|
path: str # 源码包目录,相对路径或绝对路径
|
|
65
65
|
|
|
66
|
+
class ACModWriteTool(BaseTool):
|
|
67
|
+
"""
|
|
68
|
+
Tool for creating or updating an AC Module's .ac.mod.md file.
|
|
69
|
+
"""
|
|
70
|
+
path: str # AC Module directory path, relative or absolute path
|
|
71
|
+
diff: str # diff content to edit the .ac.mod.md file
|
|
72
|
+
|
|
73
|
+
class TodoReadTool(BaseTool):
|
|
74
|
+
"""
|
|
75
|
+
Tool for reading the current todo list.
|
|
76
|
+
Takes no parameters.
|
|
77
|
+
"""
|
|
78
|
+
pass # No parameters needed
|
|
79
|
+
|
|
80
|
+
class TodoWriteTool(BaseTool):
|
|
81
|
+
"""
|
|
82
|
+
Tool for creating and managing a structured task list.
|
|
83
|
+
"""
|
|
84
|
+
action: str # 'create', 'update', 'mark_progress', 'mark_completed', 'add_task'
|
|
85
|
+
task_id: Optional[str] = None # Task ID for update/mark operations
|
|
86
|
+
content: Optional[str] = None # Task content for create/add operations
|
|
87
|
+
priority: Optional[str] = None # 'high', 'medium', 'low'
|
|
88
|
+
status: Optional[str] = None # 'pending', 'in_progress', 'completed'
|
|
89
|
+
notes: Optional[str] = None # Additional notes for the task
|
|
90
|
+
|
|
66
91
|
# Event Types for Rich Output Streaming
|
|
67
92
|
class LLMOutputEvent(BaseModel):
|
|
68
93
|
"""Represents plain text output from the LLM."""
|
|
@@ -109,6 +134,20 @@ class WindowLengthChangeEvent(BaseModel):
|
|
|
109
134
|
"""Represents the token usage in the conversation window."""
|
|
110
135
|
tokens_used: int
|
|
111
136
|
|
|
137
|
+
# Base event class for all agent events
|
|
138
|
+
class AgentEvent(BaseModel):
|
|
139
|
+
"""Base class for all agent events."""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
# Metadata for token usage tracking
|
|
143
|
+
class SingleOutputMeta(BaseModel):
|
|
144
|
+
"""Metadata for tracking token usage for a single LLM output."""
|
|
145
|
+
model_name: str
|
|
146
|
+
input_tokens: int
|
|
147
|
+
output_tokens: int
|
|
148
|
+
input_cost: float
|
|
149
|
+
output_cost: float
|
|
150
|
+
|
|
112
151
|
# Deprecated: Will be replaced by specific Event types
|
|
113
152
|
# class PlainTextOutput(BaseModel):
|
|
114
153
|
# text: str
|
|
@@ -127,8 +166,11 @@ TOOL_MODEL_MAP: Dict[str, Type[BaseTool]] = {
|
|
|
127
166
|
"attempt_completion": AttemptCompletionTool,
|
|
128
167
|
"plan_mode_respond": PlanModeRespondTool,
|
|
129
168
|
"use_mcp_tool": UseMcpTool,
|
|
130
|
-
"use_rag_tool": UseRAGTool,
|
|
131
|
-
"
|
|
169
|
+
"use_rag_tool": UseRAGTool,
|
|
170
|
+
"todo_read": TodoReadTool,
|
|
171
|
+
"todo_write": TodoWriteTool,
|
|
172
|
+
"ac_mod_read": ACModReadTool,
|
|
173
|
+
"ac_mod_write": ACModWriteTool,
|
|
132
174
|
}
|
|
133
175
|
|
|
134
176
|
class FileChangeEntry(BaseModel):
|
|
@@ -187,3 +229,4 @@ class AgenticEditConversationConfig(BaseModel):
|
|
|
187
229
|
conversation_id: Optional[str] = None
|
|
188
230
|
action: Optional[str] = None
|
|
189
231
|
query: Optional[str] = None
|
|
232
|
+
pull_request: bool = False
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Runner 模块提供了多种运行模式,用于在不同环境下执行 AgenticEdit 代理。
|
|
3
|
+
|
|
4
|
+
这个模块包含三种主要的运行器:
|
|
5
|
+
1. TerminalRunner: 在终端环境中运行代理,提供格式化输出
|
|
6
|
+
2. EventRunner: 将代理事件转换为标准事件系统格式
|
|
7
|
+
3. SdkRunner: 提供生成器接口,适用于SDK环境
|
|
8
|
+
|
|
9
|
+
使用示例:
|
|
10
|
+
```python
|
|
11
|
+
from autocoder.common.v2.agent.runner import TerminalRunner
|
|
12
|
+
from autocoder.common.v2.agent.agentic_edit_types import AgenticEditRequest
|
|
13
|
+
|
|
14
|
+
runner = TerminalRunner(llm=llm, args=args, ...)
|
|
15
|
+
runner.run(AgenticEditRequest(user_input="请帮我实现一个HTTP服务器"))
|
|
16
|
+
```
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .base_runner import BaseRunner
|
|
20
|
+
from .terminal_runner import TerminalRunner
|
|
21
|
+
from .event_runner import EventRunner
|
|
22
|
+
from .sdk_runner import SdkRunner
|
|
23
|
+
from .tool_display import get_tool_display_message
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"BaseRunner",
|
|
27
|
+
"TerminalRunner",
|
|
28
|
+
"EventRunner",
|
|
29
|
+
"SdkRunner",
|
|
30
|
+
"get_tool_display_message"
|
|
31
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BaseRunner 提供了所有运行器的基础实现和接口定义。
|
|
3
|
+
|
|
4
|
+
这个模块定义了代理运行的核心流程,包括请求处理、事件流生成和变更应用等。
|
|
5
|
+
各种具体运行器通过继承 BaseRunner 并实现特定的运行逻辑来提供不同的运行模式。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Generator, Any, Dict, Optional, Callable, List
|
|
10
|
+
|
|
11
|
+
from autocoder.common.v2.agent.agentic_edit_types import (
|
|
12
|
+
AgenticEditRequest, AgentEvent, CompletionEvent,
|
|
13
|
+
AgenticEditConversationConfig, CommandConfig, MemoryConfig
|
|
14
|
+
)
|
|
15
|
+
from autocoder.common import AutoCoderArgs, SourceCodeList
|
|
16
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
17
|
+
from autocoder.common.printer import Printer
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
class BaseRunner:
|
|
22
|
+
"""
|
|
23
|
+
代理运行器的基类,定义了运行代理的核心流程和接口。
|
|
24
|
+
|
|
25
|
+
所有具体的运行器实现(终端、事件系统、SDK)都应该继承这个类,
|
|
26
|
+
并根据特定需求实现 run 方法。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
llm: Any,
|
|
32
|
+
conversation_history: List[Dict[str, Any]],
|
|
33
|
+
files: SourceCodeList,
|
|
34
|
+
args: AutoCoderArgs,
|
|
35
|
+
memory_config: MemoryConfig,
|
|
36
|
+
command_config: Optional[CommandConfig] = None,
|
|
37
|
+
conversation_name: str = "current",
|
|
38
|
+
conversation_config: Optional[AgenticEditConversationConfig] = None
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
初始化代理运行器。
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
llm: 大语言模型实例
|
|
45
|
+
conversation_history: 对话历史
|
|
46
|
+
files: 源代码文件列表
|
|
47
|
+
args: 自动编码器参数
|
|
48
|
+
memory_config: 内存配置
|
|
49
|
+
command_config: 命令配置(可选)
|
|
50
|
+
conversation_name: 会话名称(默认为"current")
|
|
51
|
+
conversation_config: 会话配置(可选)
|
|
52
|
+
"""
|
|
53
|
+
self.llm = llm
|
|
54
|
+
self.conversation_history = conversation_history
|
|
55
|
+
self.files = files
|
|
56
|
+
self.args = args
|
|
57
|
+
self.memory_config = memory_config
|
|
58
|
+
self.command_config = command_config
|
|
59
|
+
self.conversation_name = conversation_name
|
|
60
|
+
self.conversation_config = conversation_config
|
|
61
|
+
self.printer = Printer()
|
|
62
|
+
|
|
63
|
+
self.agent = AgenticEdit(
|
|
64
|
+
llm=llm,
|
|
65
|
+
conversation_history=conversation_history,
|
|
66
|
+
files=files,
|
|
67
|
+
args=args,
|
|
68
|
+
memory_config=memory_config,
|
|
69
|
+
command_config=command_config,
|
|
70
|
+
conversation_name=conversation_name,
|
|
71
|
+
conversation_config=conversation_config
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def run(self, request: AgenticEditRequest) -> Any:
|
|
75
|
+
"""
|
|
76
|
+
运行代理处理请求。
|
|
77
|
+
|
|
78
|
+
这是一个抽象方法,需要由子类实现。
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
request: 代理编辑请求
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
根据具体实现返回不同类型的结果
|
|
85
|
+
"""
|
|
86
|
+
raise NotImplementedError("子类必须实现run方法")
|
|
87
|
+
|
|
88
|
+
def apply_pre_changes(self) -> None:
|
|
89
|
+
"""应用预处理变更"""
|
|
90
|
+
self.agent.apply_pre_changes()
|
|
91
|
+
|
|
92
|
+
def apply_changes(self) -> None:
|
|
93
|
+
"""应用代理执行后的变更"""
|
|
94
|
+
self.agent.apply_changes()
|
|
95
|
+
|
|
96
|
+
def analyze(self, request: AgenticEditRequest) -> Generator[AgentEvent, None, None]:
|
|
97
|
+
"""
|
|
98
|
+
分析请求并生成事件流。
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
request: 代理编辑请求
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
事件生成器
|
|
105
|
+
"""
|
|
106
|
+
return self.agent.analyze(request)
|