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,341 @@
1
+ """
2
+ Code tag extraction utilities using tree-sitter and pygments.
3
+
4
+ This module provides functionality to extract definitions and references
5
+ from source code files using tree-sitter parsing with pygments fallback.
6
+ """
7
+
8
+ import warnings
9
+ from pathlib import Path
10
+ from typing import Generator, List, Optional
11
+
12
+ from agents import function_tool
13
+ from grep_ast import TreeContext, filename_to_lang
14
+ from pygments.lexers import guess_lexer_for_filename
15
+ from pygments.token import Token
16
+
17
+ from siada.tools.coder.observation.observation import FunctionCallResult
18
+
19
+ from .models import Tag
20
+
21
+ # tree_sitter is throwing a FutureWarning
22
+ warnings.simplefilter("ignore", category=FutureWarning)
23
+ from grep_ast.tsl import USING_TSL_PACK, get_language, get_parser # noqa: E402
24
+
25
+ class ListCodeDefinitionNamesResult(FunctionCallResult):
26
+ """This data class represents the output of a list code definition names operation."""
27
+
28
+ def __init__(self, content: str):
29
+ self.content = content
30
+
31
+ def format_for_display(self):
32
+ return str(self)
33
+
34
+ def __str__(self):
35
+ return self.content
36
+
37
+
38
+ @function_tool(name_override="list_code_definition_names")
39
+ def list_code_definition_names(file_name: str, rel_file_name: Optional[str] = None) -> str:
40
+ """
41
+ Analyze a source code file and extract its structural definitions.
42
+
43
+ This function performs static code analysis to identify and extract code definitions
44
+ such as functions, classes, methods, and variables from a source file. It uses
45
+ tree-sitter parsing technology to build an Abstract Syntax Tree (AST) and then
46
+ queries the tree to locate definition nodes. The extracted information is formatted
47
+ into a human-readable tree structure that shows the code organization and hierarchy.
48
+
49
+ The implementation follows a two-phase approach: first, it parses the source code
50
+ to extract both definitions and references using language-specific query patterns;
51
+ second, it formats the definitions into a contextual tree view that preserves the
52
+ original code structure and provides line number information for easy navigation.
53
+
54
+ Args:
55
+ file_name: Absolute path to the source file to analyze
56
+ rel_file_name: Relative path to the source file (optional, defaults to filename)
57
+
58
+ Returns:
59
+ Formatted string containing:
60
+ - File summary with definition and reference counts
61
+ - Hierarchical tree view of code definitions with context
62
+ """
63
+ content = _list_code_definition_names(file_name, rel_file_name)
64
+ return ListCodeDefinitionNamesResult(content)
65
+
66
+ def get_scm_fname(lang: str) -> Optional[Path]:
67
+ """
68
+ Get the path to the tree-sitter query file for a given language.
69
+
70
+ Args:
71
+ lang: Programming language identifier
72
+
73
+ Returns:
74
+ Path to the .scm query file, or None if not found
75
+ """
76
+ # Find the project root by looking for the queries directory
77
+ current_file = Path(__file__)
78
+ project_root = current_file.parent.parent.parent # Go up to siada/ level
79
+ queries_dir = project_root / "queries"
80
+
81
+ if USING_TSL_PACK:
82
+ subdir = "tree-sitter-language-pack"
83
+ path = queries_dir / subdir / f"{lang}-tags.scm"
84
+ if path.exists():
85
+ return path
86
+
87
+ # Fall back to tree-sitter-languages
88
+ subdir = "tree-sitter-languages"
89
+ path = queries_dir / subdir / f"{lang}-tags.scm"
90
+ if path.exists():
91
+ return path
92
+
93
+ # If not found, return None
94
+ return None
95
+
96
+
97
+ def get_tags_raw(fname: str, rel_fname: str) -> Generator[Tag, None, None]:
98
+ """
99
+ Extract code tags (definitions and references) from a source file.
100
+
101
+ This function uses tree-sitter to parse source code and extract identifiers,
102
+ with pygments as a fallback for reference extraction when tree-sitter
103
+ queries only provide definitions.
104
+
105
+ Args:
106
+ fname: Absolute path to the source file
107
+ rel_fname: Relative path to the source file
108
+
109
+ Yields:
110
+ Tag: Named tuple containing identifier information with fields:
111
+ - rel_fname: relative file path
112
+ - fname: absolute file path
113
+ - name: identifier name
114
+ - kind: "def" for definitions, "ref" for references
115
+ - line: line number (0-based, -1 for pygments fallback)
116
+ """
117
+ lang = filename_to_lang(fname)
118
+ if not lang:
119
+ return
120
+
121
+ try:
122
+ language = get_language(lang)
123
+ parser = get_parser(lang)
124
+ except Exception as err:
125
+ print(f"Skipping file {fname}: {err}")
126
+ return
127
+
128
+ query_scm = get_scm_fname(lang)
129
+ if not query_scm or not query_scm.exists():
130
+ return
131
+ query_scm_content = query_scm.read_text()
132
+
133
+ try:
134
+ with open(fname, 'r', encoding='utf-8') as f:
135
+ code = f.read()
136
+ except (OSError, UnicodeDecodeError) as e:
137
+ print(f"Error reading file {fname}: {e}")
138
+ return
139
+
140
+ if not code:
141
+ return
142
+ tree = parser.parse(bytes(code, "utf-8"))
143
+
144
+ # Run the tags queries
145
+ query = language.query(query_scm_content)
146
+ captures = query.captures(tree.root_node)
147
+
148
+ saw = set()
149
+ if USING_TSL_PACK:
150
+ all_nodes = []
151
+ for tag, nodes in captures.items():
152
+ all_nodes += [(node, tag) for node in nodes]
153
+ else:
154
+ all_nodes = list(captures)
155
+
156
+ for node, tag in all_nodes:
157
+ if tag.startswith("name.definition."):
158
+ kind = "def"
159
+ elif tag.startswith("name.reference."):
160
+ kind = "ref"
161
+ else:
162
+ continue
163
+
164
+ saw.add(kind)
165
+
166
+ result = Tag(
167
+ rel_fname=rel_fname,
168
+ fname=fname,
169
+ name=node.text.decode("utf-8"),
170
+ kind=kind,
171
+ line=node.start_point[0],
172
+ )
173
+
174
+ yield result
175
+
176
+ if "ref" in saw:
177
+ return
178
+ if "def" not in saw:
179
+ return
180
+
181
+ # We saw defs, without any refs
182
+ # Some tags files only provide defs (cpp, for example)
183
+ # Use pygments to backfill refs
184
+
185
+ try:
186
+ lexer = guess_lexer_for_filename(fname, code)
187
+ except Exception: # On Windows, bad ref to time.clock which is deprecated?
188
+ return
189
+
190
+ tokens = list(lexer.get_tokens(code))
191
+ tokens = [token[1] for token in tokens if token[0] in Token.Name]
192
+
193
+ for token in tokens:
194
+ yield Tag(
195
+ rel_fname=rel_fname,
196
+ fname=fname,
197
+ name=token,
198
+ kind="ref",
199
+ line=-1,
200
+ )
201
+
202
+
203
+ def to_tree(tags: List[Tag]) -> str:
204
+ """
205
+ Convert a list of tags to a formatted code tree structure (single file scenario).
206
+
207
+ This function is optimized for single file analysis, without caching mechanisms.
208
+ It focuses on definitions and provides code context using TreeContext.
209
+
210
+ Args:
211
+ tags: List of Tag objects from a single file
212
+
213
+ Returns:
214
+ Formatted string containing code structure with context
215
+ """
216
+ if not tags:
217
+ return ""
218
+
219
+ # Filter only definition tags and sort by line number
220
+ def_tags = [tag for tag in tags if tag.kind == "def"]
221
+ if not def_tags:
222
+ return ""
223
+
224
+ def_tags.sort(key=lambda t: t.line)
225
+
226
+ # Get file info from first tag
227
+ rel_fname = def_tags[0].rel_fname
228
+ abs_fname = def_tags[0].fname
229
+
230
+ # Read file content
231
+ try:
232
+ with open(abs_fname, 'r', encoding='utf-8') as f:
233
+ code = f.read()
234
+ except (OSError, UnicodeDecodeError) as e:
235
+ return f"Error reading file {abs_fname}: {e}"
236
+
237
+ if not code:
238
+ return ""
239
+
240
+ # Ensure code ends with newline
241
+ if not code.endswith("\n"):
242
+ code += "\n"
243
+
244
+ # Create TreeContext for extracting code context
245
+ try:
246
+ context = TreeContext(
247
+ rel_fname,
248
+ code,
249
+ color=False,
250
+ line_number=False,
251
+ child_context=False,
252
+ last_line=False,
253
+ margin=0,
254
+ mark_lois=False,
255
+ loi_pad=0,
256
+ show_top_of_file_parent_scope=False,
257
+ )
258
+
259
+ # Add all definition lines as lines of interest
260
+ lois = [tag.line for tag in def_tags if tag.line >= 0]
261
+ context.add_lines_of_interest(lois)
262
+ context.add_context()
263
+
264
+ # Format the result
265
+ result = context.format()
266
+
267
+ # Truncate long lines to prevent excessive output
268
+ output_lines = []
269
+ for line in result.splitlines():
270
+ if len(line) > 100:
271
+ output_lines.append(line[:100] + "...")
272
+ else:
273
+ output_lines.append(line)
274
+
275
+ return "\n".join(output_lines) + "\n"
276
+
277
+ except Exception as e:
278
+ # Fallback: simple list of definitions
279
+ output = f"{rel_fname}:\n"
280
+ for tag in def_tags:
281
+ output += f" {tag.name} (line {tag.line + 1})\n"
282
+ return output
283
+
284
+
285
+ def _list_code_definition_names(fname: str, rel_fname: Optional[str] = None) -> str:
286
+ """
287
+ Analyze a source code file and extract its structural definitions.
288
+
289
+ This function performs static code analysis to identify and extract code definitions
290
+ such as functions, classes, methods, and variables from a source file. It uses
291
+ tree-sitter parsing technology to build an Abstract Syntax Tree (AST) and then
292
+ queries the tree to locate definition nodes. The extracted information is formatted
293
+ into a human-readable tree structure that shows the code organization and hierarchy.
294
+
295
+ The implementation follows a two-phase approach: first, it parses the source code
296
+ to extract both definitions and references using language-specific query patterns;
297
+ second, it formats the definitions into a contextual tree view that preserves the
298
+ original code structure and provides line number information for easy navigation.
299
+
300
+ Args:
301
+ fname: Absolute path to the source file to analyze
302
+ rel_fname: Relative path to the source file (optional, defaults to filename)
303
+
304
+ Returns:
305
+ Formatted string containing:
306
+ - File summary with definition and reference counts
307
+ - Hierarchical tree view of code definitions with context
308
+ - Line numbers for each definition
309
+ - Fallback to simple list format if tree generation fails
310
+ """
311
+ if rel_fname is None:
312
+ rel_fname = Path(fname).name
313
+
314
+ # Extract tags from the file
315
+ tags = list(get_tags_raw(fname, rel_fname))
316
+
317
+ if not tags:
318
+ return f"No code definitions found in {rel_fname}"
319
+
320
+ # Get definitions count
321
+ definitions = [tag for tag in tags if tag.kind == "def"]
322
+ references = [tag for tag in tags if tag.kind == "ref"]
323
+
324
+ # Generate header with summary
325
+ header = f"File: {rel_fname}\n"
326
+ header += f"Definitions: {len(definitions)}, References: {len(references)}\n\n"
327
+
328
+ # Generate code tree
329
+ tree_output = to_tree(tags)
330
+
331
+ if tree_output.strip():
332
+ return header + tree_output
333
+ else:
334
+ # Fallback: simple list
335
+ if definitions:
336
+ result = header + "Definitions found:\n"
337
+ for tag in sorted(definitions, key=lambda t: t.line):
338
+ result += f" - {tag.name} (line {tag.line + 1})\n"
339
+ return result
340
+ else:
341
+ return header + "No definitions found in this file."
@@ -0,0 +1,11 @@
1
+ """
2
+ AST analysis data models and structures.
3
+ """
4
+
5
+ from collections import namedtuple
6
+ from typing import Generator, Optional
7
+
8
+ # Tag namedtuple for representing code identifiers
9
+ Tag = namedtuple("Tag", "rel_fname fname line name kind".split())
10
+
11
+ __all__ = ["Tag"]
File without changes
@@ -0,0 +1,70 @@
1
+ import asyncio
2
+ from agents import function_tool, RunContextWrapper
3
+ from siada.foundation.code_agent_context import CodeAgentContext
4
+ from siada.tools.coder.observation.observation import FunctionCallResult
5
+
6
+
7
+ class AskFollowupQuestionResult(FunctionCallResult):
8
+
9
+ answer: str
10
+
11
+ error: str
12
+
13
+ def __init__(self, answer: str = None, error: str = None):
14
+ super().__init__(content=answer)
15
+ self.answer = answer
16
+ self.error = error
17
+
18
+ def format_for_display(self) -> str:
19
+ if self.error:
20
+ return f"I can't get the answer from the user, because {self.error} occurred"
21
+ elif self.answer:
22
+ return "Siada has received the answer!"
23
+ else:
24
+ return "Siada has not received the answer yet!"
25
+
26
+ def __str__(self) -> str:
27
+ if self.error:
28
+ return f"Unable to retrieve user's answer due to error: {self.error}"
29
+ elif self.answer:
30
+ return f"The user's answer is : {self.answer}"
31
+ else:
32
+ return f"the user has not answered the question"
33
+
34
+
35
+ ASK_FOLLOWUP_QUESTION_DOCS = """Ask Follow-up Question Tool
36
+
37
+ Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
38
+
39
+ Args:
40
+ question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need.
41
+ """
42
+
43
+ @function_tool(
44
+ name_override="ask_followup_question",
45
+ description_override=ASK_FOLLOWUP_QUESTION_DOCS
46
+ )
47
+ async def ask_followup_question(
48
+ context: RunContextWrapper[CodeAgentContext],
49
+ question: str,
50
+ ) -> FunctionCallResult:
51
+ """
52
+ Asks a followup question to the user to get more information.
53
+
54
+ Args:
55
+ context: The run context wrapper.
56
+ question: The question to ask the user.
57
+
58
+ Returns:
59
+ An observation containing the question for the user.
60
+ """
61
+
62
+ try:
63
+ code_agent_context : CodeAgentContext = context.context
64
+ # 在线程中运行同步的prompt_ask以避免异步冲突
65
+ answer = await asyncio.to_thread(code_agent_context.session.get_input)
66
+
67
+ except Exception as e:
68
+ return AskFollowupQuestionResult(error=str(e))
69
+
70
+ return AskFollowupQuestionResult(answer=answer)
@@ -0,0 +1,132 @@
1
+ import os
2
+ import platform
3
+ import subprocess
4
+ import sys
5
+ from io import BytesIO
6
+
7
+ import pexpect
8
+ import psutil
9
+
10
+
11
+ def run_cmd_impl(command, verbose=False, cwd=None, error_print=None):
12
+ try:
13
+ if sys.stdin.isatty() and hasattr(pexpect, "spawn") and platform.system() != "Windows":
14
+ return run_cmd_pexpect(command, verbose, cwd)
15
+
16
+ return run_cmd_subprocess(command, verbose, cwd)
17
+ except OSError as e:
18
+ error_message = f"Error occurred while running command '{command}': {str(e)}"
19
+ if error_print is None:
20
+ print(error_message)
21
+ else:
22
+ error_print(error_message)
23
+ return 1, error_message
24
+
25
+
26
+ def get_windows_parent_process_name():
27
+ try:
28
+ current_process = psutil.Process()
29
+ while True:
30
+ parent = current_process.parent()
31
+ if parent is None:
32
+ break
33
+ parent_name = parent.name().lower()
34
+ if parent_name in ["powershell.exe", "cmd.exe"]:
35
+ return parent_name
36
+ current_process = parent
37
+ return None
38
+ except Exception:
39
+ return None
40
+
41
+
42
+ def run_cmd_subprocess(command, verbose=False, cwd=None, encoding=sys.stdout.encoding):
43
+ if verbose:
44
+ print("Using run_cmd_subprocess:", command)
45
+
46
+ try:
47
+ shell = os.environ.get("SHELL", "/bin/sh")
48
+ parent_process = None
49
+
50
+ # Determine the appropriate shell
51
+ if platform.system() == "Windows":
52
+ parent_process = get_windows_parent_process_name()
53
+ if parent_process == "powershell.exe":
54
+ command = f"powershell -Command {command}"
55
+
56
+ if verbose:
57
+ print("Running command:", command)
58
+ print("SHELL:", shell)
59
+ if platform.system() == "Windows":
60
+ print("Parent process:", parent_process)
61
+
62
+ process = subprocess.Popen(
63
+ command,
64
+ stdout=subprocess.PIPE,
65
+ stderr=subprocess.STDOUT,
66
+ text=True,
67
+ shell=True,
68
+ encoding=encoding,
69
+ errors="replace",
70
+ bufsize=0, # Set bufsize to 0 for unbuffered output
71
+ universal_newlines=True,
72
+ cwd=cwd,
73
+ )
74
+
75
+ output = []
76
+ while True:
77
+ chunk = process.stdout.read(1)
78
+ if not chunk:
79
+ break
80
+ # print(chunk, end="", flush=True) # Print the chunk in real-time
81
+ output.append(chunk) # Store the chunk for later use
82
+
83
+ process.wait()
84
+ return process.returncode, "".join(output)
85
+ except Exception as e:
86
+ return 1, str(e)
87
+
88
+
89
+ def run_cmd_pexpect(command, verbose=False, cwd=None):
90
+ """
91
+ Run a shell command interactively using pexpect, capturing all output.
92
+
93
+ :param command: The command to run as a string.
94
+ :param verbose: If True, print output in real-time.
95
+ :return: A tuple containing (exit_status, output)
96
+ """
97
+ if verbose:
98
+ print("Using run_cmd_pexpect:", command)
99
+
100
+ output = BytesIO()
101
+
102
+ def output_callback(b):
103
+ output.write(b)
104
+ return b
105
+
106
+ try:
107
+ # Use the SHELL environment variable, falling back to /bin/sh if not set
108
+ shell = os.environ.get("SHELL", "/bin/sh")
109
+ if verbose:
110
+ print("With shell:", shell)
111
+
112
+ if os.path.exists(shell):
113
+ # Use the shell from SHELL environment variable
114
+ if verbose:
115
+ print("Running pexpect.spawn with shell:", shell)
116
+ child = pexpect.spawn(shell, args=["-i", "-c", command], encoding="utf-8", cwd=cwd)
117
+ else:
118
+ # Fall back to spawning the command directly
119
+ if verbose:
120
+ print("Running pexpect.spawn without shell.")
121
+ child = pexpect.spawn(command, encoding="utf-8", cwd=cwd)
122
+
123
+ # Transfer control to the user, capturing output
124
+ child.interact(output_filter=output_callback)
125
+
126
+ # Wait for the command to finish and get the exit status
127
+ child.close()
128
+ return child.exitstatus, output.getvalue().decode("utf-8", errors="replace")
129
+
130
+ except (pexpect.ExceptionPexpect, TypeError, ValueError) as e:
131
+ error_msg = f"Error running command {command}: {e}"
132
+ return 1, error_msg