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