auto-coder 0.1.398__py3-none-any.whl → 0.1.400__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 (86) hide show
  1. auto_coder-0.1.400.dist-info/METADATA +396 -0
  2. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/RECORD +82 -29
  3. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/WHEEL +1 -1
  4. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/entry_points.txt +2 -0
  5. autocoder/agent/base_agentic/base_agent.py +2 -2
  6. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +1 -1
  7. autocoder/agent/entry_command_agent/__init__.py +29 -0
  8. autocoder/agent/entry_command_agent/auto_tool.py +61 -0
  9. autocoder/agent/entry_command_agent/chat.py +475 -0
  10. autocoder/agent/entry_command_agent/designer.py +53 -0
  11. autocoder/agent/entry_command_agent/generate_command.py +50 -0
  12. autocoder/agent/entry_command_agent/project_reader.py +58 -0
  13. autocoder/agent/entry_command_agent/voice2text.py +71 -0
  14. autocoder/auto_coder.py +23 -548
  15. autocoder/auto_coder_runner.py +511 -8
  16. autocoder/chat/rules_command.py +1 -1
  17. autocoder/chat_auto_coder.py +6 -1
  18. autocoder/common/ac_style_command_parser/__init__.py +15 -0
  19. autocoder/common/ac_style_command_parser/example.py +7 -0
  20. autocoder/{command_parser.py → common/ac_style_command_parser/parser.py} +28 -45
  21. autocoder/common/ac_style_command_parser/test_parser.py +516 -0
  22. autocoder/common/auto_coder_lang.py +78 -0
  23. autocoder/common/command_completer_v2.py +1 -1
  24. autocoder/common/command_file_manager/examples.py +22 -8
  25. autocoder/common/command_file_manager/manager.py +37 -6
  26. autocoder/common/conversations/get_conversation_manager.py +143 -0
  27. autocoder/common/conversations/manager.py +122 -11
  28. autocoder/common/conversations/storage/index_manager.py +89 -0
  29. autocoder/common/pull_requests/__init__.py +256 -0
  30. autocoder/common/pull_requests/base_provider.py +191 -0
  31. autocoder/common/pull_requests/config.py +66 -0
  32. autocoder/common/pull_requests/example.py +1 -0
  33. autocoder/common/pull_requests/exceptions.py +46 -0
  34. autocoder/common/pull_requests/manager.py +201 -0
  35. autocoder/common/pull_requests/models.py +164 -0
  36. autocoder/common/pull_requests/providers/__init__.py +23 -0
  37. autocoder/common/pull_requests/providers/gitcode_provider.py +19 -0
  38. autocoder/common/pull_requests/providers/gitee_provider.py +20 -0
  39. autocoder/common/pull_requests/providers/github_provider.py +214 -0
  40. autocoder/common/pull_requests/providers/gitlab_provider.py +29 -0
  41. autocoder/common/pull_requests/test_module.py +1 -0
  42. autocoder/common/pull_requests/utils.py +344 -0
  43. autocoder/common/tokens/__init__.py +62 -0
  44. autocoder/common/tokens/counter.py +211 -0
  45. autocoder/common/tokens/file_detector.py +105 -0
  46. autocoder/common/tokens/filters.py +111 -0
  47. autocoder/common/tokens/models.py +28 -0
  48. autocoder/common/v2/agent/agentic_edit.py +312 -85
  49. autocoder/common/v2/agent/agentic_edit_types.py +11 -0
  50. autocoder/common/v2/code_auto_generate_editblock.py +10 -2
  51. autocoder/dispacher/__init__.py +10 -0
  52. autocoder/rags.py +0 -27
  53. autocoder/run_context.py +1 -0
  54. autocoder/sdk/__init__.py +188 -0
  55. autocoder/sdk/cli/__init__.py +15 -0
  56. autocoder/sdk/cli/__main__.py +26 -0
  57. autocoder/sdk/cli/completion_wrapper.py +38 -0
  58. autocoder/sdk/cli/formatters.py +211 -0
  59. autocoder/sdk/cli/handlers.py +175 -0
  60. autocoder/sdk/cli/install_completion.py +301 -0
  61. autocoder/sdk/cli/main.py +286 -0
  62. autocoder/sdk/cli/options.py +73 -0
  63. autocoder/sdk/constants.py +102 -0
  64. autocoder/sdk/core/__init__.py +20 -0
  65. autocoder/sdk/core/auto_coder_core.py +880 -0
  66. autocoder/sdk/core/bridge.py +500 -0
  67. autocoder/sdk/example.py +0 -0
  68. autocoder/sdk/exceptions.py +72 -0
  69. autocoder/sdk/models/__init__.py +19 -0
  70. autocoder/sdk/models/messages.py +209 -0
  71. autocoder/sdk/models/options.py +196 -0
  72. autocoder/sdk/models/responses.py +311 -0
  73. autocoder/sdk/session/__init__.py +32 -0
  74. autocoder/sdk/session/session.py +106 -0
  75. autocoder/sdk/session/session_manager.py +56 -0
  76. autocoder/sdk/utils/__init__.py +24 -0
  77. autocoder/sdk/utils/formatters.py +216 -0
  78. autocoder/sdk/utils/io_utils.py +302 -0
  79. autocoder/sdk/utils/validators.py +287 -0
  80. autocoder/version.py +2 -1
  81. auto_coder-0.1.398.dist-info/METADATA +0 -111
  82. autocoder/common/conversations/compatibility.py +0 -303
  83. autocoder/common/conversations/conversation_manager.py +0 -502
  84. autocoder/common/conversations/example.py +0 -152
  85. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info/licenses}/LICENSE +0 -0
  86. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,880 @@
