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.
- agent_config.yaml +34 -0
- siada/__init__.py +13 -0
- siada/agent_hub/__init__.py +10 -0
- siada/agent_hub/coder/bug_fix_agent.py +398 -0
- siada/agent_hub/coder/code_gen_agent.py +235 -0
- siada/agent_hub/coder/fe_gen_agent.py +37 -0
- siada/agent_hub/coder/issue_review_agent.py +68 -0
- siada/agent_hub/coder/prompt/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
- siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
- siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
- siada/agent_hub/coder/prompt/base/rules.py +40 -0
- siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
- siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
- siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
- siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
- siada/agent_hub/coder/tracing/__init__.py +22 -0
- siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
- siada/agent_hub/hooks/__init__.py +0 -0
- siada/agent_hub/hooks/io_hooks.py +30 -0
- siada/agent_hub/siada_agent.py +214 -0
- siada/config/__init__.py +0 -0
- siada/config/config_loader.py +67 -0
- siada/entrypoint/__init__.py +3 -0
- siada/entrypoint/args_parser/__init__.py +0 -0
- siada/entrypoint/args_parser/args.py +195 -0
- siada/entrypoint/interaction/__init__.py +0 -0
- siada/entrypoint/interaction/base_turn.py +107 -0
- siada/entrypoint/interaction/config.py +30 -0
- siada/entrypoint/interaction/controller.py +122 -0
- siada/entrypoint/interaction/nointeractive_controller.py +27 -0
- siada/entrypoint/interaction/turn/interface.py +116 -0
- siada/entrypoint/interaction/turn/models.py +34 -0
- siada/entrypoint/interaction/turn/run_turn.py +641 -0
- siada/entrypoint/interaction/turn_factory.py +35 -0
- siada/entrypoint/interaction/turn_models.py +28 -0
- siada/entrypoint/siadahub.py +413 -0
- siada/foundation/__init__.py +0 -0
- siada/foundation/code_agent_context.py +48 -0
- siada/foundation/config.py +59 -0
- siada/foundation/constants.py +2 -0
- siada/foundation/context.py +76 -0
- siada/foundation/logging.py +341 -0
- siada/foundation/tools/__init__.py +0 -0
- siada/foundation/tools/get_git_diff.py +105 -0
- siada/io/__init__.py +0 -0
- siada/io/banner.py +148 -0
- siada/io/color_settings.py +207 -0
- siada/io/color_utils.py +16 -0
- siada/io/components/__init__.py +0 -0
- siada/io/components/mdstream.py +204 -0
- siada/io/console_printer.py +68 -0
- siada/io/io.py +661 -0
- siada/io/key_bindings.py +129 -0
- siada/io/notification_command.py +40 -0
- siada/io/test_banner.py +64 -0
- siada/models/__init__.py +0 -0
- siada/models/agent.py +47 -0
- siada/models/coder/__init__.py +0 -0
- siada/models/converter.py +27 -0
- siada/models/model_base_config.py +121 -0
- siada/models/model_run_config.py +153 -0
- siada/provider/__init__.py +0 -0
- siada/provider/client_factory.py +112 -0
- siada/provider/llm_client.py +11 -0
- siada/provider/openrouter/__init__.py +0 -0
- siada/provider/openrouter/coverter.py +17 -0
- siada/provider/openrouter/openroute_provider.py +33 -0
- siada/provider/provider_factory.py +53 -0
- siada/queries/tree-sitter-language-pack/README.md +7 -0
- siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- siada/queries/tree-sitter-languages/README.md +23 -0
- siada/queries/tree-sitter-languages/c-tags.scm +9 -0
- siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
- siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
- siada/queries/tree-sitter-languages/go-tags.scm +30 -0
- siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- siada/queries/tree-sitter-languages/java-tags.scm +20 -0
- siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-languages/php-tags.scm +26 -0
- siada/queries/tree-sitter-languages/python-tags.scm +12 -0
- siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
- siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
- siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
- siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- siada/services/__init__.py +30 -0
- siada/services/at_file_recommendation_design_document.md +1213 -0
- siada/services/code_context_manager.py +136 -0
- siada/services/enhanced_fix_result_check.py +546 -0
- siada/services/execution_trace_collector.py +207 -0
- siada/services/file_recommendation/__init__.py +136 -0
- siada/services/file_recommendation/core/__init__.py +17 -0
- siada/services/file_recommendation/core/completion_engine.py +351 -0
- siada/services/file_recommendation/core/config.py +62 -0
- siada/services/file_recommendation/core/file_discovery.py +353 -0
- siada/services/file_recommendation/core/suggestion.py +110 -0
- siada/services/file_recommendation/utils/__init__.py +22 -0
- siada/services/file_recommendation/utils/path_utils.py +138 -0
- siada/services/file_recommendation/utils/text_utils.py +253 -0
- siada/services/fix_result_check.py +377 -0
- siada/services/handle_at_command/README.md +247 -0
- siada/services/handle_at_command/__init__.py +115 -0
- siada/services/handle_at_command/exceptions.py +46 -0
- siada/services/handle_at_command/models.py +92 -0
- siada/services/handle_at_command/parser.py +189 -0
- siada/services/handle_at_command/processor.py +321 -0
- siada/services/handle_at_command/resolver.py +318 -0
- siada/services/handle_at_command/utils.py +345 -0
- siada/services/model_info_service.py +103 -0
- siada/services/siada_runner.py +170 -0
- siada/session/__init__.py +29 -0
- siada/session/session_manager.py +66 -0
- siada/session/session_models.py +43 -0
- siada/support/__init__.py +0 -0
- siada/support/completer.py +204 -0
- siada/support/editor.py +146 -0
- siada/support/envprocessor.py +54 -0
- siada/support/repo.py +51 -0
- siada/support/slash_commands.py +410 -0
- siada/support/spinner.py +320 -0
- siada/tools/__init__.py +0 -0
- siada/tools/ast/README.md +345 -0
- siada/tools/ast/ast_tool.py +341 -0
- siada/tools/ast/models.py +11 -0
- siada/tools/coder/__init__.py +0 -0
- siada/tools/coder/ask_followup_question.py +70 -0
- siada/tools/coder/cmd_runner.py +132 -0
- siada/tools/coder/file_operator.py +284 -0
- siada/tools/coder/file_search/README.md +223 -0
- siada/tools/coder/file_search/__init__.py +10 -0
- siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
- siada/tools/coder/file_search/bin/rg.exe +0 -0
- siada/tools/coder/file_search/search.py +437 -0
- siada/tools/coder/file_search/setup_package.py +150 -0
- siada/tools/coder/file_search/test_search.py +142 -0
- siada/tools/coder/files.py +147 -0
- siada/tools/coder/fix_attempt_completion.py +58 -0
- siada/tools/coder/issue_review_completion.py +29 -0
- siada/tools/coder/observation/__init__.py +0 -0
- siada/tools/coder/observation/error.py +22 -0
- siada/tools/coder/observation/file_observation.py +216 -0
- siada/tools/coder/observation/observation.py +71 -0
- siada/tools/coder/repo_map/__init__.py +157 -0
- siada/tools/coder/repo_map/dump.py +29 -0
- siada/tools/coder/repo_map/io.py +226 -0
- siada/tools/coder/repo_map/repo_map.py +881 -0
- siada/tools/coder/repo_map/special.py +203 -0
- siada/tools/coder/repo_map/token_counter.py +154 -0
- siada/tools/coder/repo_map/waiting.py +221 -0
- siada/tools/coder/reproduce_completion.py +39 -0
- siada/tools/coder/run_cmd.py +60 -0
- siada/tools/coder/test_completion.py +25 -0
- siada/tools/coder/tool_docs.py +44 -0
- siada/tools/compression_tool.py +200 -0
- siada/tools/read_many_files/README.md +220 -0
- siada/tools/read_many_files/__init__.py +25 -0
- siada/tools/read_many_files/file_processor.py +486 -0
- siada/tools/read_many_files/filters.py +326 -0
- siada/tools/read_many_files/formatters.py +204 -0
- siada/tools/read_many_files/models.py +189 -0
- siada/tools/read_many_files_tool.py +233 -0
- siada/tools/tool_call_format/__init__.py +55 -0
- siada/tools/tool_call_format/file_to_language.py +231 -0
- siada/tools/tool_call_format/formatter_factory.py +80 -0
- siada/tools/tool_call_format/formatters.py +282 -0
- siada/tools/tool_call_format/tool_call_formatter.py +54 -0
- siada/tools/web/__init__.py +0 -0
- siada/tools/web/web_crawl.py +208 -0
- siada/utils.py +194 -0
- siada_cli-0.0.1.dist-info/LICENSE +201 -0
- siada_cli-0.0.1.dist-info/METADATA +457 -0
- siada_cli-0.0.1.dist-info/RECORD +215 -0
- siada_cli-0.0.1.dist-info/WHEEL +4 -0
- 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
|
+
|