siada-cli 0.0.1__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.
Files changed (215) hide show
  1. agent_config.yaml +34 -0
  2. siada/__init__.py +13 -0
  3. siada/agent_hub/__init__.py +10 -0
  4. siada/agent_hub/coder/bug_fix_agent.py +398 -0
  5. siada/agent_hub/coder/code_gen_agent.py +235 -0
  6. siada/agent_hub/coder/fe_gen_agent.py +37 -0
  7. siada/agent_hub/coder/issue_review_agent.py +68 -0
  8. siada/agent_hub/coder/prompt/__init__.py +0 -0
  9. siada/agent_hub/coder/prompt/base/__init__.py +0 -0
  10. siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
  11. siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
  12. siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
  13. siada/agent_hub/coder/prompt/base/rules.py +40 -0
  14. siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
  15. siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
  16. siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
  17. siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
  18. siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
  19. siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
  20. siada/agent_hub/coder/tracing/__init__.py +22 -0
  21. siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
  22. siada/agent_hub/hooks/__init__.py +0 -0
  23. siada/agent_hub/hooks/io_hooks.py +30 -0
  24. siada/agent_hub/siada_agent.py +214 -0
  25. siada/config/__init__.py +0 -0
  26. siada/config/config_loader.py +67 -0
  27. siada/entrypoint/__init__.py +3 -0
  28. siada/entrypoint/args_parser/__init__.py +0 -0
  29. siada/entrypoint/args_parser/args.py +195 -0
  30. siada/entrypoint/interaction/__init__.py +0 -0
  31. siada/entrypoint/interaction/base_turn.py +107 -0
  32. siada/entrypoint/interaction/config.py +30 -0
  33. siada/entrypoint/interaction/controller.py +122 -0
  34. siada/entrypoint/interaction/nointeractive_controller.py +27 -0
  35. siada/entrypoint/interaction/turn/interface.py +116 -0
  36. siada/entrypoint/interaction/turn/models.py +34 -0
  37. siada/entrypoint/interaction/turn/run_turn.py +641 -0
  38. siada/entrypoint/interaction/turn_factory.py +35 -0
  39. siada/entrypoint/interaction/turn_models.py +28 -0
  40. siada/entrypoint/siadahub.py +413 -0
  41. siada/foundation/__init__.py +0 -0
  42. siada/foundation/code_agent_context.py +48 -0
  43. siada/foundation/config.py +59 -0
  44. siada/foundation/constants.py +2 -0
  45. siada/foundation/context.py +76 -0
  46. siada/foundation/logging.py +341 -0
  47. siada/foundation/tools/__init__.py +0 -0
  48. siada/foundation/tools/get_git_diff.py +105 -0
  49. siada/io/__init__.py +0 -0
  50. siada/io/banner.py +148 -0
  51. siada/io/color_settings.py +207 -0
  52. siada/io/color_utils.py +16 -0
  53. siada/io/components/__init__.py +0 -0
  54. siada/io/components/mdstream.py +204 -0
  55. siada/io/console_printer.py +68 -0
  56. siada/io/io.py +661 -0
  57. siada/io/key_bindings.py +129 -0
  58. siada/io/notification_command.py +40 -0
  59. siada/io/test_banner.py +64 -0
  60. siada/models/__init__.py +0 -0
  61. siada/models/agent.py +47 -0
  62. siada/models/coder/__init__.py +0 -0
  63. siada/models/converter.py +27 -0
  64. siada/models/model_base_config.py +121 -0
  65. siada/models/model_run_config.py +153 -0
  66. siada/provider/__init__.py +0 -0
  67. siada/provider/client_factory.py +112 -0
  68. siada/provider/llm_client.py +11 -0
  69. siada/provider/openrouter/__init__.py +0 -0
  70. siada/provider/openrouter/coverter.py +17 -0
  71. siada/provider/openrouter/openroute_provider.py +33 -0
  72. siada/provider/provider_factory.py +53 -0
  73. siada/queries/tree-sitter-language-pack/README.md +7 -0
  74. siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  91. siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  92. siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  93. siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  94. siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  95. siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  96. siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  97. siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  98. siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  99. siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  100. siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  101. siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  102. siada/queries/tree-sitter-languages/README.md +23 -0
  103. siada/queries/tree-sitter-languages/c-tags.scm +9 -0
  104. siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  105. siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  106. siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
  107. siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  108. siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  109. siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
  110. siada/queries/tree-sitter-languages/go-tags.scm +30 -0
  111. siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  112. siada/queries/tree-sitter-languages/java-tags.scm +20 -0
  113. siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  114. siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  115. siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  116. siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  117. siada/queries/tree-sitter-languages/php-tags.scm +26 -0
  118. siada/queries/tree-sitter-languages/python-tags.scm +12 -0
  119. siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
  120. siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  121. siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
  122. siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
  123. siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  124. siada/services/__init__.py +30 -0
  125. siada/services/at_file_recommendation_design_document.md +1213 -0
  126. siada/services/code_context_manager.py +136 -0
  127. siada/services/enhanced_fix_result_check.py +546 -0
  128. siada/services/execution_trace_collector.py +207 -0
  129. siada/services/file_recommendation/__init__.py +136 -0
  130. siada/services/file_recommendation/core/__init__.py +17 -0
  131. siada/services/file_recommendation/core/completion_engine.py +351 -0
  132. siada/services/file_recommendation/core/config.py +62 -0
  133. siada/services/file_recommendation/core/file_discovery.py +353 -0
  134. siada/services/file_recommendation/core/suggestion.py +110 -0
  135. siada/services/file_recommendation/utils/__init__.py +22 -0
  136. siada/services/file_recommendation/utils/path_utils.py +138 -0
  137. siada/services/file_recommendation/utils/text_utils.py +253 -0
  138. siada/services/fix_result_check.py +377 -0
  139. siada/services/handle_at_command/README.md +247 -0
  140. siada/services/handle_at_command/__init__.py +115 -0
  141. siada/services/handle_at_command/exceptions.py +46 -0
  142. siada/services/handle_at_command/models.py +92 -0
  143. siada/services/handle_at_command/parser.py +189 -0
  144. siada/services/handle_at_command/processor.py +321 -0
  145. siada/services/handle_at_command/resolver.py +318 -0
  146. siada/services/handle_at_command/utils.py +345 -0
  147. siada/services/model_info_service.py +103 -0
  148. siada/services/siada_runner.py +170 -0
  149. siada/session/__init__.py +29 -0
  150. siada/session/session_manager.py +66 -0
  151. siada/session/session_models.py +43 -0
  152. siada/support/__init__.py +0 -0
  153. siada/support/completer.py +204 -0
  154. siada/support/editor.py +146 -0
  155. siada/support/envprocessor.py +54 -0
  156. siada/support/repo.py +51 -0
  157. siada/support/slash_commands.py +410 -0
  158. siada/support/spinner.py +320 -0
  159. siada/tools/__init__.py +0 -0
  160. siada/tools/ast/README.md +345 -0
  161. siada/tools/ast/ast_tool.py +341 -0
  162. siada/tools/ast/models.py +11 -0
  163. siada/tools/coder/__init__.py +0 -0
  164. siada/tools/coder/ask_followup_question.py +70 -0
  165. siada/tools/coder/cmd_runner.py +132 -0
  166. siada/tools/coder/file_operator.py +284 -0
  167. siada/tools/coder/file_search/README.md +223 -0
  168. siada/tools/coder/file_search/__init__.py +10 -0
  169. siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
  170. siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
  171. siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
  172. siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
  173. siada/tools/coder/file_search/bin/rg.exe +0 -0
  174. siada/tools/coder/file_search/search.py +437 -0
  175. siada/tools/coder/file_search/setup_package.py +150 -0
  176. siada/tools/coder/file_search/test_search.py +142 -0
  177. siada/tools/coder/files.py +147 -0
  178. siada/tools/coder/fix_attempt_completion.py +58 -0
  179. siada/tools/coder/issue_review_completion.py +29 -0
  180. siada/tools/coder/observation/__init__.py +0 -0
  181. siada/tools/coder/observation/error.py +22 -0
  182. siada/tools/coder/observation/file_observation.py +216 -0
  183. siada/tools/coder/observation/observation.py +71 -0
  184. siada/tools/coder/repo_map/__init__.py +157 -0
  185. siada/tools/coder/repo_map/dump.py +29 -0
  186. siada/tools/coder/repo_map/io.py +226 -0
  187. siada/tools/coder/repo_map/repo_map.py +881 -0
  188. siada/tools/coder/repo_map/special.py +203 -0
  189. siada/tools/coder/repo_map/token_counter.py +154 -0
  190. siada/tools/coder/repo_map/waiting.py +221 -0
  191. siada/tools/coder/reproduce_completion.py +39 -0
  192. siada/tools/coder/run_cmd.py +60 -0
  193. siada/tools/coder/test_completion.py +25 -0
  194. siada/tools/coder/tool_docs.py +44 -0
  195. siada/tools/compression_tool.py +200 -0
  196. siada/tools/read_many_files/README.md +220 -0
  197. siada/tools/read_many_files/__init__.py +25 -0
  198. siada/tools/read_many_files/file_processor.py +486 -0
  199. siada/tools/read_many_files/filters.py +326 -0
  200. siada/tools/read_many_files/formatters.py +204 -0
  201. siada/tools/read_many_files/models.py +189 -0
  202. siada/tools/read_many_files_tool.py +233 -0
  203. siada/tools/tool_call_format/__init__.py +55 -0
  204. siada/tools/tool_call_format/file_to_language.py +231 -0
  205. siada/tools/tool_call_format/formatter_factory.py +80 -0
  206. siada/tools/tool_call_format/formatters.py +282 -0
  207. siada/tools/tool_call_format/tool_call_formatter.py +54 -0
  208. siada/tools/web/__init__.py +0 -0
  209. siada/tools/web/web_crawl.py +208 -0
  210. siada/utils.py +194 -0
  211. siada_cli-0.0.1.dist-info/LICENSE +201 -0
  212. siada_cli-0.0.1.dist-info/METADATA +457 -0
  213. siada_cli-0.0.1.dist-info/RECORD +215 -0
  214. siada_cli-0.0.1.dist-info/WHEEL +4 -0
  215. siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,157 @@