1
+ """
2
+ Auto-Coder SDK 核心封装类
3
+
4
+ 提供统一的查询接口,处理同步和异步调用。
5
+ """
6
+
7
+ from typing import AsyncIterator, Optional, Dict, Any, Iterator
8
+ import asyncio
9
+ import os
10
+ import time
11
+ from concurrent.futures import ThreadPoolExecutor
12
+
13
+ # Rich 渲染相关导入
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.markdown import Markdown
17
+ from rich.syntax import Syntax
18
+ from rich.progress import Progress, SpinnerColumn, TextColumn
19
+
20
+ from ..models.options import AutoCodeOptions
21
+ from ..models.messages import Message
22
+ from ..models.responses import StreamEvent, CodeModificationResult
23
+ from ..exceptions import BridgeError
24
+ from .bridge import AutoCoderBridge
25
+
26
+
27
+ class AutoCoderCore:
28
+ """AutoCoder核心封装类"""
29
+
30
+ def __init__(self, options: AutoCodeOptions):
31
+ """
32
+ 初始化AutoCoderCore
33
+
34
+ Args:
35
+ options: 配置选项
36
+ """
37
+ self.options = options
38
+ cwd_str = str(options.cwd) if options.cwd is not None else os.getcwd()
39
+ self.bridge = AutoCoderBridge(cwd_str,options)
40
+ self._executor = ThreadPoolExecutor(max_workers=1)
41
+ self._console = Console()
42
+
43
+ # 用于累计TokenUsageEvent数据
44
+ self._accumulated_token_usage = {
45
+ "model_name": "",
46
+ "input_tokens": 0,
47
+ "output_tokens": 0,
48
+ "input_cost": 0.0,
49
+ "output_cost": 0.0
50
+ }
51
+
52
+ def _render_stream_event(self, event: StreamEvent, show_terminal: bool = True) -> None:
53
+ """
54
+ 渲染流式事件到终端
55
+
56
+ Args:
57
+ event: 流式事件
58
+ show_terminal: 是否显示到终端
59
+ """
60
+ if not show_terminal:
61
+ return
62
+
63
+ try:
64
+ # 处理新的事件类型(动态检查以避免导入依赖)
65
+ event_class_name = type(event).__name__
66
+
67
+ # 处理 TokenUsageEvent 和 WindowLengthChangeEvent
68
+ if 'TokenUsageEvent' in event_class_name:
69
+ usage = getattr(event, 'usage', None)
70
+ if usage:
71
+ self._process_token_usage_event(usage)
72
+ return
73
+
74
+ elif 'WindowLengthChangeEvent' in event_class_name:
75
+ tokens_used = getattr(event, 'tokens_used', 0)
76
+ if tokens_used > 0:
77
+ self._console.print(f"[dim]当前会话总 tokens: {tokens_used}[/dim]")
78
+ return
79
+
80
+ elif 'LLMThinkingEvent' in event_class_name:
81
+ text = getattr(event, 'text', '')
82
+ if text.strip():
83
+ self._console.print(f"[grey50]{text}[/grey50]", end="")
84
+ return
85
+
86
+ elif 'LLMOutputEvent' in event_class_name:
87
+ text = getattr(event, 'text', '')
88
+ if text.strip():
89
+ self._console.print(text, end="")
90
+ return
91
+
92
+ elif 'ToolCallEvent' in event_class_name:
93
+ # 跳过 AttemptCompletionTool 的工具调用显示
94
+ tool = getattr(event, 'tool', None)
95
+ if tool and 'AttemptCompletionTool' in type(tool).__name__:
96
+ return
97
+
98
+ tool_name = type(tool).__name__ if tool else "Unknown Tool"
99
+ try:
100
+ # 尝试使用 get_tool_display_message 函数
101
+ from autocoder.common.v2.agent.agentic_edit_types import get_tool_display_message
102
+ display_content = get_tool_display_message(tool)
103
+ except:
104
+ # 如果导入失败,使用简单的显示
105
+ display_content = f"Tool: {tool_name}"
106
+ if hasattr(tool, '__dict__'):
107
+ for key, value in tool.__dict__.items():
108
+ if not key.startswith('_'):
109
+ display_content += f"\n{key}: {value}"
110
+
111
+ self._console.print(Panel(
112
+ display_content,
113
+ title=f"🛠️ Action: {tool_name}",
114
+ border_style="blue",
115
+ title_align="left"
116
+ ))
117
+ return
118
+
119
+ elif 'ToolResultEvent' in event_class_name:
120
+ # 跳过 AttemptCompletionTool 和 PlanModeRespondTool 的结果显示
121
+ tool_name = getattr(event, 'tool_name', 'Unknown')
122
+ if tool_name in ["AttemptCompletionTool", "PlanModeRespondTool"]:
123
+ return
124
+
125
+ result = getattr(event, 'result', None)
126
+ if result:
127
+ success = getattr(result, 'success', True)
128
+ message = getattr(result, 'message', '')
129
+ content = getattr(result, 'content', None)
130
+
131
+ title = f"✅ Tool Result: {tool_name}" if success else f"❌ Tool Result: {tool_name}"
132
+ border_style = "green" if success else "red"
133
+
134
+ base_content = f"[bold]Status:[/bold] {'Success' if success else 'Failure'}\n"
135
+ base_content += f"[bold]Message:[/bold] {message}\n"
136
+
137
+ # 处理内容显示
138
+ if content is not None:
139
+ formatted_content = self._format_tool_result_content(content, tool_name)
140
+ if isinstance(formatted_content, Syntax):
141
+ self._console.print(Panel(base_content, title=title, border_style=border_style, title_align="left"))
142
+ self._console.print(formatted_content)
143
+ else:
144
+ base_content += f"\n{formatted_content}"
145
+ self._console.print(Panel(base_content, title=title, border_style=border_style, title_align="left"))
146
+ else:
147
+ self._console.print(Panel(base_content, title=title, border_style=border_style, title_align="left"))
148
+ return
149
+
150
+ elif 'CompletionEvent' in event_class_name:
151
+ completion = getattr(event, 'completion', None)
152
+ if completion:
153
+ result = getattr(completion, 'result', 'Task completed successfully')
154
+ command = getattr(completion, 'command', None)
155
+
156
+ self._console.print(Panel(
157
+ Markdown(result),
158
+ title="🏁 Task Completion",
159
+ border_style="green",
160
+ title_align="left"
161
+ ))
162
+ if command:
163
+ self._console.print(f"[dim]Suggested command:[/dim] [bold cyan]{command}[/]")
164
+ return
165
+
166
+ elif 'PlanModeRespondEvent' in event_class_name:
167
+ completion = getattr(event, 'completion', None)
168
+ if completion:
169
+ response = getattr(completion, 'response', 'Plan completed')
170
+ self._console.print(Panel(
171
+ Markdown(response),
172
+ title="🏁 Plan Completion",
173
+ border_style="green",
174
+ title_align="left"
175
+ ))
176
+ return
177
+
178
+ elif 'ErrorEvent' in event_class_name:
179
+ message = getattr(event, 'message', 'Unknown error')
180
+ self._console.print(Panel(
181
+ f"[bold red]Error:[/bold red] {message}",
182
+ title="🔥 Error",
183
+ border_style="red",
184
+ title_align="left"
185
+ ))
186
+ return
187
+
188
+ elif 'ConversationIdEvent' in event_class_name:
189
+ conversation_id = getattr(event, 'conversation_id', '')
190
+ if conversation_id:
191
+ self._console.print(f"[dim]Conversation ID: {conversation_id}[/dim]")
192
+ return
193
+
194
+ # 处理旧格式的事件类型
195
+ if event.event_type == "start":
196
+ project_name = os.path.basename(os.path.abspath(self.options.cwd or os.getcwd()))
197
+ self._console.rule(f"[bold cyan]Starting Auto-Coder: {project_name}[/]")
198
+ query = event.data.get("query", "")
199
+ if query:
200
+ self._console.print(Panel(
201
+ f"[bold]Query:[/bold]\n{query}",
202
+ title="🎯 Objective",
203
+ border_style="blue"
204
+ ))
205
+
206
+ elif event.event_type == "llm_thinking":
207
+ text = event.data.get("text", "")
208
+ if text.strip():
209
+ self._console.print(f"[grey50]{text}[/grey50]", end="")
210
+
211
+ elif event.event_type == "llm_output":
212
+ text = event.data.get("text", "")
213
+ if text.strip():
214
+ self._console.print(text, end="")
215
+
216
+ elif event.event_type == "tool_call":
217
+ tool_name = event.data.get("tool_name", "Unknown Tool")
218
+ tool_args = event.data.get("args", {})
219
+ display_content = self._format_tool_display(tool_name, tool_args)
220
+ self._console.print(Panel(
221
+ display_content,
222
+ title=f"🛠️ Action: {tool_name}",
223
+ border_style="blue",
224
+ title_align="left"
225
+ ))
226
+
227
+ elif event.event_type == "tool_result":
228
+ tool_name = event.data.get("tool_name", "Unknown Tool")
229
+ success = event.data.get("success", True)
230
+ message = event.data.get("message", "")
231
+ content = event.data.get("content")
232
+
233
+ title = f"✅ Tool Result: {tool_name}" if success else f"❌ Tool Result: {tool_name}"
234
+ border_style = "green" if success else "red"
235
+
236
+ base_content = f"[bold]Status:[/bold] {'Success' if success else 'Failure'}\n"
237
+ base_content += f"[bold]Message:[/bold] {message}\n"
238
+
239
+ # 处理内容显示
240
+ if content is not None:
241
+ formatted_content = self._format_tool_result_content(content, tool_name)
242
+ if isinstance(formatted_content, Syntax):
243
+ self._console.print(Panel(base_content, title=title, border_style=border_style, title_align="left"))
244
+ self._console.print(formatted_content)
245
+ else:
246
+ base_content += f"\n{formatted_content}"
247
+ self._console.print(Panel(base_content, title=title, border_style=border_style, title_align="left"))
248
+ else:
249
+ self._console.print(Panel(base_content, title=title, border_style=border_style, title_align="left"))
250
+
251
+ elif event.event_type == "completion":
252
+ result = event.data.get("result", "Task completed successfully")
253
+ self._console.print(Panel(
254
+ Markdown(result),
255
+ title="🏁 Task Completion",
256
+ border_style="green",
257
+ title_align="left"
258
+ ))
259
+
260
+ elif event.event_type == "plan_mode_respond":
261
+ result = event.data.get("result", "Plan completed")
262
+ self._console.print(Panel(
263
+ Markdown(result),
264
+ title="🏁 Plan Completion",
265
+ border_style="green",
266
+ title_align="left"
267
+ ))
268
+
269
+ elif event.event_type == "token_usage":
270
+ usage = event.data.get("usage")
271
+ if usage:
272
+ self._process_token_usage_event(usage)
273
+
274
+ elif event.event_type == "window_change":
275
+ tokens_used = event.data.get("tokens_used", 0)
276
+ if tokens_used > 0:
277
+ self._console.print(f"[dim]当前会话总 tokens: {tokens_used}[/dim]")
278
+
279
+ elif event.event_type == "conversation_id":
280
+ conversation_id = event.data.get("conversation_id", "")
281
+ if conversation_id:
282
+ self._console.print(f"[dim]Conversation ID: {conversation_id}[/dim]")
283
+
284
+ elif event.event_type == "content":
285
+ content = event.data.get("content", "")
286
+ if content.strip():
287
+ # 检查是否是思考过程(通常包含特定标记)
288
+ if any(marker in content.lower() for marker in ["thinking", "analyzing", "考虑", "分析"]):
289
+ self._console.print(f"[grey50]{content}[/grey50]", end="")
290
+ else:
291
+ self._console.print(content, end="")
292
+
293
+ elif event.event_type == "file_modified":
294
+ files = event.data.get("files", [])
295
+ if files:
296
+ files_str = "\n".join([f" - {f}" for f in files])
297
+ self._console.print(Panel(
298
+ f"[bold]Modified Files:[/bold]\n{files_str}",
299
+ title="📝 File Changes",
300
+ border_style="yellow",
301
+ title_align="left"
302
+ ))
303
+
304
+ elif event.event_type == "file_created":
305
+ files = event.data.get("files", [])
306
+ if files:
307
+ files_str = "\n".join([f" - {f}" for f in files])
308
+ self._console.print(Panel(
309
+ f"[bold]Created Files:[/bold]\n{files_str}",
310
+ title="📄 New Files",
311
+ border_style="green",
312
+ title_align="left"
313
+ ))
314
+
315
+ elif event.event_type == "file_deleted":
316
+ files = event.data.get("files", [])
317
+ if files:
318
+ files_str = "\n".join([f" - {f}" for f in files])
319
+ self._console.print(Panel(
320
+ f"[bold]Deleted Files:[/bold]\n{files_str}",
321
+ title="🗑️ Removed Files",
322
+ border_style="red",
323
+ title_align="left"
324
+ ))
325
+
326
+ elif event.event_type == "end":
327
+ status = event.data.get("status", "completed")
328
+ if status == "completed":
329
+ self._console.rule("[bold green]Auto-Coder Finished Successfully[/]")
330
+ else:
331
+ self._console.rule(f"[bold yellow]Auto-Coder Finished: {status}[/]")
332
+
333
+ elif event.event_type == "error":
334
+ error = event.data.get("error", "Unknown error")
335
+ error_type = event.data.get("error_type", "Error")
336
+ self._console.print(Panel(
337
+ f"[bold red]Error Type:[/bold red] {error_type}\n[bold red]Message:[/bold red] {error}",
338
+ title="🔥 Error",
339
+ border_style="red",
340
+ title_align="left"
341
+ ))
342
+
343
+ except Exception as e:
344
+ # 渲染错误不应该影响主流程
345
+ self._console.print(f"[dim red]Render error: {str(e)}[/dim red]")
346
+
347
+ def _format_tool_display(self, tool_name: str, tool_args: Dict[str, Any]) -> str:
348
+ """
349
+ 格式化工具调用显示内容
350
+
351
+ Args:
352
+ tool_name: 工具名称
353
+ tool_args: 工具参数
354
+
355
+ Returns:
356
+ str: 格式化后的显示内容
357
+ """
358
+ if not tool_args:
359
+ return f"[bold]Tool:[/bold] {tool_name}"
360
+
361
+ content_parts = [f"[bold]Tool:[/bold] {tool_name}"]
362
+
363
+ for key, value in tool_args.items():
364
+ if isinstance(value, str) and len(value) > 100:
365
+ value = f"{value[:50]}...{value[-50:]}"
366
+ content_parts.append(f"[bold]{key}:[/bold] {value}")
367
+
368
+ return "\n".join(content_parts)
369
+
370
+ def _process_token_usage_event(self, usage):
371
+ """
372
+ 处理 TokenUsageEvent,累计 token 使用情况
373
+
374
+ Args:
375
+ usage: SingleOutputMeta 对象
376
+ """
377
+ try:
378
+ # 正确提取 SingleOutputMeta 对象的属性
379
+ input_tokens = getattr(usage, 'input_tokens_count', 0)
380
+ output_tokens = getattr(usage, 'generated_tokens_count', 0)
381
+
382
+ # 获取模型信息用于定价
383
+ try:
384
+ from autocoder.utils import llms as llm_utils
385
+ # 这里需要获取 LLM 实例,但在 SDK 中可能不直接可用
386
+ # 暂时使用默认模型名称
387
+ model_name = self.options.model or "unknown"
388
+ model_info = llm_utils.get_model_info(model_name, "lite") or {}
389
+ input_price = model_info.get("input_price", 0.0)
390
+ output_price = model_info.get("output_price", 0.0)
391
+ except:
392
+ # 如果获取模型信息失败,使用默认值
393
+ model_name = self.options.model or "unknown"
394
+ input_price = 0.0
395
+ output_price = 0.0
396
+
397
+ # 计算成本
398
+ input_cost = (input_tokens * input_price) / 1000000 # 转换为百万token单位
399
+ output_cost = (output_tokens * output_price) / 1000000
400
+
401
+ # 累计token使用情况
402
+ self._accumulated_token_usage["model_name"] = model_name
403
+ self._accumulated_token_usage["input_tokens"] += input_tokens
404
+ self._accumulated_token_usage["output_tokens"] += output_tokens
405
+ self._accumulated_token_usage["input_cost"] += input_cost
406
+ self._accumulated_token_usage["output_cost"] += output_cost
407
+
408
+ # 显示当前的 token 使用情况
409
+ total_tokens = input_tokens + output_tokens
410
+ self._console.print(f"[dim]Token usage: Input={input_tokens}, Output={output_tokens}, Total={total_tokens}[/dim]")
411
+
412
+ except Exception as e:
413
+ self._console.print(f"[dim red]Error processing token usage: {str(e)}[/dim red]")
414
+
415
+ def _print_final_token_usage(self):
416
+ """
417
+ 打印最终的累计 token 使用情况
418
+ """
419
+ try:
420
+ if self._accumulated_token_usage["input_tokens"] > 0:
421
+ from autocoder.common.printer import Printer
422
+ printer = Printer()
423
+ printer.print_in_terminal(
424
+ "code_generation_complete",
425
+ duration=0.0,
426
+ input_tokens=self._accumulated_token_usage["input_tokens"],
427
+ output_tokens=self._accumulated_token_usage["output_tokens"],
428
+ input_cost=self._accumulated_token_usage["input_cost"],
429
+ output_cost=self._accumulated_token_usage["output_cost"],
430
+ speed=0.0,
431
+ model_names=self._accumulated_token_usage["model_name"],
432
+ sampling_count=1
433
+ )
434
+ except Exception as e:
435
+ # 如果打印失败,使用简单的格式
436
+ total_tokens = self._accumulated_token_usage["input_tokens"] + self._accumulated_token_usage["output_tokens"]
437
+ total_cost = self._accumulated_token_usage["input_cost"] + self._accumulated_token_usage["output_cost"]
438
+ self._console.print(Panel(
439
+ f"总计 Token 使用: {total_tokens} (输入: {self._accumulated_token_usage['input_tokens']}, 输出: {self._accumulated_token_usage['output_tokens']})\n"
440
+ f"总计成本: ${total_cost:.6f}",
441
+ title="📊 Token 使用统计",
442
+ border_style="cyan"
443
+ ))
444
+
445
+ def _reset_token_usage(self):
446
+ """
447
+ 重置累计的 token 使用情况
448
+ """
449
+ self._accumulated_token_usage = {
450
+ "model_name": "",
451
+ "input_tokens": 0,
452
+ "output_tokens": 0,
453
+ "input_cost": 0.0,
454
+ "output_cost": 0.0
455
+ }
456
+
457
+ def _format_tool_result_content(self, content: Any, tool_name: str = "") -> str | Syntax:
458
+ """
459
+ 格式化工具结果内容
460
+
461
+ Args:
462
+ content: 结果内容
463
+ tool_name: 工具名称(用于推断语法类型)
464
+
465
+ Returns:
466
+ str | Syntax: 格式化后的内容或语法高亮对象
467
+ """
468
+ def _truncate_content(content_str: str) -> str:
469
+ if len(content_str) > 500:
470
+ return f"{content_str[:200]}\n...\n{content_str[-200:]}"
471
+ return content_str
472
+
473
+ try:
474
+ if isinstance(content, (dict, list)):
475
+ import json
476
+ content_str = json.dumps(content, indent=2, ensure_ascii=False)
477
+ return Syntax(_truncate_content(content_str), "json", theme="default", line_numbers=False)
478
+
479
+ elif isinstance(content, str):
480
+ # 检查是否是多行内容或代码
481
+ if '\n' in content or content.strip().startswith('<') or len(content) > 200:
482
+ # 推断语法类型
483
+ lexer = "text"
484
+ if "ReadFile" in tool_name:
485
+ if any(ext in content for ext in [".py", "python"]):
486
+ lexer = "python"
487
+ elif any(ext in content for ext in [".js", "javascript"]):
488
+ lexer = "javascript"
489
+ elif any(ext in content for ext in [".ts", "typescript"]):
490
+ lexer = "typescript"
491
+ elif any(ext in content for ext in [".html", "<!DOCTYPE", "<html"]):
492
+ lexer = "html"
493
+ elif any(ext in content for ext in [".css", "{"]):
494
+ lexer = "css"
495
+ elif any(ext in content for ext in [".json", "{"]):
496
+ lexer = "json"
497
+ elif any(ext in content for ext in [".xml", "<?xml"]):
498
+ lexer = "xml"
499
+ elif any(ext in content for ext in [".md", "#"]):
500
+ lexer = "markdown"
501
+ elif "ExecuteCommand" in tool_name or "Shell" in tool_name:
502
+ lexer = "shell"
503
+ elif content.strip().startswith('{') or content.strip().startswith('['):
504
+ lexer = "json"
505
+
506
+ return Syntax(_truncate_content(content), lexer, theme="default", line_numbers=True)
507
+ else:
508
+ return _truncate_content(content)
509
+ else:
510
+ return _truncate_content(str(content))
511
+
512
+ except Exception:
513
+ return _truncate_content(str(content))
514
+
515
+ async def query_stream(self, prompt: str, show_terminal: bool = True) -> AsyncIterator[Message]:
516
+ """
517
+ 异步流式查询 - 使用 run_auto_command
518
+
519
+ Args:
520
+ prompt: 查询提示
521
+ show_terminal: 是否显示到终端
522
+
523
+ Yields:
524
+ Message: 响应消息流
525
+
526
+ Raises:
527
+ BridgeError: 桥接层错误
528
+ """
529
+ try:
530
+ # 重置累计的 token 使用情况
531
+ self._reset_token_usage()
532
+
533
+ # 先返回用户消息
534
+ user_message = Message(role="user", content=prompt)
535
+ yield user_message
536
+
537
+ # 在线程池中执行同步调用
538
+ loop = asyncio.get_event_loop()
539
+
540
+ # 使用 run_auto_command 进行代码修改
541
+ event_stream = await loop.run_in_executor(
542
+ self._executor,
543
+ self._sync_run_auto_command,
544
+ prompt
545
+ )
546
+
547
+ # 处理事件流并转换为消息
548
+ assistant_content = ""
549
+ for event in event_stream:
550
+ # 渲染事件到终端
551
+ self._render_stream_event(event, show_terminal)
552
+
553
+ if event.event_type == "content":
554
+ content = event.data.get("content", "")
555
+ assistant_content += content
556
+
557
+ # 返回增量消息
558
+ yield Message(
559
+ role="assistant",
560
+ content=content,
561
+ metadata={
562
+ "event_type": event.event_type,
563
+ "model": self.options.model,
564
+ "temperature": self.options.temperature,
565
+ "is_incremental": True
566
+ }
567
+ )
568
+ elif event.event_type == "end":
569
+ # 返回最终完整消息
570
+ yield Message(
571
+ role="assistant",
572
+ content=assistant_content,
573
+ metadata={
574
+ "event_type": event.event_type,
575
+ "model": self.options.model,
576
+ "temperature": self.options.temperature,
577
+ "is_final": True,
578
+ "status": event.data.get("status", "completed")
579
+ }
580
+ )
581
+ elif event.event_type == "error":
582
+ # 返回错误消息
583
+ yield Message(
584
+ role="assistant",
585
+ content=f"Error: {event.data.get('error', 'Unknown error')}",
586
+ metadata={
587
+ "event_type": event.event_type,
588
+ "error_type": event.data.get("error_type", "Unknown"),
589
+ "is_error": True
590
+ }
591
+ )
592
+
593
+ # 添加小延迟以改善视觉效果
594
+ if show_terminal:
595
+ time.sleep(0.05)
596
+
597
+ # 打印最终的累计 token 使用情况
598
+ if show_terminal:
599
+ self._print_final_token_usage()
600
+
601
+ except Exception as e:
602
+ # 在异常时也打印累计的 token 使用情况
603
+ if show_terminal:
604
+ self._print_final_token_usage()
605
+ raise e
606
+
607
+ def query_sync(self, prompt: str, show_terminal: bool = True) -> str:
608
+ """
609
+ 同步查询 - 使用 run_auto_command
610
+
611
+ Args:
612
+ prompt: 查询提示
613
+ show_terminal: 是否显示到终端
614
+
615
+ Returns:
616
+ str: 响应内容
617
+
618
+ Raises:
619
+ BridgeError: 桥接层错误
620
+ """
621
+ try:
622
+ # 重置累计的 token 使用情况
623
+ self._reset_token_usage()
624
+
625
+ event_stream = self._sync_run_auto_command(prompt)
626
+
627
+ # 收集所有内容
628
+ content_parts = []
629
+ for event in event_stream:
630
+ # 渲染事件到终端
631
+ self._render_stream_event(event, show_terminal)
632
+
633
+ if event.event_type == "content":
634
+ content_parts.append(event.data.get("content", ""))
635
+ elif event.event_type == "error":
636
+ raise BridgeError(f"Query failed: {event.data.get('error', 'Unknown error')}")
637
+
638
+ # 添加小延迟以改善视觉效果
639
+ if show_terminal:
640
+ time.sleep(0.05)
641
+
642
+ # 打印最终的累计 token 使用情况
643
+ if show_terminal:
644
+ self._print_final_token_usage()
645
+
646
+ return "".join(content_parts)
647
+
648
+ except Exception as e:
649
+ # 在异常时也打印累计的 token 使用情况
650
+ if show_terminal:
651
+ self._print_final_token_usage()
652
+
653
+ raise e
654
+
655
+ def modify_code(
656
+ self,
657
+ prompt: str,
658
+ pre_commit: bool = False,
659
+ pr: Optional[bool] = None,
660
+ extra_args: Optional[Dict[str, Any]] = None,
661
+ show_terminal: bool = True
662
+ ) -> CodeModificationResult:
663
+ """
664
+ 代码修改接口 - 直接使用 run_auto_command
665
+
666
+ Args:
667
+ prompt: 修改提示
668
+ pre_commit: 是否预提交
669
+ pr: 是否创建 PR,如果为None则使用配置中的值
670
+ extra_args: 额外参数
671
+ show_terminal: 是否显示到终端
672
+
673
+ Returns:
674
+ CodeModificationResult: 修改结果
675
+ """
676
+ try:
677
+ # 重置累计的 token 使用情况
678
+ self._reset_token_usage()
679
+
680
+ event_stream = self._sync_run_auto_command(
681
+ prompt,
682
+ pre_commit=pre_commit,
683
+ pr=pr,
684
+ extra_args=extra_args
685
+ )
686
+
687
+ # 分析事件流,提取修改结果
688
+ modified_files = []
689
+ created_files = []
690
+ deleted_files = []
691
+ messages = []
692
+ success = True
693
+ error_details = None
694
+
695
+ for event in event_stream:
696
+ # 渲染事件到终端
697
+ self._render_stream_event(event, show_terminal)
698
+
699
+ if event.event_type == "content":
700
+ messages.append(event.data.get("content", ""))
701
+ elif event.event_type == "error":
702
+ success = False
703
+ error_details = event.data.get("error", "Unknown error")
704
+ elif event.event_type == "file_modified":
705
+ modified_files.extend(event.data.get("files", []))
706
+ elif event.event_type == "file_created":
707
+ created_files.extend(event.data.get("files", []))
708
+ elif event.event_type == "file_deleted":
709
+ deleted_files.extend(event.data.get("files", []))
710
+
711
+ # 添加小延迟以改善视觉效果
712
+ if show_terminal:
713
+ time.sleep(0.05)
714
+
715
+ # 打印最终的累计 token 使用情况
716
+ if show_terminal:
717
+ self._print_final_token_usage()
718
+
719
+ return CodeModificationResult(
720
+ success=success,
721
+ message="".join(messages),
722
+ modified_files=modified_files,
723
+ created_files=created_files,
724
+ deleted_files=deleted_files,
725
+ error_details=error_details,
726
+ metadata={
727
+ "pre_commit": pre_commit,
728
+ "extra_args": extra_args or {}
729
+ }
730
+ )
731
+
732
+ except Exception as e:
733
+ # 在异常时也打印累计的 token 使用情况
734
+ if show_terminal:
735
+ self._print_final_token_usage()
736
+ self._console.print(Panel(
737
+ f"[bold red]FATAL ERROR:[/bold red]\n{str(e)}",
738
+ title="🔥 System Error",
739
+ border_style="red"
740
+ ))
741
+ return CodeModificationResult(
742
+ success=False,
743
+ message="",
744
+ error_details=str(e),
745
+ metadata={"exception_type": type(e).__name__}
746
+ )
747
+
748
+ async def modify_code_stream(
749
+ self,
750
+ prompt: str,
751
+ pre_commit: bool = False,
752
+ pr: Optional[bool] = None,
753
+ extra_args: Optional[Dict[str, Any]] = None,
754
+ show_terminal: bool = True
755
+ ) -> AsyncIterator[StreamEvent]:
756
+ """
757
+ 异步流式代码修改接口
758
+
759
+ Args:
760
+ prompt: 修改提示
761
+ pre_commit: 是否预提交
762
+ pr: 是否创建 PR,如果为None则使用配置中的值
763
+ extra_args: 额外参数
764
+ show_terminal: 是否显示到终端
765
+
766
+ Yields:
767
+ StreamEvent: 修改事件流
768
+ """
769
+ try:
770
+ # 重置累计的 token 使用情况
771
+ self._reset_token_usage()
772
+
773
+ loop = asyncio.get_event_loop()
774
+
775
+ # 在线程池中执行同步调用
776
+ event_stream = await loop.run_in_executor(
777
+ self._executor,
778
+ self._sync_run_auto_command,
779
+ prompt,
780
+ pre_commit,
781
+ pr,
782
+ extra_args
783
+ )
784
+
785
+ # 处理并转发事件流
786
+ for event in event_stream:
787
+ # 渲染事件到终端
788
+ self._render_stream_event(event, show_terminal)
789
+
790
+ # 转发事件
791
+ yield event
792
+
793
+ # 添加小延迟以改善视觉效果
794
+ if show_terminal:
795
+ time.sleep(0.05)
796
+
797
+ # 打印最终的累计 token 使用情况
798
+ if show_terminal:
799
+ self._print_final_token_usage()
800
+
801
+ except Exception as e:
802
+ # 在异常时也打印累计的 token 使用情况
803
+ if show_terminal:
804
+ self._print_final_token_usage()
805
+
806
+ error_event = StreamEvent(
807
+ event_type="error",
808
+ data={"error": str(e), "error_type": type(e).__name__}
809
+ )
810
+ self._render_stream_event(error_event, show_terminal)
811
+ yield error_event
812
+
813
+ def _sync_run_auto_command(
814
+ self,
815
+ prompt: str,
816
+ pre_commit: bool = False,
817
+ pr: Optional[bool] = None,
818
+ extra_args: Optional[Dict[str, Any]] = None
819
+ ) -> Iterator[StreamEvent]:
820
+ """
821
+ 内部同步调用 run_auto_command
822
+
823
+ Args:
824
+ prompt: 查询提示
825
+ pre_commit: 是否预提交
826
+ pr: 是否创建 PR,如果为None则使用配置中的值
827
+ extra_args: 额外参数
828
+
829
+ Returns:
830
+ Iterator[StreamEvent]: 事件流
831
+ """
832
+ # 如果没有明确指定pr参数,使用配置中的值
833
+ if pr is None:
834
+ pr = self.options.pr
835
+
836
+ return self.bridge.call_run_auto_command(
837
+ query=prompt,
838
+ pre_commit=pre_commit,
839
+ pr=pr,
840
+ extra_args=extra_args or {},
841
+ stream=True
842
+ )
843
+
844
+
845
+ def get_project_memory(self) -> Dict[str, Any]:
846
+ """
847
+ 获取项目内存状态
848
+
849
+ Returns:
850
+ Dict[str, Any]: 项目内存数据
851
+ """
852
+ return self.bridge.get_memory()
853
+
854
+ def save_project_memory(self, memory_data: Dict[str, Any]) -> None:
855
+ """
856
+ 保存项目内存状态
857
+
858
+ Args:
859
+ memory_data: 内存数据
860
+ """
861
+ self.bridge.save_memory(memory_data)
862
+
863
+ def get_project_config(self) -> Dict[str, Any]:
864
+ """
865
+ 获取项目配置
866
+
867
+ Returns:
868
+ Dict[str, Any]: 项目配置
869
+ """
870
+ return self.bridge.get_project_config()
871
+
872
+ def __del__(self):
873
+ """清理资源"""
874
+ if hasattr(self, '_executor'):
875
+ self._executor.shutdown(wait=False)
876
+
877
+
878
+
879
+
880
+