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,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)