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,345 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for HandleAtCommand functionality.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Dict, Optional, Any, Tuple
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_path(path: str) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Normalize a file path for consistent handling
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
path: File path to normalize
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Normalized path string
|
|
20
|
+
"""
|
|
21
|
+
if not path:
|
|
22
|
+
return path
|
|
23
|
+
|
|
24
|
+
# Convert to Path object and resolve
|
|
25
|
+
normalized = Path(path).as_posix()
|
|
26
|
+
|
|
27
|
+
# Remove leading './' if present
|
|
28
|
+
if normalized.startswith('./'):
|
|
29
|
+
normalized = normalized[2:]
|
|
30
|
+
|
|
31
|
+
return normalized
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_text_file(file_path: str) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if a file is likely a text file based on extension
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
file_path: Path to the file
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
True if likely a text file, False otherwise
|
|
43
|
+
"""
|
|
44
|
+
text_extensions = {
|
|
45
|
+
'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.c', '.cpp', '.cc', '.cxx',
|
|
46
|
+
'.h', '.hpp', '.cs', '.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala',
|
|
47
|
+
'.clj', '.hs', '.ml', '.fs', '.vb', '.pl', '.sh', '.bash', '.zsh', '.fish',
|
|
48
|
+
'.ps1', '.bat', '.cmd', '.html', '.htm', '.xml', '.css', '.scss', '.sass',
|
|
49
|
+
'.less', '.json', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.conf',
|
|
50
|
+
'.properties', '.env', '.md', '.rst', '.txt', '.log', '.sql', '.r',
|
|
51
|
+
'.m', '.mm', '.dart', '.lua', '.vim', '.dockerfile', '.makefile',
|
|
52
|
+
'.cmake', '.gradle', '.maven', '.ant', '.sbt', '.cabal', '.nix'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
file_ext = Path(file_path).suffix.lower()
|
|
56
|
+
return file_ext in text_extensions
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_image_file(file_path: str) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Check if a file is an image based on extension
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
file_path: Path to the file
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if an image file, False otherwise
|
|
68
|
+
"""
|
|
69
|
+
image_extensions = {
|
|
70
|
+
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg', '.ico',
|
|
71
|
+
'.tiff', '.tif', '.psd', '.ai', '.eps'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
file_ext = Path(file_path).suffix.lower()
|
|
75
|
+
return file_ext in image_extensions
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_pdf_file(file_path: str) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Check if a file is a PDF
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
file_path: Path to the file
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if a PDF file, False otherwise
|
|
87
|
+
"""
|
|
88
|
+
return Path(file_path).suffix.lower() == '.pdf'
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def sanitize_path_for_display(path: str) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Sanitize a path for safe display in messages
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
path: Path to sanitize
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Sanitized path string
|
|
100
|
+
"""
|
|
101
|
+
if not path:
|
|
102
|
+
return path
|
|
103
|
+
|
|
104
|
+
# Remove any potential control characters
|
|
105
|
+
sanitized = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', path)
|
|
106
|
+
|
|
107
|
+
# Limit length for display
|
|
108
|
+
max_length = 100
|
|
109
|
+
if len(sanitized) > max_length:
|
|
110
|
+
sanitized = sanitized[:max_length] + '...'
|
|
111
|
+
|
|
112
|
+
return sanitized
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def format_file_size(size_bytes: int) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Format file size in human-readable format
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
size_bytes: Size in bytes
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Formatted size string
|
|
124
|
+
"""
|
|
125
|
+
if size_bytes == 0:
|
|
126
|
+
return "0 B"
|
|
127
|
+
|
|
128
|
+
size_names = ["B", "KB", "MB", "GB", "TB"]
|
|
129
|
+
i = 0
|
|
130
|
+
size = float(size_bytes)
|
|
131
|
+
|
|
132
|
+
while size >= 1024.0 and i < len(size_names) - 1:
|
|
133
|
+
size /= 1024.0
|
|
134
|
+
i += 1
|
|
135
|
+
|
|
136
|
+
return f"{size:.1f} {size_names[i]}"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def extract_filename_from_path(path: str) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Extract filename from a path
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
path: File path
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Filename without directory
|
|
148
|
+
"""
|
|
149
|
+
return Path(path).name
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_file_extension(path: str) -> str:
|
|
153
|
+
"""
|
|
154
|
+
Get file extension from path
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
path: File path
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
File extension (including dot)
|
|
161
|
+
"""
|
|
162
|
+
return Path(path).suffix
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def is_hidden_file(path: str) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Check if a file or directory is hidden (starts with dot)
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
path: File path
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if hidden, False otherwise
|
|
174
|
+
"""
|
|
175
|
+
filename = Path(path).name
|
|
176
|
+
return filename.startswith('.')
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def split_path_components(path: str) -> List[str]:
|
|
180
|
+
"""
|
|
181
|
+
Split path into components
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
path: File path
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of path components
|
|
188
|
+
"""
|
|
189
|
+
return Path(path).parts
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def join_path_components(components: List[str]) -> str:
|
|
193
|
+
"""
|
|
194
|
+
Join path components into a path
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
components: List of path components
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Joined path string
|
|
201
|
+
"""
|
|
202
|
+
if not components:
|
|
203
|
+
return ""
|
|
204
|
+
|
|
205
|
+
return str(Path(*components))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def validate_filename(filename: str) -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Validate that a filename is safe and valid
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
filename: Filename to validate
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
True if valid, False otherwise
|
|
217
|
+
"""
|
|
218
|
+
if not filename:
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
# Check for invalid characters
|
|
222
|
+
invalid_chars = ['<', '>', ':', '"', '|', '?', '*', '\0']
|
|
223
|
+
for char in invalid_chars:
|
|
224
|
+
if char in filename:
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
# Check for reserved names (Windows)
|
|
228
|
+
reserved_names = {
|
|
229
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
230
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
231
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
name_without_ext = Path(filename).stem.upper()
|
|
235
|
+
if name_without_ext in reserved_names:
|
|
236
|
+
return False
|
|
237
|
+
|
|
238
|
+
# Check length
|
|
239
|
+
if len(filename) > 255:
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def create_safe_filename(original: str) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Create a safe filename from an original string
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
original: Original filename
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Safe filename
|
|
254
|
+
"""
|
|
255
|
+
if not original:
|
|
256
|
+
return "unnamed"
|
|
257
|
+
|
|
258
|
+
# Replace invalid characters with underscores
|
|
259
|
+
safe = re.sub(r'[<>:"|?*\0]', '_', original)
|
|
260
|
+
|
|
261
|
+
# Remove leading/trailing dots and spaces
|
|
262
|
+
safe = safe.strip('. ')
|
|
263
|
+
|
|
264
|
+
# Ensure it's not empty
|
|
265
|
+
if not safe:
|
|
266
|
+
safe = "unnamed"
|
|
267
|
+
|
|
268
|
+
# Truncate if too long
|
|
269
|
+
if len(safe) > 255:
|
|
270
|
+
name, ext = os.path.splitext(safe)
|
|
271
|
+
max_name_len = 255 - len(ext)
|
|
272
|
+
safe = name[:max_name_len] + ext
|
|
273
|
+
|
|
274
|
+
return safe
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def format_processing_time(seconds: float) -> str:
|
|
278
|
+
"""
|
|
279
|
+
Format processing time in human-readable format
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
seconds: Time in seconds
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Formatted time string
|
|
286
|
+
"""
|
|
287
|
+
if seconds < 1:
|
|
288
|
+
return f"{seconds * 1000:.0f}ms"
|
|
289
|
+
elif seconds < 60:
|
|
290
|
+
return f"{seconds:.1f}s"
|
|
291
|
+
else:
|
|
292
|
+
minutes = int(seconds // 60)
|
|
293
|
+
remaining_seconds = seconds % 60
|
|
294
|
+
return f"{minutes}m {remaining_seconds:.1f}s"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def truncate_content(content: str, max_length: int = 1000) -> Tuple[str, bool]:
|
|
298
|
+
"""
|
|
299
|
+
Truncate content if it's too long
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
content: Content to truncate
|
|
303
|
+
max_length: Maximum length allowed
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Tuple of (truncated_content, was_truncated)
|
|
307
|
+
"""
|
|
308
|
+
if len(content) <= max_length:
|
|
309
|
+
return content, False
|
|
310
|
+
|
|
311
|
+
truncated = content[:max_length] + "... [content truncated]"
|
|
312
|
+
return truncated, True
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def count_lines(content: str) -> int:
|
|
316
|
+
"""
|
|
317
|
+
Count number of lines in content
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
content: Content to count lines in
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Number of lines
|
|
324
|
+
"""
|
|
325
|
+
if not content:
|
|
326
|
+
return 0
|
|
327
|
+
|
|
328
|
+
return len(content.splitlines())
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def estimate_tokens(content: str) -> int:
|
|
332
|
+
"""
|
|
333
|
+
Estimate number of tokens in content (rough approximation)
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
content: Content to estimate tokens for
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Estimated token count
|
|
340
|
+
"""
|
|
341
|
+
if not content:
|
|
342
|
+
return 0
|
|
343
|
+
|
|
344
|
+
# Rough approximation: 1 token ≈ 4 characters
|
|
345
|
+
return len(content) // 4
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model Information Service
|
|
3
|
+
|
|
4
|
+
Provides comprehensive model information, validation, and management capabilities.
|
|
5
|
+
This service acts as a centralized interface for model-related operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional, Dict, Any
|
|
9
|
+
from dataclasses import asdict
|
|
10
|
+
|
|
11
|
+
from siada.models.model_base_config import ModelBaseConfig, MODEL_SETTING, get_model_config
|
|
12
|
+
from siada.models.model_run_config import ModelRunConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ModelInfoService:
|
|
16
|
+
"""
|
|
17
|
+
Service for managing and querying model information.
|
|
18
|
+
|
|
19
|
+
This service provides methods to:
|
|
20
|
+
- List available models
|
|
21
|
+
- Get detailed model information
|
|
22
|
+
- Validate model configurations
|
|
23
|
+
- Search and filter models by capabilities
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def get_all_models() -> List[ModelBaseConfig]:
|
|
28
|
+
"""
|
|
29
|
+
Get all available model configurations.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List[ModelBaseConfig]: List of all model configurations
|
|
33
|
+
"""
|
|
34
|
+
return MODEL_SETTING.copy()
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_model_names() -> List[str]:
|
|
38
|
+
"""
|
|
39
|
+
Get a list of all available model names.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List[str]: List of model names
|
|
43
|
+
"""
|
|
44
|
+
return [model.model_name for model in MODEL_SETTING]
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def get_model_info(model_name: str) -> Optional[ModelBaseConfig]:
|
|
48
|
+
"""
|
|
49
|
+
Get detailed information for a specific model.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
model_name: Name of the model to query
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Optional[ModelBaseConfig]: Model configuration if found, None otherwise
|
|
56
|
+
"""
|
|
57
|
+
return get_model_config(model_name)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def is_model_supported(model_name: str) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Check if a model is supported.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
model_name: Name of the model to check
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
bool: True if model is supported, False otherwise
|
|
69
|
+
"""
|
|
70
|
+
return get_model_config(model_name) is not None
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def get_models_with_images() -> List[ModelBaseConfig]:
|
|
74
|
+
"""
|
|
75
|
+
Get all models that support image processing.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List[ModelBaseConfig]: List of models with image support
|
|
79
|
+
"""
|
|
80
|
+
return [model for model in MODEL_SETTING if model.supports_images]
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def search_models(query: str, case_sensitive: bool = False) -> List[ModelBaseConfig]:
|
|
84
|
+
"""
|
|
85
|
+
Search for models by name.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
query: Search query string
|
|
89
|
+
case_sensitive: Whether to perform case-sensitive search (default: False)
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
List[ModelBaseConfig]: List of models matching the search query
|
|
93
|
+
"""
|
|
94
|
+
if not case_sensitive:
|
|
95
|
+
query = query.lower()
|
|
96
|
+
|
|
97
|
+
results = []
|
|
98
|
+
for model in MODEL_SETTING:
|
|
99
|
+
model_name = model.model_name if case_sensitive else model.model_name.lower()
|
|
100
|
+
if query in model_name:
|
|
101
|
+
results.append(model)
|
|
102
|
+
|
|
103
|
+
return results
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from siada.session.session_models import RunningSession
|
|
6
|
+
from typing import Dict, Type, Optional, Union, Literal, overload
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from agents import RunResult, RunResultStreaming, Agent, set_trace_processors
|
|
10
|
+
|
|
11
|
+
from siada.agent_hub.coder.tracing import create_detailed_logger
|
|
12
|
+
from siada.agent_hub.siada_agent import SiadaAgent
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from siada.services.code_context_manager import ContextTracingProcessor
|
|
17
|
+
|
|
18
|
+
class SiadaRunner:
|
|
19
|
+
|
|
20
|
+
@overload
|
|
21
|
+
@staticmethod
|
|
22
|
+
async def run_agent(
|
|
23
|
+
agent_name: str,
|
|
24
|
+
user_input: str,
|
|
25
|
+
workspace: str = None,
|
|
26
|
+
session: RunningSession = None,
|
|
27
|
+
*,
|
|
28
|
+
stream: Literal[True],
|
|
29
|
+
) -> RunResultStreaming: ...
|
|
30
|
+
|
|
31
|
+
@overload
|
|
32
|
+
@staticmethod
|
|
33
|
+
async def run_agent(
|
|
34
|
+
agent_name: str,
|
|
35
|
+
user_input: str,
|
|
36
|
+
workspace: str = None,
|
|
37
|
+
session: RunningSession = None,
|
|
38
|
+
*,
|
|
39
|
+
stream: Literal[False],
|
|
40
|
+
) -> RunResult: ...
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
async def run_agent(
|
|
44
|
+
agent_name: str,
|
|
45
|
+
user_input: str,
|
|
46
|
+
workspace: str = None,
|
|
47
|
+
session: RunningSession = None,
|
|
48
|
+
stream: bool = False,
|
|
49
|
+
) -> RunResult | RunResultStreaming:
|
|
50
|
+
"""
|
|
51
|
+
Run the specified Agent.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
agent_name: Name of the Agent.
|
|
55
|
+
user_input: User input.
|
|
56
|
+
workspace: Workspace path, optional.
|
|
57
|
+
session: The running session object, optional.
|
|
58
|
+
stream: Whether to enable streaming output, defaults to False.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Union[RunResult, RunResultStreaming]: Returns a regular or streaming result based on the stream parameter.
|
|
62
|
+
"""
|
|
63
|
+
agent = await SiadaRunner.get_agent(agent_name)
|
|
64
|
+
context = await agent.get_context()
|
|
65
|
+
console_output = False
|
|
66
|
+
if workspace:
|
|
67
|
+
context.root_dir = workspace
|
|
68
|
+
if session:
|
|
69
|
+
context.session = session
|
|
70
|
+
|
|
71
|
+
# set_trace_processors([create_detailed_logger(output_file="agent_trace.log")])
|
|
72
|
+
console_output = session.siada_config.console_output if session else True
|
|
73
|
+
context_tracing_processor = ContextTracingProcessor(context)
|
|
74
|
+
|
|
75
|
+
set_trace_processors([create_detailed_logger(console_output=console_output),
|
|
76
|
+
context_tracing_processor])
|
|
77
|
+
|
|
78
|
+
if stream:
|
|
79
|
+
# Stream execution
|
|
80
|
+
result = await agent.run_streamed(user_input, context)
|
|
81
|
+
else:
|
|
82
|
+
# Normal execution
|
|
83
|
+
result = await agent.run(user_input, context)
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
async def get_agent(agent_name: str) -> SiadaAgent:
|
|
89
|
+
"""
|
|
90
|
+
Get the corresponding Agent instance based on agent name
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
agent_name: Agent name, supports case-insensitive matching
|
|
94
|
+
e.g.: 'bugfix', 'BugFix', 'bug_fix', etc.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Agent: The corresponding Agent instance
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: Raised when the corresponding Agent type is not found
|
|
101
|
+
FileNotFoundError: Raised when the configuration file does not exist
|
|
102
|
+
ImportError: Raised when unable to import Agent class
|
|
103
|
+
"""
|
|
104
|
+
# Normalize agent name: convert to lowercase and remove underscores and hyphens
|
|
105
|
+
normalized_name = agent_name.lower().replace('_', '').replace('-', '')
|
|
106
|
+
|
|
107
|
+
# Load Agent mapping from configuration file
|
|
108
|
+
agent_configs = SiadaRunner._load_agent_config()
|
|
109
|
+
|
|
110
|
+
# Find the corresponding Agent configuration
|
|
111
|
+
agent_config = agent_configs.get(normalized_name)
|
|
112
|
+
|
|
113
|
+
if agent_config is None:
|
|
114
|
+
supported_agents = [name for name, config in agent_configs.items()
|
|
115
|
+
if config.get('enabled', False) and config.get('class')]
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"Unsupported agent type: '{agent_name}'. "
|
|
118
|
+
f"Supported agent types: {supported_agents}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Check if Agent is enabled
|
|
122
|
+
if not agent_config.get('enabled', False):
|
|
123
|
+
raise ValueError(f"Agent '{agent_name}' is disabled")
|
|
124
|
+
|
|
125
|
+
# Check if Agent class is implemented
|
|
126
|
+
class_path = agent_config.get('class')
|
|
127
|
+
if not class_path:
|
|
128
|
+
raise ValueError(f"Agent '{agent_name}' is not implemented yet")
|
|
129
|
+
|
|
130
|
+
# Dynamically import and instantiate Agent class
|
|
131
|
+
try:
|
|
132
|
+
agent_class = SiadaRunner._import_agent_class(class_path)
|
|
133
|
+
return agent_class()
|
|
134
|
+
except (ImportError, AttributeError) as e:
|
|
135
|
+
raise ImportError(f"Failed to import agent class '{class_path}': {e}")
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _load_agent_config() -> Dict[str, Dict]:
|
|
139
|
+
"""
|
|
140
|
+
Load Agent configuration from configuration file
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Dict[str, Dict]: Agent configuration dictionary
|
|
144
|
+
"""
|
|
145
|
+
# Get the configuration file path in the project root directory
|
|
146
|
+
current_dir = Path(__file__).parent.parent.parent # Go back to project root directory
|
|
147
|
+
config_path = current_dir / "agent_config.yaml"
|
|
148
|
+
|
|
149
|
+
if not config_path.exists():
|
|
150
|
+
raise FileNotFoundError(f"Agent configuration file not found: {config_path}")
|
|
151
|
+
|
|
152
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
153
|
+
config = yaml.safe_load(f)
|
|
154
|
+
|
|
155
|
+
return config.get('agents', {})
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _import_agent_class(class_path: str) -> Type[Agent]:
|
|
159
|
+
"""
|
|
160
|
+
Dynamically import Agent class
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
class_path: Complete import path of Agent class, e.g. 'siada.agent_hub.coder.bug_fix_agent.BugFixAgent'
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Type[Agent]: Agent class
|
|
167
|
+
"""
|
|
168
|
+
module_path, class_name = class_path.rsplit('.', 1)
|
|
169
|
+
module = importlib.import_module(module_path)
|
|
170
|
+
return getattr(module, class_name)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interaction Session Management Module
|
|
3
|
+
|
|
4
|
+
Provides interaction session management functionality working with OpenAI Agents SQLiteSession:
|
|
5
|
+
|
|
6
|
+
Core Features:
|
|
7
|
+
- Create interaction sessions and associated OpenAI SQLiteSession
|
|
8
|
+
- Interaction session and openai_session share the same ID
|
|
9
|
+
- Support ModelSettings model configuration
|
|
10
|
+
- Simplified API focusing on session creation
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .session_models import (
|
|
14
|
+
RunningSession,
|
|
15
|
+
SessionState
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .session_manager import (
|
|
19
|
+
RunningSessionManager,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Data models
|
|
24
|
+
"RunningSession",
|
|
25
|
+
"SessionState",
|
|
26
|
+
|
|
27
|
+
# Managers
|
|
28
|
+
"RunningSessionManager",
|
|
29
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
import logging
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from siada.entrypoint.interaction.config import RunningConfig
|
|
6
|
+
from siada.io.io import InputOutput
|
|
7
|
+
from siada.models.model_run_config import ModelRunConfig
|
|
8
|
+
from siada.support.slash_commands import SlashCommands
|
|
9
|
+
|
|
10
|
+
from .session_models import RunningSession, SessionState
|
|
11
|
+
from siada.models.model_base_config import ModelBaseConfig
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RunningSessionManager:
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def create_session(
|
|
20
|
+
siada_config: RunningConfig,
|
|
21
|
+
session_id: Optional[str] = None,
|
|
22
|
+
) -> RunningSession:
|
|
23
|
+
"""
|
|
24
|
+
Create a new interaction session
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
siada_config: config of siada running
|
|
28
|
+
session_id: Session ID, auto-generates UUID if not provided
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Session: Created session object
|
|
32
|
+
"""
|
|
33
|
+
# Use provided session_id or generate new UUID
|
|
34
|
+
if session_id is None:
|
|
35
|
+
session_id = str(uuid4())
|
|
36
|
+
|
|
37
|
+
# Create interaction session
|
|
38
|
+
session = RunningSession(
|
|
39
|
+
session_id=session_id,
|
|
40
|
+
siada_config=siada_config,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Create associated OpenAI SQLiteSession with same ID
|
|
44
|
+
from agents import SQLiteSession
|
|
45
|
+
|
|
46
|
+
# Create OpenAI Session
|
|
47
|
+
openai_session = SQLiteSession(
|
|
48
|
+
session_id=session_id, # Use same ID
|
|
49
|
+
)
|
|
50
|
+
session.state.openai_session = openai_session
|
|
51
|
+
return session
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def get_default_session():
|
|
55
|
+
llm_config = ModelRunConfig.get_default_config()
|
|
56
|
+
io = InputOutput()
|
|
57
|
+
|
|
58
|
+
siada_config = RunningConfig(
|
|
59
|
+
llm_config=llm_config,
|
|
60
|
+
io=io,
|
|
61
|
+
workspace='',
|
|
62
|
+
agent_name='',
|
|
63
|
+
console_output=True,
|
|
64
|
+
interactive=False,
|
|
65
|
+
)
|
|
66
|
+
return RunningSessionManager.create_session(siada_config)
|