1
+ """
2
+ Repo Map模块 - 代码仓库地图生成工具
3
+
4
+ 该模块提供了生成代码仓库地图的完整功能,包括:
5
+ - 代码文件分析和标签提取
6
+ - 基于PageRank算法的文件重要性排序
7
+ - 智能的代码结构展示
8
+ - 高性能的token计算
9
+ - 灵活的IO处理
10
+
11
+ 主要组件:
12
+ - RepoMap: 核心仓库地图生成器
13
+ - IO: 标准输入输出处理器
14
+ - TokenCounterModel: token计算模型
15
+ - Tag: 代码标签数据结构
16
+ """
17
+
18
+ from .repo_map import RepoMap, Tag
19
+ from .io import IO, SilentIO, FileIO
20
+ from .token_counter import TokenCounterModel, OptimizedTokenCounterModel
21
+ from .dump import dump
22
+ from .special import filter_important_files, is_important
23
+ from .waiting import Spinner, WaitingSpinner
24
+
25
+ __all__ = [
26
+ # 核心类
27
+ 'RepoMap',
28
+ 'Tag',
29
+
30
+ # IO类
31
+ 'IO',
32
+ 'SilentIO',
33
+ 'FileIO',
34
+
35
+ # Token计算类
36
+ 'TokenCounterModel',
37
+ 'OptimizedTokenCounterModel',
38
+
39
+ # 工具函数
40
+ 'dump',
41
+ 'filter_important_files',
42
+ 'is_important',
43
+
44
+ # 等待/进度指示器
45
+ 'Spinner',
46
+ 'WaitingSpinner',
47
+ ]
48
+
49
+ # 版本信息
50
+ __version__ = '1.0.0'
51
+
52
+ # 模块级别的便捷函数
53
+ def create_repo_map(
54
+ root_path: str,
55
+ model_name: str = "claude-3-5-sonnet-20241022",
56
+ verbose: bool = False,
57
+ map_tokens: int = 1024,
58
+ **kwargs
59
+ ) -> RepoMap:
60
+ """
61
+ 创建RepoMap实例的便捷函数
62
+
63
+ Args:
64
+ root_path (str): 仓库根目录路径
65
+ model_name (str): 语言模型名称,默认为Claude 3.5 Sonnet
66
+ verbose (bool): 是否启用详细输出
67
+ map_tokens (int): 地图最大token数量
68
+ **kwargs: 其他RepoMap参数
69
+
70
+ Returns:
71
+ RepoMap: 配置好的RepoMap实例
72
+
73
+ Example:
74
+ >>> repo_map = create_repo_map("/path/to/repo", verbose=True)
75
+ >>> result = repo_map.get_repo_map(chat_files=[], other_files=python_files)
76
+ """
77
+ io = IO(verbose=verbose)
78
+ model = TokenCounterModel(model_name)
79
+
80
+ return RepoMap(
81
+ root=root_path,
82
+ main_model=model,
83
+ io=io,
84
+ verbose=verbose,
85
+ map_tokens=map_tokens,
86
+ **kwargs
87
+ )
88
+
89
+
90
+ def create_optimized_repo_map(
91
+ root_path: str,
92
+ model_name: str = "claude-3-5-sonnet-20241022",
93
+ verbose: bool = False,
94
+ map_tokens: int = 8192,
95
+ sampling_threshold: int = 10000,
96
+ **kwargs
97
+ ) -> RepoMap:
98
+ """
99
+ 创建优化版RepoMap实例的便捷函数
100
+
101
+ 适用于大型代码仓库,使用优化的token计算器
102
+
103
+ Args:
104
+ root_path (str): 仓库根目录路径
105
+ model_name (str): 语言模型名称
106
+ verbose (bool): 是否启用详细输出
107
+ map_tokens (int): 地图最大token数量,默认8192
108
+ sampling_threshold (int): 采样阈值
109
+ **kwargs: 其他RepoMap参数
110
+
111
+ Returns:
112
+ RepoMap: 配置好的优化版RepoMap实例
113
+ """
114
+ io = IO(verbose=verbose)
115
+ model = OptimizedTokenCounterModel(model_name, sampling_threshold)
116
+
117
+ return RepoMap(
118
+ root=root_path,
119
+ main_model=model,
120
+ io=io,
121
+ verbose=verbose,
122
+ map_tokens=map_tokens,
123
+ **kwargs
124
+ )
125
+
126
+
127
+ def create_silent_repo_map(
128
+ root_path: str,
129
+ model_name: str = "claude-3-5-sonnet-20241022",
130
+ map_tokens: int = 1024,
131
+ **kwargs
132
+ ) -> RepoMap:
133
+ """
134
+ 创建静默版RepoMap实例的便捷函数
135
+
136
+ 适用于测试或需要静默运行的场景
137
+
138
+ Args:
139
+ root_path (str): 仓库根目录路径
140
+ model_name (str): 语言模型名称
141
+ map_tokens (int): 地图最大token数量
142
+ **kwargs: 其他RepoMap参数
143
+
144
+ Returns:
145
+ RepoMap: 配置好的静默版RepoMap实例
146
+ """
147
+ io = SilentIO()
148
+ model = TokenCounterModel(model_name)
149
+
150
+ return RepoMap(
151
+ root=root_path,
152
+ main_model=model,
153
+ io=io,
154
+ verbose=False,
155
+ map_tokens=map_tokens,
156
+ **kwargs
157
+ )
@@ -0,0 +1,29 @@
1
+ import json
2
+ import traceback
3
+
4
+
5
+ def cvt(s):
6
+ if isinstance(s, str):
7
+ return s
8
+ try:
9
+ return json.dumps(s, indent=4)
10
+ except TypeError:
11
+ return str(s)
12
+
13
+
14
+ def dump(*vals):
15
+ # http://docs.python.org/library/traceback.html
16
+ stack = traceback.extract_stack()
17
+ vars = stack[-2][3]
18
+
19
+ # strip away the call to dump()
20
+ vars = "(".join(vars.split("(")[1:])
21
+ vars = ")".join(vars.split(")")[:-1])
22
+
23
+ vals = [cvt(v) for v in vals]
24
+ has_newline = sum(1 for v in vals if "\n" in v)
25
+ if has_newline:
26
+ print("%s:" % vars)
27
+ print(", ".join(vals))
28
+ else:
29
+ print("%s:" % vars, ", ".join(vals))
@@ -0,0 +1,226 @@
1
+ """
2
+ IO类 - 提供标准的输入输出接口
3
+
4
+ 该模块提供了RepoMap使用的标准IO接口,包括:
5
+ - 不同级别的日志输出(info, warning, error)
6
+ - 文件读取功能
7
+ - 可配置的输出选项
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ from typing import Optional, TextIO
13
+ from pathlib import Path
14
+
15
+
16
+ class IO:
17
+ """
18
+ 标准IO类,提供文件读取和日志输出功能
19
+
20
+ 该类为RepoMap提供统一的IO接口,支持:
21
+ - 多级别日志输出
22
+ - 文件内容读取
23
+ - 可配置的输出目标
24
+ - 详细模式控制
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ verbose: bool = False,
30
+ output_stream: Optional[TextIO] = None,
31
+ error_stream: Optional[TextIO] = None
32
+ ):
33
+ """
34
+ 初始化IO实例
35
+
36
+ Args:
37
+ verbose (bool): 是否启用详细输出模式
38
+ output_stream (TextIO, optional): 标准输出流,默认为sys.stdout
39
+ error_stream (TextIO, optional): 错误输出流,默认为sys.stderr
40
+ """
41
+ self.verbose = verbose
42
+ self.output_stream = output_stream or sys.stdout
43
+ self.error_stream = error_stream or sys.stderr
44
+
45
+ # 统计信息
46
+ self.outputs = []
47
+ self.warnings = []
48
+ self.errors = []
49
+
50
+ def tool_output(self, message: str) -> None:
51
+ """
52
+ 输出信息消息
53
+
54
+ Args:
55
+ message (str): 要输出的消息
56
+ """
57
+ self.outputs.append(message)
58
+ if self.verbose:
59
+ print(f"[INFO] {message}", file=self.output_stream)
60
+
61
+ def tool_warning(self, message: str) -> None:
62
+ """
63
+ 输出警告消息
64
+
65
+ Args:
66
+ message (str): 要输出的警告消息
67
+ """
68
+ self.warnings.append(message)
69
+ print(f"[WARNING] {message}", file=self.error_stream)
70
+
71
+ def tool_error(self, message: str) -> None:
72
+ """
73
+ 输出错误消息
74
+
75
+ Args:
76
+ message (str): 要输出的错误消息
77
+ """
78
+ self.errors.append(message)
79
+ print(f"[ERROR] {message}", file=self.error_stream)
80
+
81
+ def read_text(self, filepath: str) -> str:
82
+ """
83
+ 读取文件内容
84
+
85
+ Args:
86
+ filepath (str): 文件路径
87
+
88
+ Returns:
89
+ str: 文件内容,如果读取失败则返回空字符串
90
+ """
91
+ try:
92
+ # 确保路径是绝对路径或相对于当前工作目录的路径
93
+ path = Path(filepath)
94
+
95
+ # 尝试不同的编码方式
96
+ encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb2312', 'latin1']
97
+
98
+ for encoding in encodings:
99
+ try:
100
+ with open(path, 'r', encoding=encoding) as f:
101
+ content = f.read()
102
+ return content
103
+ except UnicodeDecodeError:
104
+ continue
105
+
106
+ # 如果所有编码都失败,尝试二进制模式读取并忽略错误
107
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
108
+ content = f.read()
109
+
110
+ if self.verbose:
111
+ self.tool_warning(f"文件 {filepath} 使用fallback编码读取")
112
+
113
+ return content
114
+
115
+ except FileNotFoundError:
116
+ self.tool_error(f"文件不存在: {filepath}")
117
+ return ""
118
+ except PermissionError:
119
+ self.tool_error(f"没有权限读取文件: {filepath}")
120
+ return ""
121
+ except IsADirectoryError:
122
+ self.tool_error(f"路径是目录而不是文件: {filepath}")
123
+ return ""
124
+ except Exception as e:
125
+ self.tool_error(f"读取文件失败 {filepath}: {str(e)}")
126
+ return ""
127
+
128
+ def clear_stats(self) -> None:
129
+ """清除统计信息"""
130
+ self.outputs.clear()
131
+ self.warnings.clear()
132
+ self.errors.clear()
133
+
134
+ def get_stats(self) -> dict:
135
+ """
136
+ 获取统计信息
137
+
138
+ Returns:
139
+ dict: 包含输出、警告、错误数量的字典
140
+ """
141
+ return {
142
+ 'outputs': len(self.outputs),
143
+ 'warnings': len(self.warnings),
144
+ 'errors': len(self.errors)
145
+ }
146
+
147
+ def set_verbose(self, verbose: bool) -> None:
148
+ """
149
+ 设置详细模式
150
+
151
+ Args:
152
+ verbose (bool): 是否启用详细输出
153
+ """
154
+ self.verbose = verbose
155
+
156
+
157
+ class SilentIO(IO):
158
+ """
159
+ 静默IO类,不输出任何信息到控制台
160
+
161
+ 适用于测试或需要静默运行的场景
162
+ """
163
+
164
+ def __init__(self):
165
+ """初始化静默IO实例"""
166
+ super().__init__(verbose=False)
167
+
168
+ def tool_output(self, message: str) -> None:
169
+ """静默记录输出消息"""
170
+ self.outputs.append(message)
171
+
172
+ def tool_warning(self, message: str) -> None:
173
+ """静默记录警告消息"""
174
+ self.warnings.append(message)
175
+
176
+ def tool_error(self, message: str) -> None:
177
+ """静默记录错误消息"""
178
+ self.errors.append(message)
179
+
180
+
181
+ class FileIO(IO):
182
+ """
183
+ 文件IO类,将输出重定向到文件
184
+
185
+ 适用于需要将日志保存到文件的场景
186
+ """
187
+
188
+ def __init__(
189
+ self,
190
+ log_file: str,
191
+ verbose: bool = True,
192
+ append: bool = True
193
+ ):
194
+ """
195
+ 初始化文件IO实例
196
+
197
+ Args:
198
+ log_file (str): 日志文件路径
199
+ verbose (bool): 是否启用详细输出
200
+ append (bool): 是否追加到现有文件
201
+ """
202
+ self.log_file = Path(log_file)
203
+ self.append = append
204
+
205
+ # 确保日志目录存在
206
+ self.log_file.parent.mkdir(parents=True, exist_ok=True)
207
+
208
+ # 打开日志文件
209
+ mode = 'a' if append else 'w'
210
+ self.log_stream = open(self.log_file, mode, encoding='utf-8')
211
+
212
+ super().__init__(
213
+ verbose=verbose,
214
+ output_stream=self.log_stream,
215
+ error_stream=self.log_stream
216
+ )
217
+
218
+ def __del__(self):
219
+ """析构函数,确保文件被正确关闭"""
220
+ if hasattr(self, 'log_stream') and not self.log_stream.closed:
221
+ self.log_stream.close()
222
+
223
+ def close(self):
224
+ """手动关闭日志文件"""
225
+ if hasattr(self, 'log_stream') and not self.log_stream.closed:
226
+ self.log_stream.close()