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,203 @@
1
+ import os
2
+
3
+ ROOT_IMPORTANT_FILES = [
4
+ # Version Control
5
+ ".gitignore",
6
+ ".gitattributes",
7
+ # Documentation
8
+ "README",
9
+ "README.md",
10
+ "README.txt",
11
+ "README.rst",
12
+ "CONTRIBUTING",
13
+ "CONTRIBUTING.md",
14
+ "CONTRIBUTING.txt",
15
+ "CONTRIBUTING.rst",
16
+ "LICENSE",
17
+ "LICENSE.md",
18
+ "LICENSE.txt",
19
+ "CHANGELOG",
20
+ "CHANGELOG.md",
21
+ "CHANGELOG.txt",
22
+ "CHANGELOG.rst",
23
+ "SECURITY",
24
+ "SECURITY.md",
25
+ "SECURITY.txt",
26
+ "CODEOWNERS",
27
+ # Package Management and Dependencies
28
+ "requirements.txt",
29
+ "Pipfile",
30
+ "Pipfile.lock",
31
+ "pyproject.toml",
32
+ "setup.py",
33
+ "setup.cfg",
34
+ "package.json",
35
+ "package-lock.json",
36
+ "yarn.lock",
37
+ "npm-shrinkwrap.json",
38
+ "Gemfile",
39
+ "Gemfile.lock",
40
+ "composer.json",
41
+ "composer.lock",
42
+ "pom.xml",
43
+ "build.gradle",
44
+ "build.gradle.kts",
45
+ "build.sbt",
46
+ "go.mod",
47
+ "go.sum",
48
+ "Cargo.toml",
49
+ "Cargo.lock",
50
+ "mix.exs",
51
+ "rebar.config",
52
+ "project.clj",
53
+ "Podfile",
54
+ "Cartfile",
55
+ "dub.json",
56
+ "dub.sdl",
57
+ # Configuration and Settings
58
+ ".env",
59
+ ".env.example",
60
+ ".editorconfig",
61
+ "tsconfig.json",
62
+ "jsconfig.json",
63
+ ".babelrc",
64
+ "babel.config.js",
65
+ ".eslintrc",
66
+ ".eslintignore",
67
+ ".prettierrc",
68
+ ".stylelintrc",
69
+ "tslint.json",
70
+ ".pylintrc",
71
+ ".flake8",
72
+ ".rubocop.yml",
73
+ ".scalafmt.conf",
74
+ ".dockerignore",
75
+ ".gitpod.yml",
76
+ "sonar-project.properties",
77
+ "renovate.json",
78
+ "dependabot.yml",
79
+ ".pre-commit-config.yaml",
80
+ "mypy.ini",
81
+ "tox.ini",
82
+ ".yamllint",
83
+ "pyrightconfig.json",
84
+ # Build and Compilation
85
+ "webpack.config.js",
86
+ "rollup.config.js",
87
+ "parcel.config.js",
88
+ "gulpfile.js",
89
+ "Gruntfile.js",
90
+ "build.xml",
91
+ "build.boot",
92
+ "project.json",
93
+ "build.cake",
94
+ "MANIFEST.in",
95
+ # Testing
96
+ "pytest.ini",
97
+ "phpunit.xml",
98
+ "karma.conf.js",
99
+ "jest.config.js",
100
+ "cypress.json",
101
+ ".nycrc",
102
+ ".nycrc.json",
103
+ # CI/CD
104
+ ".travis.yml",
105
+ ".gitlab-ci.yml",
106
+ "Jenkinsfile",
107
+ "azure-pipelines.yml",
108
+ "bitbucket-pipelines.yml",
109
+ "appveyor.yml",
110
+ "circle.yml",
111
+ ".circleci/config.yml",
112
+ ".github/dependabot.yml",
113
+ "codecov.yml",
114
+ ".coveragerc",
115
+ # Docker and Containers
116
+ "Dockerfile",
117
+ "docker-compose.yml",
118
+ "docker-compose.override.yml",
119
+ # Cloud and Serverless
120
+ "serverless.yml",
121
+ "firebase.json",
122
+ "now.json",
123
+ "netlify.toml",
124
+ "vercel.json",
125
+ "app.yaml",
126
+ "terraform.tf",
127
+ "main.tf",
128
+ "cloudformation.yaml",
129
+ "cloudformation.json",
130
+ "ansible.cfg",
131
+ "kubernetes.yaml",
132
+ "k8s.yaml",
133
+ # Database
134
+ "schema.sql",
135
+ "liquibase.properties",
136
+ "flyway.conf",
137
+ # Framework-specific
138
+ "next.config.js",
139
+ "nuxt.config.js",
140
+ "vue.config.js",
141
+ "angular.json",
142
+ "gatsby-config.js",
143
+ "gridsome.config.js",
144
+ # API Documentation
145
+ "swagger.yaml",
146
+ "swagger.json",
147
+ "openapi.yaml",
148
+ "openapi.json",
149
+ # Development environment
150
+ ".nvmrc",
151
+ ".ruby-version",
152
+ ".python-version",
153
+ "Vagrantfile",
154
+ # Quality and metrics
155
+ ".codeclimate.yml",
156
+ "codecov.yml",
157
+ # Documentation
158
+ "mkdocs.yml",
159
+ "_config.yml",
160
+ "book.toml",
161
+ "readthedocs.yml",
162
+ ".readthedocs.yaml",
163
+ # Package registries
164
+ ".npmrc",
165
+ ".yarnrc",
166
+ # Linting and formatting
167
+ ".isort.cfg",
168
+ ".markdownlint.json",
169
+ ".markdownlint.yaml",
170
+ # Security
171
+ ".bandit",
172
+ ".secrets.baseline",
173
+ # Misc
174
+ ".pypirc",
175
+ ".gitkeep",
176
+ ".npmignore",
177
+ ]
178
+
179
+
180
+ # Normalize the lists once
181
+ NORMALIZED_ROOT_IMPORTANT_FILES = set(os.path.normpath(path) for path in ROOT_IMPORTANT_FILES)
182
+
183
+
184
+ def is_important(file_path):
185
+ file_name = os.path.basename(file_path)
186
+ dir_name = os.path.normpath(os.path.dirname(file_path))
187
+ normalized_path = os.path.normpath(file_path)
188
+
189
+ # Check for GitHub Actions workflow files
190
+ if dir_name == os.path.normpath(".github/workflows") and file_name.endswith(".yml"):
191
+ return True
192
+
193
+ return normalized_path in NORMALIZED_ROOT_IMPORTANT_FILES
194
+
195
+
196
+ def filter_important_files(file_paths):
197
+ """
198
+ Filter a list of file paths to return only those that are commonly important in codebases.
199
+
200
+ :param file_paths: List of file paths to check
201
+ :return: List of file paths that match important file patterns
202
+ """
203
+ return list(filter(is_important, file_paths))
@@ -0,0 +1,154 @@
1
+ """
2
+ Token counting functionality for language models.
3
+
4
+ Provides token calculation with litellm integration and fallback estimation.
5
+ Supports any model with zero configuration and basic caching for performance.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+
12
+ class TokenCounterModel:
13
+ """
14
+ Token counting model with litellm integration and fallback estimation.
15
+
16
+ Uses litellm for accurate counting when available, otherwise falls back
17
+ to simple estimation (4 characters ≈ 1 token). Supports any model name
18
+ with basic caching mechanism.
19
+ """
20
+
21
+ def __init__(self, model_name: str):
22
+ """
23
+ Initialize TokenCounterModel instance.
24
+
25
+ Args:
26
+ model_name (str): Language model name (any name supported)
27
+ """
28
+ self.model_name = model_name
29
+ self._litellm_available = self._check_litellm_availability()
30
+ self._cache = {}
31
+
32
+ logger = logging.getLogger(__name__)
33
+ if logger.isEnabledFor(logging.DEBUG):
34
+ logger.debug(f"Initialized TokenCounterModel: {self.model_name}")
35
+
36
+ def token_count(self, text: str) -> int:
37
+ """
38
+ Calculate token count for given text.
39
+
40
+ Args:
41
+ text (str): Text to count tokens for
42
+
43
+ Returns:
44
+ int: Number of tokens
45
+ """
46
+ if not text:
47
+ return 0
48
+
49
+ text_hash = hash(text)
50
+ if text_hash in self._cache:
51
+ return self._cache[text_hash]
52
+
53
+ try:
54
+ if self._litellm_available:
55
+ token_count = self._count_with_litellm(text)
56
+ else:
57
+ token_count = self._count_with_estimation(text)
58
+
59
+ if len(self._cache) >= 1000:
60
+ self._cache.clear()
61
+ self._cache[text_hash] = token_count
62
+
63
+ return token_count
64
+
65
+ except Exception as e:
66
+ logging.getLogger(__name__).warning(f"Token calculation failed, using estimation: {e}")
67
+ return self._count_with_estimation(text)
68
+
69
+ def _check_litellm_availability(self) -> bool:
70
+ """Check if litellm is available."""
71
+ try:
72
+ from siada.provider.lazy_lite_llm import litellm
73
+ return True
74
+ except ImportError:
75
+ return False
76
+
77
+ def _count_with_litellm(self, text: str) -> int:
78
+ """Count tokens using litellm."""
79
+ from siada.provider.lazy_lite_llm import litellm
80
+ response = litellm.token_counter(model=self.model_name, text=text)
81
+ return int(response)
82
+
83
+ def _count_with_estimation(self, text: str) -> int:
84
+ """Count tokens using simple estimation (4 chars ≈ 1 token)."""
85
+ return max(1, len(text) // 4)
86
+
87
+ def __str__(self) -> str:
88
+ """String representation."""
89
+ return f"TokenCounterModel(model={self.model_name}, litellm={self._litellm_available})"
90
+
91
+ def __repr__(self) -> str:
92
+ """Detailed string representation."""
93
+ return f"TokenCounterModel(model_name='{self.model_name}', litellm_available={self._litellm_available})"
94
+
95
+
96
+ class OptimizedTokenCounterModel(TokenCounterModel):
97
+ """
98
+ Optimized token counter for long texts and large codebases.
99
+
100
+ Uses sampling for texts exceeding the threshold to improve performance.
101
+ """
102
+
103
+ def __init__(self, model_name: str, sampling_threshold: int = 10000):
104
+ """
105
+ Initialize optimized TokenCounterModel.
106
+
107
+ Args:
108
+ model_name (str): Model name
109
+ sampling_threshold (int): Threshold for sampling, texts longer than this will use sampling
110
+ """
111
+ super().__init__(model_name)
112
+ self.sampling_threshold = sampling_threshold
113
+
114
+ def token_count(self, text: str) -> int:
115
+ """
116
+ Optimized token counting with sampling for long texts.
117
+
118
+ Args:
119
+ text (str): Text to count tokens for
120
+
121
+ Returns:
122
+ int: Number of tokens
123
+ """
124
+ if not text:
125
+ return 0
126
+
127
+ if len(text) <= self.sampling_threshold:
128
+ return super().token_count(text)
129
+
130
+ return self._count_with_sampling(text)
131
+
132
+ def _count_with_sampling(self, text: str) -> int:
133
+ """Count tokens for long text using sampling method."""
134
+ lines = text.splitlines(keepends=True)
135
+ num_lines = len(lines)
136
+
137
+ if num_lines <= 100:
138
+ return super().token_count(text)
139
+
140
+ step = max(1, num_lines // 100)
141
+ sampled_lines = lines[::step]
142
+ sample_text = "".join(sampled_lines)
143
+
144
+ sample_tokens = super().token_count(sample_text)
145
+
146
+ sample_length = len(sample_text)
147
+ total_length = len(text)
148
+
149
+ if sample_length > 0:
150
+ estimated_tokens = int(sample_tokens * total_length / sample_length)
151
+ else:
152
+ estimated_tokens = self._count_with_estimation(text)
153
+
154
+ return estimated_tokens
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ Thread-based, killable spinner utility.
5
+
6
+ Use it like:
7
+
8
+ from .waiting import WaitingSpinner
9
+
10
+ spinner = WaitingSpinner("Waiting for LLM")
11
+ spinner.start()
12
+ ... # long task
13
+ spinner.stop()
14
+ """
15
+
16
+ import sys
17
+ import threading
18
+ import time
19
+
20
+ from rich.console import Console
21
+
22
+
23
+ class Spinner:
24
+ """
25
+ Minimal spinner that scans a single marker back and forth across a line.
26
+
27
+ The animation is pre-rendered into a list of frames. If the terminal
28
+ cannot display unicode the frames are converted to plain ASCII.
29
+ """
30
+
31
+ last_frame_idx = 0 # Class variable to store the last frame index
32
+
33
+ def __init__(self, text: str, width: int = 7):
34
+ self.text = text
35
+ self.start_time = time.time()
36
+ self.last_update = 0.0
37
+ self.visible = False
38
+ self.is_tty = sys.stdout.isatty()
39
+ self.console = Console()
40
+
41
+ # Pre-render the animation frames using pure ASCII so they will
42
+ # always display, even on very limited terminals.
43
+ ascii_frames = [
44
+ "#= ", # C1 C2 space(8)
45
+ "=# ", # C2 C1 space(8)
46
+ " =# ", # space(1) C2 C1 space(7)
47
+ " =# ", # space(2) C2 C1 space(6)
48
+ " =# ", # space(3) C2 C1 space(5)
49
+ " =# ", # space(4) C2 C1 space(4)
50
+ " =# ", # space(5) C2 C1 space(3)
51
+ " =# ", # space(6) C2 C1 space(2)
52
+ " =# ", # space(7) C2 C1 space(1)
53
+ " =#", # space(8) C2 C1
54
+ " #=", # space(8) C1 C2
55
+ " #= ", # space(7) C1 C2 space(1)
56
+ " #= ", # space(6) C1 C2 space(2)
57
+ " #= ", # space(5) C1 C2 space(3)
58
+ " #= ", # space(4) C1 C2 space(4)
59
+ " #= ", # space(3) C1 C2 space(5)
60
+ " #= ", # space(2) C1 C2 space(6)
61
+ " #= ", # space(1) C1 C2 space(7)
62
+ ]
63
+
64
+ self.unicode_palette = "░█"
65
+ xlate_from, xlate_to = ("=#", self.unicode_palette)
66
+
67
+ # If unicode is supported, swap the ASCII chars for nicer glyphs.
68
+ if self._supports_unicode():
69
+ translation_table = str.maketrans(xlate_from, xlate_to)
70
+ frames = [f.translate(translation_table) for f in ascii_frames]
71
+ self.scan_char = xlate_to[xlate_from.find("#")]
72
+ else:
73
+ frames = ascii_frames
74
+ self.scan_char = "#"
75
+
76
+ # Bounce the scanner back and forth.
77
+ self.frames = frames
78
+ self.frame_idx = Spinner.last_frame_idx # Initialize from class variable
79
+ self.width = len(frames[0]) - 2 # number of chars between the brackets
80
+ self.animation_len = len(frames[0])
81
+ self.last_display_len = 0 # Length of the last spinner line (frame + text)
82
+
83
+ def _supports_unicode(self) -> bool:
84
+ if not self.is_tty:
85
+ return False
86
+ try:
87
+ out = self.unicode_palette
88
+ out += "\b" * len(self.unicode_palette)
89
+ out += " " * len(self.unicode_palette)
90
+ out += "\b" * len(self.unicode_palette)
91
+ sys.stdout.write(out)
92
+ sys.stdout.flush()
93
+ return True
94
+ except UnicodeEncodeError:
95
+ return False
96
+ except Exception:
97
+ return False
98
+
99
+ def _next_frame(self) -> str:
100
+ frame = self.frames[self.frame_idx]
101
+ self.frame_idx = (self.frame_idx + 1) % len(self.frames)
102
+ Spinner.last_frame_idx = self.frame_idx # Update class variable
103
+ return frame
104
+
105
+ def step(self, text: str = None) -> None:
106
+ if text is not None:
107
+ self.text = text
108
+
109
+ if not self.is_tty:
110
+ return
111
+
112
+ now = time.time()
113
+ if not self.visible and now - self.start_time >= 0.5:
114
+ self.visible = True
115
+ self.last_update = 0.0
116
+ if self.is_tty:
117
+ self.console.show_cursor(False)
118
+
119
+ if not self.visible or now - self.last_update < 0.1:
120
+ return
121
+
122
+ self.last_update = now
123
+ frame_str = self._next_frame()
124
+
125
+ # Determine the maximum width for the spinner line
126
+ # Subtract 2 as requested, to leave a margin or prevent cursor wrapping issues
127
+ max_spinner_width = self.console.width - 2
128
+ if max_spinner_width < 0: # Handle extremely narrow terminals
129
+ max_spinner_width = 0
130
+
131
+ current_text_payload = f" {self.text}"
132
+ line_to_display = f"{frame_str}{current_text_payload}"
133
+
134
+ # Truncate the line if it's too long for the console width
135
+ if len(line_to_display) > max_spinner_width:
136
+ line_to_display = line_to_display[:max_spinner_width]
137
+
138
+ len_line_to_display = len(line_to_display)
139
+
140
+ # Calculate padding to clear any remnants from a longer previous line
141
+ padding_to_clear = " " * max(0, self.last_display_len - len_line_to_display)
142
+
143
+ # Write the spinner frame, text, and any necessary clearing spaces
144
+ sys.stdout.write(f"\r{line_to_display}{padding_to_clear}")
145
+ self.last_display_len = len_line_to_display
146
+
147
+ # Calculate number of backspaces to position cursor at the scanner character
148
+ scan_char_abs_pos = frame_str.find(self.scan_char)
149
+
150
+ # Total characters written to the line (frame + text + padding)
151
+ total_chars_written_on_line = len_line_to_display + len(padding_to_clear)
152
+
153
+ # num_backspaces will be non-positive if scan_char_abs_pos is beyond
154
+ # total_chars_written_on_line (e.g., if the scan char itself was truncated).
155
+ # (e.g., if the scan char itself was truncated).
156
+ # In such cases, (effectively) 0 backspaces are written,
157
+ # and the cursor stays at the end of the line.
158
+ num_backspaces = total_chars_written_on_line - scan_char_abs_pos
159
+ sys.stdout.write("\b" * num_backspaces)
160
+ sys.stdout.flush()
161
+
162
+ def end(self) -> None:
163
+ if self.visible and self.is_tty:
164
+ clear_len = self.last_display_len # Use the length of the last displayed content
165
+ sys.stdout.write("\r" + " " * clear_len + "\r")
166
+ sys.stdout.flush()
167
+ self.console.show_cursor(True)
168
+ self.visible = False
169
+
170
+
171
+ class WaitingSpinner:
172
+ """Background spinner that can be started/stopped safely."""
173
+
174
+ def __init__(self, text: str = "Waiting for LLM", delay: float = 0.15):
175
+ self.spinner = Spinner(text)
176
+ self.delay = delay
177
+ self._stop_event = threading.Event()
178
+ self._thread = threading.Thread(target=self._spin, daemon=True)
179
+
180
+ def _spin(self):
181
+ while not self._stop_event.is_set():
182
+ self.spinner.step()
183
+ time.sleep(self.delay)
184
+ self.spinner.end()
185
+
186
+ def start(self):
187
+ """Start the spinner in a background thread."""
188
+ if not self._thread.is_alive():
189
+ self._thread.start()
190
+
191
+ def stop(self):
192
+ """Request the spinner to stop and wait briefly for the thread to exit."""
193
+ self._stop_event.set()
194
+ if self._thread.is_alive():
195
+ self._thread.join(timeout=self.delay)
196
+ self.spinner.end()
197
+
198
+ # Allow use as a context-manager
199
+ def __enter__(self):
200
+ self.start()
201
+ return self
202
+
203
+ def __exit__(self, exc_type, exc_val, exc_tb):
204
+ self.stop()
205
+
206
+
207
+ def main():
208
+ spinner = Spinner("Running spinner...")
209
+ try:
210
+ for _ in range(100):
211
+ time.sleep(0.15)
212
+ spinner.step()
213
+ print("Success!")
214
+ except KeyboardInterrupt:
215
+ print("\nInterrupted by user.")
216
+ finally:
217
+ spinner.end()
218
+
219
+
220
+ if __name__ == "__main__":
221
+ main()
@@ -0,0 +1,39 @@
1
+ from agents import RunContextWrapper, function_tool
2
+
3
+ from siada.foundation.code_agent_context import CodeAgentContext
4
+ from siada.tools.coder.observation.observation import FunctionCallResult
5
+
6
+ REPRODUCE_COMPLETION_DOCS = f"""ISSUE Reproduction Completion Tool
7
+ Use this tool to submit your work results to the user whenever the ISSUE has been successfully reproduced—that is, when executing the test cases you generated can reliably reproduce the ISSUE. Include a brief description of your reproduction process.
8
+ IMPORTANT NOTE: Do not invoke this tool unless the issue has been confirmed as reproduced.
9
+ Args:
10
+ test_case: The full path of the test case used to reproduce the issue in the task.
11
+ bug_analysis: Analysis of the cause of the bug, including the involved code files, methods, and other relevant components.
12
+ """
13
+
14
+ class ReproduceCompletionResult(FunctionCallResult):
15
+ def __init__(self, content: str):
16
+ super().__init__(content=content)
17
+
18
+ def __str__(self):
19
+ return self.content
20
+
21
+ def format_for_display(self):
22
+ return "Reproduction Completed"
23
+
24
+
25
+ @function_tool(
26
+ name_override="reproduce_completion", description_override=REPRODUCE_COMPLETION_DOCS, failure_error_function=None
27
+ )
28
+ async def reproduce_completion(context: RunContextWrapper[CodeAgentContext], test_case: str, bug_analysis: str) -> ReproduceCompletionResult:
29
+ # 获取 session_id
30
+ content = (f"====\n"
31
+ f"This issue can be reproduced using test case : {test_case}.\n Analysis of the issue: {bug_analysis} \n"
32
+ f"When you start fixing the issue, you must thoroughly refer to this information."
33
+ f"\n====")
34
+
35
+ return ReproduceCompletionResult(
36
+ content=content
37
+ )
38
+
39
+
@@ -0,0 +1,60 @@
1
+ from agents import function_tool, RunContextWrapper
2
+
3
+ from siada.foundation.code_agent_context import CodeAgentContext
4
+ from siada.tools.coder.cmd_runner import run_cmd_impl
5
+ from siada.tools.coder.observation.observation import FunctionCallResult
6
+
7
+
8
+ RUN_CMD_DOCS = """Execute a shell command using the most appropriate method for the current environment.
9
+
10
+ This function automatically selects between pexpect (for interactive terminals on Unix-like
11
+ systems) and subprocess (for Windows or non-interactive environments) to execute shell
12
+ commands. It provides real-time output streaming and proper error handling.
13
+
14
+ Args:
15
+ command (str): The shell command to execute as a string.
16
+ """
17
+
18
+
19
+ class RunCmdResult(FunctionCallResult):
20
+ """This data class represents the output of a command."""
21
+
22
+ def __init__(self, command: str, output: str, code: int):
23
+ self.command = command
24
+ self.output = output
25
+ self.code = code if code is not None else 1
26
+
27
+ @property
28
+ def content(self) -> str:
29
+ """Return the command output as content."""
30
+ return str((self.code, self.output))
31
+
32
+ def format_for_display(self) -> str:
33
+ if self.code == 0:
34
+ return f"`{self.command}` executed successfully!"
35
+ else:
36
+ return f"`{self.command}` executed with error!"
37
+
38
+ def __str__(self):
39
+ return self.content
40
+
41
+
42
+ @function_tool
43
+ def run_cmd(context: RunContextWrapper[CodeAgentContext], command) -> FunctionCallResult:
44
+ """Execute a shell command using the most appropriate method for the current environment.
45
+
46
+ This function automatically selects between pexpect (for interactive terminals on Unix-like
47
+ systems) and subprocess (for Windows or non-interactive environments) to execute shell
48
+ commands. It provides real-time output streaming and proper error handling.
49
+
50
+ Args:
51
+ command (str): The shell command to execute as a string.
52
+ verbose (bool, optional): If True, prints detailed execution information including
53
+ the execution method used, shell information, and command details. Defaults to False.
54
+ error_print (callable, optional): Custom error printing function. If provided, errors
55
+ will be output using this function instead of the default print(). Should accept
56
+ a single string argument. Defaults to None.
57
+ """
58
+ cwd = context.context.root_dir
59
+ code, output = run_cmd_impl(command=command, cwd=cwd)
60
+ return RunCmdResult(command=command, output=output, code=code)
@@ -0,0 +1,25 @@
1
+ from agents import RunContextWrapper, function_tool
2
+
3
+ from siada.foundation.code_agent_context import CodeAgentContext
4
+
5
+ TEST_COMPLETION_DOCS = f"""Test Completion Tool
6
+ Use this tool to submit your test work results to the user whenever the test task has been completed. This tool reports whether tests passed or failed and provides detailed information about the test execution.
7
+ IMPORTANT NOTE: Do not invoke this tool unless the test task has been confirmed as completed.
8
+ Args:
9
+ is_passed: Integer type, 0 indicates test failed, 1 indicates test passed.
10
+ test_detail: String type. If tests passed, describe which test cases were executed. If tests failed, describe each failed test case and the failure reason.
11
+ """
12
+
13
+
14
+ @function_tool(
15
+ name_override="test_completion", description_override=TEST_COMPLETION_DOCS, failure_error_function=None
16
+ )
17
+ async def test_completion(context: RunContextWrapper[CodeAgentContext], is_passed: int, test_detail: str) -> str:
18
+ # 获取 session_id
19
+ test_result = "Tests passed" if is_passed == 1 else "Tests failed"
20
+
21
+ return {
22
+ "is_passed": is_passed,
23
+ "test_detail": test_result + ":" + test_detail
24
+ }
25
+