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,458 @@
1
+ import json
2
+ from dataclasses import dataclass, field
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from agents.tracing import TracingProcessor
7
+
8
+
9
+ @dataclass
10
+ class TraceState:
11
+ """Track state information for a single Trace"""
12
+ trace_id: str
13
+ message_count: int = 0 # Number of printed messages
14
+ agent_history: List[str] = field(default_factory=list) # Agent switching history
15
+ start_time: datetime = field(default_factory=datetime.now)
16
+ handoff_count: int = 0 # Number of handoffs
17
+ model_call_count: int = 0 # Number of model calls
18
+ tool_call_count: int = 0 # Number of tool calls
19
+ total_input_tokens: int = 0 # Total input tokens
20
+ total_output_tokens: int = 0 # Total output tokens
21
+ total_tokens: int = 0
22
+
23
+
24
+ class LoggerTracingProcessor(TracingProcessor):
25
+ """
26
+ Detailed reasoning process logger
27
+
28
+ This TracingProcessor provides detailed visualization of the reasoning process, including:
29
+ - Incremental input and complete output of model calls
30
+ - Input/output details of tool calls
31
+ - Complete record of Agent handoffs
32
+ - Lifecycle tracking of the entire reasoning process
33
+
34
+ Usage:
35
+ from agents.tracing import add_trace_processor
36
+ from examples.tracing.logger_tracing_processor import LoggerTracingProcessor
37
+
38
+ # Register the processor
39
+ add_trace_processor(LoggerTracingProcessor())
40
+
41
+ # Then run the Agent normally
42
+ result = await Runner.run(agent=your_agent, input="your input")
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ show_model_calls: bool = True,
48
+ show_tool_calls: bool = True,
49
+ show_handoffs: bool = True,
50
+ show_trace_lifecycle: bool = True,
51
+ show_timestamps: bool = True,
52
+ show_system_messages: bool = False,
53
+ use_colors: bool = True,
54
+ console_output: bool = True,
55
+ output_file: Optional[str] = None,
56
+ indent_level: int = 0
57
+ ):
58
+ """
59
+ Initialize the logger
60
+
61
+ Args:
62
+ show_model_calls: Whether to show model calls
63
+ show_tool_calls: Whether to show tool calls
64
+ show_handoffs: Whether to show Agent handoffs
65
+ show_trace_lifecycle: Whether to show Trace lifecycle
66
+ show_timestamps: Whether to show timestamps
67
+ show_system_messages: Whether to show system messages (default: False)
68
+ use_colors: Whether to use colored output
69
+ console_output: Whether to output to console (default: True)
70
+ output_file: Optional output file path
71
+ indent_level: Indentation level
72
+ """
73
+ self.show_model_calls = show_model_calls
74
+ self.show_tool_calls = show_tool_calls
75
+ self.show_handoffs = show_handoffs
76
+ self.show_trace_lifecycle = show_trace_lifecycle
77
+ self.show_timestamps = show_timestamps
78
+ self.show_system_messages = show_system_messages
79
+ self.use_colors = use_colors
80
+ self.console_output = console_output
81
+ self.output_file = output_file
82
+ self.indent_level = indent_level
83
+
84
+ # State tracking
85
+ self.trace_states: Dict[str, TraceState] = {}
86
+
87
+ # Color definitions
88
+ self.colors = {
89
+ 'trace': '\033[95m', # Purple
90
+ 'model': '\033[94m', # Blue
91
+ 'tool': '\033[92m', # Green
92
+ 'handoff': '\033[93m', # Yellow
93
+ 'input': '\033[96m', # Cyan
94
+ 'output': '\033[91m', # Red
95
+ 'reset': '\033[0m', # Reset
96
+ 'bold': '\033[1m', # Bold
97
+ } if use_colors else {k: '' for k in ['trace', 'model', 'tool', 'handoff', 'input', 'output', 'reset', 'bold']}
98
+
99
+ def _print(self, message: str) -> None:
100
+ """Print message with file output support"""
101
+ indent = " " * self.indent_level
102
+ full_message = f"{indent}{message}"
103
+
104
+ # Print to console only if console_output is enabled
105
+ if self.console_output:
106
+ print(full_message)
107
+
108
+ if self.output_file:
109
+ try:
110
+ with open(self.output_file, 'a', encoding='utf-8') as f:
111
+ # Remove color codes for file output
112
+ clean_message = full_message
113
+ for color in self.colors.values():
114
+ clean_message = clean_message.replace(color, '')
115
+ f.write(clean_message + '\n')
116
+ except Exception as e:
117
+ # Only print warning to console if console output is enabled
118
+ if self.console_output:
119
+ print(f"Warning: Failed to write to file {self.output_file}: {e}")
120
+
121
+ def _truncate_content(self, content: str, max_length: int = 500) -> str:
122
+ """Truncate overly long content"""
123
+ if len(content) <= max_length:
124
+ return content
125
+ return content[:max_length] + "..."
126
+
127
+ def _format_timestamp(self) -> str:
128
+ """Format timestamp"""
129
+ if not self.show_timestamps:
130
+ return ""
131
+ return f"🕐 {datetime.now().strftime('%H:%M:%S')} "
132
+
133
+
134
+ def _format_json(self, data: Any) -> str:
135
+ """Format JSON data"""
136
+ try:
137
+ if isinstance(data, str):
138
+ return data
139
+ return json.dumps(data, ensure_ascii=False, indent=2)
140
+ except Exception:
141
+ return data
142
+
143
+ def _print_incremental_messages(self, trace_id: str, messages: List[Dict[str, Any]]) -> None:
144
+ """Print message list incrementally"""
145
+ if not messages:
146
+ return
147
+
148
+ state = self.trace_states.get(trace_id)
149
+ if not state:
150
+ return
151
+
152
+ # Only print new messages
153
+ new_messages = messages[state.message_count:]
154
+ if not new_messages:
155
+ return
156
+
157
+ # Pre-filter messages to check if we have any to display
158
+ messages_to_display = []
159
+ for msg in new_messages:
160
+ role = msg.get('role', 'unknown')
161
+ # Skip system messages if show_system_messages is False
162
+ if role == 'system' and not self.show_system_messages:
163
+ continue
164
+ messages_to_display.append(msg)
165
+
166
+ # Only print header if we have messages to display
167
+ if not messages_to_display:
168
+ # Update the count even if no messages are displayed
169
+ state.message_count = len(messages)
170
+ return
171
+
172
+ self._print(f"{self.colors['input']}📥 New Input Messages:{self.colors['reset']}")
173
+
174
+ for msg in messages_to_display:
175
+ role = msg.get('role', 'unknown')
176
+ content = msg.get('content', '')
177
+
178
+ # Handle different types of content
179
+ if isinstance(content, list):
180
+ # Multimodal content
181
+ content_summary = []
182
+ for item in content:
183
+ if isinstance(item, dict):
184
+ if item.get('type') == 'text':
185
+ content_summary.append(f"text: {item.get('text', '')}")
186
+
187
+ elif item.get('type') == 'image_url':
188
+ content_summary.append("image: [image data]")
189
+ else:
190
+ content_summary.append(f"{item.get('type', 'unknown')}: [data]")
191
+ content_str = " | ".join(content_summary)
192
+ else:
193
+ content_str = content
194
+
195
+ self._print(f" [{role}]: {content_str}")
196
+
197
+ # Update the count of printed messages
198
+ state.message_count = len(messages)
199
+
200
+ def _format_model_output(self, output: List[Dict[str, Any]]) -> None:
201
+ """Format model output"""
202
+ self._print(f"{self.colors['output']}📤 Model Output:{self.colors['reset']}")
203
+
204
+ for item in output:
205
+ if isinstance(item, dict):
206
+ # First check if there's a role field, which usually indicates a message
207
+ if 'role' in item:
208
+ role = item.get('role', 'assistant')
209
+ content = item.get('content', '')
210
+ self._print(f" [{role}]: {content}")
211
+ continue
212
+
213
+ # Check if there's a type field
214
+ item_type = item.get('type')
215
+ if item_type == 'message':
216
+ # Message output
217
+ role = item.get('role', 'assistant')
218
+ content = item.get('content', '')
219
+ self._print(f" [{role}]: {content}")
220
+
221
+ elif item_type == 'function_call':
222
+ # Tool call
223
+ name = item.get('name', 'unknown')
224
+ args = item.get('arguments', {})
225
+ self._print(f" 🔧 Tool Call: {name}({self._format_json(args)})")
226
+
227
+ elif item_type:
228
+ # Other types with explicit type
229
+ self._print(f" [{item_type}]: {self._format_json(item)}")
230
+
231
+ else:
232
+ # No type field, try to identify intelligently
233
+ if 'content' in item:
234
+ # Looks like a message
235
+ role = item.get('role', 'assistant')
236
+ content = item.get('content', '')
237
+ self._print(f" [{role}]: {content}")
238
+ elif 'name' in item and 'arguments' in item:
239
+ # Looks like a function call
240
+ name = item.get('name', 'unknown')
241
+ args = item.get('arguments', {})
242
+ self._print(f" 🔧 Tool Call: {name}({self._format_json(args)})")
243
+ else:
244
+ # Unrecognized structure, display as data
245
+ self._print(f" [data]: {self._format_json(item)}")
246
+ else:
247
+ self._print(f" {self._format_json(item)}")
248
+
249
+ def on_trace_start(self, trace) -> None:
250
+ """Callback when Trace starts"""
251
+ if not self.show_trace_lifecycle:
252
+ return
253
+
254
+ # Record Trace state
255
+ self.trace_states[trace.trace_id] = TraceState(
256
+ trace_id=trace.trace_id,
257
+ start_time=datetime.now()
258
+ )
259
+
260
+ self._print(f"\n{self.colors['trace']}{self.colors['bold']}🚀 === TRACE STARTED ==={self.colors['reset']}")
261
+ self._print(f"{self.colors['trace']}Workflow: {trace.name}{self.colors['reset']}")
262
+ self._print(f"{self.colors['trace']}Trace ID: {trace.trace_id}{self.colors['reset']}")
263
+ if trace.group_id:
264
+ self._print(f"{self.colors['trace']}Group ID: {trace.group_id}{self.colors['reset']}")
265
+ self._print(f"{self.colors['trace']}Started: {self._format_timestamp()}{self.colors['reset']}")
266
+ self._print(f"{self.colors['trace']}========================={self.colors['reset']}\n")
267
+
268
+ def on_trace_end(self, trace) -> None:
269
+ """Callback when Trace ends"""
270
+ if not self.show_trace_lifecycle:
271
+ return
272
+
273
+ state = self.trace_states.get(trace.trace_id)
274
+ if state:
275
+ duration = datetime.now() - state.start_time
276
+
277
+ self._print(f"\n{self.colors['trace']}{self.colors['bold']}🏁 === TRACE ENDED ==={self.colors['reset']}")
278
+ self._print(f"{self.colors['trace']}Workflow: {trace.name}{self.colors['reset']}")
279
+ self._print(f"{self.colors['trace']}Duration: {duration.total_seconds():.1f}s{self.colors['reset']}")
280
+ self._print(f"{self.colors['trace']}Model Calls: {state.model_call_count}{self.colors['reset']}")
281
+ self._print(f"{self.colors['trace']}Tool Calls: {state.tool_call_count}{self.colors['reset']}")
282
+ self._print(f"{self.colors['trace']}Handoffs: {state.handoff_count}{self.colors['reset']}")
283
+ self._print(f"{self.colors['trace']}Tokens: Input={state.total_input_tokens}, Output={state.total_output_tokens}, Total={state.total_tokens}{self.colors['reset']}")
284
+
285
+ self._print(f"{self.colors['trace']}======================{self.colors['reset']}\n")
286
+
287
+ # Clean up state
288
+ del self.trace_states[trace.trace_id]
289
+
290
+ def on_span_start(self, span) -> None:
291
+ """Span 开始时的回调"""
292
+ span_type = span.span_data.type
293
+ trace_id = span.trace_id
294
+ state = self.trace_states.get(trace_id)
295
+
296
+ if span_type == "generation" and self.show_model_calls:
297
+ # Update count when span starts
298
+ if state:
299
+ state.model_call_count += 1
300
+
301
+ # Handle the start of model generation Span
302
+ data = span.span_data
303
+ call_num = state.model_call_count if state else "?"
304
+
305
+ self._print(f"\n{self.colors['model']}{self.colors['bold']}🤖 === MODEL CALL {call_num} ==={self.colors['reset']}")
306
+ self._print(f"{self.colors['model']}{self._format_timestamp()}Model: {data.model or 'unknown'}{self.colors['reset']}")
307
+
308
+ # Print incremental input messages
309
+ if data.input and state:
310
+ self._print_incremental_messages(span.trace_id, data.input)
311
+
312
+ def on_span_end(self, span) -> None:
313
+ """Span 结束时的回调"""
314
+ span_type = span.span_data.type
315
+ trace_id = span.trace_id
316
+
317
+ # 更新状态计数
318
+ state = self.trace_states.get(trace_id)
319
+ if state:
320
+ if span_type == "generation":
321
+ # generation 的计数已在 on_span_start 中更新
322
+ pass
323
+ elif span_type == "function":
324
+ state.tool_call_count += 1
325
+ elif span_type == "handoff":
326
+ state.handoff_count += 1
327
+
328
+ if span_type == "generation" and self.show_model_calls:
329
+ self._handle_generation_span(span, state)
330
+ elif span_type == "function" and self.show_tool_calls:
331
+ self._handle_function_span(span, state)
332
+ elif span_type == "handoff" and self.show_handoffs:
333
+ self._handle_handoff_span(span, state)
334
+
335
+ def _handle_generation_span(self, span, state: Optional[TraceState]) -> None:
336
+ """Handle output when model generation Span ends"""
337
+ data = span.span_data
338
+
339
+ # Print model output
340
+ if data.output:
341
+ self._format_model_output(data.output)
342
+
343
+ # Print usage statistics
344
+ if data.usage:
345
+ usage = data.usage
346
+ input_tokens = usage.get('input_tokens', 0)
347
+ output_tokens = usage.get('output_tokens', 0)
348
+ total_tokens = usage.get('total_tokens', 0)
349
+ self._print(f"{self.colors['model']}📊 Usage: Input={usage.get('input_tokens', 0)}, Output={usage.get('output_tokens', 0)}, Total={usage.get('total_tokens', 0)}{self.colors['reset']}")
350
+ if state:
351
+ state.total_input_tokens += input_tokens
352
+ state.total_output_tokens += output_tokens
353
+ state.total_tokens += total_tokens
354
+ self._print(f"{self.colors['model']}==================={self.colors['reset']}")
355
+
356
+ def _handle_function_span(self, span, state: Optional[TraceState]) -> None:
357
+ """处理函数调用 Span"""
358
+ data = span.span_data
359
+
360
+ call_num = state.tool_call_count if state else "?"
361
+ self._print(f"\n{self.colors['tool']}{self.colors['bold']}🔧 === TOOL CALL {call_num} ==={self.colors['reset']}")
362
+ self._print(f"{self.colors['tool']}{self._format_timestamp()}Function: {data.name}{self.colors['reset']}")
363
+
364
+ # 打印输入
365
+ if data.input:
366
+ self._print(f"{self.colors['input']}📥 Input: {self._format_json(data.input)}{self.colors['reset']}")
367
+
368
+ # 打印输出
369
+ if data.output is not None:
370
+ self._print(f"{self.colors['output']}📤 Output: {self._format_json(data.output)}{self.colors['reset']}")
371
+
372
+ # 打印 MCP 数据(如果有)
373
+ if data.mcp_data:
374
+ self._print(f"{self.colors['tool']}🔗 MCP Data: {self._format_json(data.mcp_data)}{self.colors['reset']}")
375
+
376
+ self._print(f"{self.colors['tool']}==============={self.colors['reset']}")
377
+
378
+ def _handle_handoff_span(self, span, state: Optional[TraceState]) -> None:
379
+ """Handle Handoff Span end"""
380
+ data = span.span_data
381
+
382
+ handoff_num = state.handoff_count if state else "?"
383
+
384
+ # Update Agent history
385
+ if state and data.to_agent:
386
+ if data.to_agent not in state.agent_history:
387
+ state.agent_history.append(data.to_agent)
388
+
389
+ self._print(f"{self.colors['handoff']}=================={self.colors['reset']}")
390
+
391
+ def shutdown(self) -> None:
392
+ """Shutdown the processor"""
393
+ if self.trace_states:
394
+ self._print("Warning: Some traces were not properly ended")
395
+ self.trace_states.clear()
396
+
397
+ def force_flush(self) -> None:
398
+ """Force flush the buffer"""
399
+ # For console output, usually no special handling is needed
400
+ pass
401
+
402
+
403
+ # Convenient factory functions
404
+ def create_simple_logger(console_output: bool = True) -> LoggerTracingProcessor:
405
+ """Create a simple logger"""
406
+ return LoggerTracingProcessor(
407
+ show_model_calls=True,
408
+ show_tool_calls=True,
409
+ show_handoffs=True,
410
+ show_trace_lifecycle=True,
411
+ use_colors=True,
412
+ console_output=console_output
413
+ )
414
+
415
+
416
+ def create_detailed_logger(output_file: Optional[str] = None, console_output: bool = True) -> LoggerTracingProcessor:
417
+ """Create a detailed logger"""
418
+ # If no output file is specified, use the default log file path
419
+ if output_file is None:
420
+ import os
421
+ from datetime import datetime
422
+
423
+ # Create log directory (unified across all platforms)
424
+ from pathlib import Path
425
+ log_dir = Path.home() / ".siada-cli" / "logs"
426
+ log_dir.mkdir(parents=True, exist_ok=True)
427
+
428
+ # Generate log file name
429
+ date_str = datetime.now().strftime("%Y%m%d")
430
+ output_file = log_dir / f"agent_trace-{date_str}.log"
431
+
432
+ return LoggerTracingProcessor(
433
+ show_model_calls=True,
434
+ show_tool_calls=True,
435
+ show_handoffs=True,
436
+ show_trace_lifecycle=True,
437
+ show_timestamps=True,
438
+ use_colors=True,
439
+ console_output=console_output,
440
+ output_file=output_file
441
+ )
442
+
443
+
444
+ def create_file_only_logger(output_file: Optional[str] = None) -> LoggerTracingProcessor:
445
+ """Create a logger that only writes to file (no console output)"""
446
+ return create_detailed_logger(output_file=output_file, console_output=False)
447
+
448
+
449
+ def create_minimal_logger(console_output: bool = True) -> LoggerTracingProcessor:
450
+ """Create a minimal logger"""
451
+ return LoggerTracingProcessor(
452
+ show_model_calls=True,
453
+ show_tool_calls=False,
454
+ show_handoffs=True,
455
+ show_trace_lifecycle=False,
456
+ use_colors=False,
457
+ console_output=console_output
458
+ )
File without changes
@@ -0,0 +1,30 @@
1
+ from agents import Agent, RunContextWrapper, RunHooks, Runner, TContext, Tool
2
+ from siada.foundation.code_agent_context import CodeAgentContext
3
+
4
+
5
+
6
+ class IoRunHooks(RunHooks):
7
+
8
+
9
+ def __init__(self):
10
+ super().__init__()
11
+
12
+ async def on_tool_start(
13
+ self,
14
+ context: RunContextWrapper[CodeAgentContext],
15
+ agent: Agent[TContext],
16
+ tool: Tool,
17
+ ) -> None:
18
+ ## implements until this function args contains arguments
19
+ pass
20
+
21
+ async def on_tool_end(
22
+ self,
23
+ context: RunContextWrapper[CodeAgentContext],
24
+ agent: Agent[TContext],
25
+ tool: Tool,
26
+ result: str,
27
+ ) -> None:
28
+ pass
29
+
30
+