alita-sdk 0.3.351__py3-none-any.whl → 0.3.499__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 (206) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3601 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1256 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +64 -8
  30. alita_sdk/community/inventory/__init__.py +224 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/visualize.py +1370 -0
  58. alita_sdk/configurations/bitbucket.py +94 -2
  59. alita_sdk/configurations/confluence.py +96 -1
  60. alita_sdk/configurations/gitlab.py +79 -0
  61. alita_sdk/configurations/jira.py +103 -0
  62. alita_sdk/configurations/testrail.py +88 -0
  63. alita_sdk/configurations/xray.py +93 -0
  64. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  65. alita_sdk/configurations/zephyr_essential.py +75 -0
  66. alita_sdk/runtime/clients/artifact.py +1 -1
  67. alita_sdk/runtime/clients/client.py +214 -42
  68. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  69. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  70. alita_sdk/runtime/clients/sandbox_client.py +373 -0
  71. alita_sdk/runtime/langchain/assistant.py +118 -30
  72. alita_sdk/runtime/langchain/constants.py +8 -1
  73. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  74. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  75. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
  76. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +41 -12
  77. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -1
  78. alita_sdk/runtime/langchain/document_loaders/constants.py +116 -99
  79. alita_sdk/runtime/langchain/interfaces/llm_processor.py +2 -2
  80. alita_sdk/runtime/langchain/langraph_agent.py +307 -71
  81. alita_sdk/runtime/langchain/utils.py +48 -8
  82. alita_sdk/runtime/llms/preloaded.py +2 -6
  83. alita_sdk/runtime/models/mcp_models.py +61 -0
  84. alita_sdk/runtime/toolkits/__init__.py +26 -0
  85. alita_sdk/runtime/toolkits/application.py +9 -2
  86. alita_sdk/runtime/toolkits/artifact.py +18 -6
  87. alita_sdk/runtime/toolkits/datasource.py +13 -6
  88. alita_sdk/runtime/toolkits/mcp.py +780 -0
  89. alita_sdk/runtime/toolkits/planning.py +178 -0
  90. alita_sdk/runtime/toolkits/tools.py +205 -55
  91. alita_sdk/runtime/toolkits/vectorstore.py +9 -4
  92. alita_sdk/runtime/tools/__init__.py +11 -3
  93. alita_sdk/runtime/tools/application.py +7 -0
  94. alita_sdk/runtime/tools/artifact.py +225 -12
  95. alita_sdk/runtime/tools/function.py +95 -5
  96. alita_sdk/runtime/tools/graph.py +10 -4
  97. alita_sdk/runtime/tools/image_generation.py +212 -0
  98. alita_sdk/runtime/tools/llm.py +494 -102
  99. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  100. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  101. alita_sdk/runtime/tools/mcp_server_tool.py +4 -4
  102. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  103. alita_sdk/runtime/tools/planning/models.py +246 -0
  104. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  105. alita_sdk/runtime/tools/router.py +2 -1
  106. alita_sdk/runtime/tools/sandbox.py +180 -79
  107. alita_sdk/runtime/tools/vectorstore.py +22 -21
  108. alita_sdk/runtime/tools/vectorstore_base.py +125 -52
  109. alita_sdk/runtime/utils/AlitaCallback.py +106 -20
  110. alita_sdk/runtime/utils/mcp_client.py +465 -0
  111. alita_sdk/runtime/utils/mcp_oauth.py +244 -0
  112. alita_sdk/runtime/utils/mcp_sse_client.py +405 -0
  113. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  114. alita_sdk/runtime/utils/streamlit.py +40 -13
  115. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  116. alita_sdk/runtime/utils/utils.py +12 -0
  117. alita_sdk/tools/__init__.py +77 -33
  118. alita_sdk/tools/ado/repos/__init__.py +7 -6
  119. alita_sdk/tools/ado/repos/repos_wrapper.py +11 -11
  120. alita_sdk/tools/ado/test_plan/__init__.py +7 -7
  121. alita_sdk/tools/ado/wiki/__init__.py +7 -11
  122. alita_sdk/tools/ado/wiki/ado_wrapper.py +89 -15
  123. alita_sdk/tools/ado/work_item/__init__.py +7 -11
  124. alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
  125. alita_sdk/tools/advanced_jira_mining/__init__.py +8 -7
  126. alita_sdk/tools/aws/delta_lake/__init__.py +11 -9
  127. alita_sdk/tools/azure_ai/search/__init__.py +7 -6
  128. alita_sdk/tools/base_indexer_toolkit.py +345 -70
  129. alita_sdk/tools/bitbucket/__init__.py +9 -8
  130. alita_sdk/tools/bitbucket/api_wrapper.py +50 -6
  131. alita_sdk/tools/browser/__init__.py +4 -4
  132. alita_sdk/tools/carrier/__init__.py +4 -6
  133. alita_sdk/tools/chunkers/__init__.py +3 -1
  134. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  135. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  136. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  137. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  138. alita_sdk/tools/cloud/aws/__init__.py +7 -6
  139. alita_sdk/tools/cloud/azure/__init__.py +7 -6
  140. alita_sdk/tools/cloud/gcp/__init__.py +7 -6
  141. alita_sdk/tools/cloud/k8s/__init__.py +7 -6
  142. alita_sdk/tools/code/linter/__init__.py +7 -7
  143. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  144. alita_sdk/tools/code/sonar/__init__.py +8 -7
  145. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  146. alita_sdk/tools/confluence/__init__.py +9 -8
  147. alita_sdk/tools/confluence/api_wrapper.py +171 -75
  148. alita_sdk/tools/confluence/loader.py +10 -0
  149. alita_sdk/tools/custom_open_api/__init__.py +9 -4
  150. alita_sdk/tools/elastic/__init__.py +8 -7
  151. alita_sdk/tools/elitea_base.py +492 -52
  152. alita_sdk/tools/figma/__init__.py +7 -7
  153. alita_sdk/tools/figma/api_wrapper.py +2 -1
  154. alita_sdk/tools/github/__init__.py +9 -9
  155. alita_sdk/tools/github/api_wrapper.py +9 -26
  156. alita_sdk/tools/github/github_client.py +62 -2
  157. alita_sdk/tools/gitlab/__init__.py +8 -8
  158. alita_sdk/tools/gitlab/api_wrapper.py +135 -33
  159. alita_sdk/tools/gitlab_org/__init__.py +7 -8
  160. alita_sdk/tools/google/bigquery/__init__.py +11 -12
  161. alita_sdk/tools/google_places/__init__.py +8 -7
  162. alita_sdk/tools/jira/__init__.py +9 -7
  163. alita_sdk/tools/jira/api_wrapper.py +100 -52
  164. alita_sdk/tools/keycloak/__init__.py +8 -7
  165. alita_sdk/tools/localgit/local_git.py +56 -54
  166. alita_sdk/tools/memory/__init__.py +1 -1
  167. alita_sdk/tools/non_code_indexer_toolkit.py +3 -2
  168. alita_sdk/tools/ocr/__init__.py +8 -7
  169. alita_sdk/tools/openapi/__init__.py +10 -1
  170. alita_sdk/tools/pandas/__init__.py +8 -7
  171. alita_sdk/tools/postman/__init__.py +7 -8
  172. alita_sdk/tools/postman/api_wrapper.py +19 -8
  173. alita_sdk/tools/postman/postman_analysis.py +8 -1
  174. alita_sdk/tools/pptx/__init__.py +8 -9
  175. alita_sdk/tools/qtest/__init__.py +16 -11
  176. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  177. alita_sdk/tools/rally/__init__.py +7 -8
  178. alita_sdk/tools/report_portal/__init__.py +9 -7
  179. alita_sdk/tools/salesforce/__init__.py +7 -7
  180. alita_sdk/tools/servicenow/__init__.py +10 -10
  181. alita_sdk/tools/sharepoint/__init__.py +7 -6
  182. alita_sdk/tools/sharepoint/api_wrapper.py +127 -36
  183. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  184. alita_sdk/tools/sharepoint/utils.py +8 -2
  185. alita_sdk/tools/slack/__init__.py +7 -6
  186. alita_sdk/tools/sql/__init__.py +8 -7
  187. alita_sdk/tools/sql/api_wrapper.py +71 -23
  188. alita_sdk/tools/testio/__init__.py +7 -6
  189. alita_sdk/tools/testrail/__init__.py +8 -9
  190. alita_sdk/tools/utils/__init__.py +26 -4
  191. alita_sdk/tools/utils/content_parser.py +88 -60
  192. alita_sdk/tools/utils/text_operations.py +254 -0
  193. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +76 -26
  194. alita_sdk/tools/xray/__init__.py +9 -7
  195. alita_sdk/tools/zephyr/__init__.py +7 -6
  196. alita_sdk/tools/zephyr_enterprise/__init__.py +8 -6
  197. alita_sdk/tools/zephyr_essential/__init__.py +7 -6
  198. alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
  199. alita_sdk/tools/zephyr_scale/__init__.py +7 -6
  200. alita_sdk/tools/zephyr_squad/__init__.py +7 -6
  201. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/METADATA +147 -2
  202. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/RECORD +206 -130
  203. alita_sdk-0.3.499.dist-info/entry_points.txt +2 -0
  204. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/WHEEL +0 -0
  205. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/licenses/LICENSE +0 -0
  206. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,61 @@
1
- import logging
2
1
  import asyncio
2
+ import logging
3
3
  import subprocess
4
4
  import os
5
- from typing import Any, Type, Optional, Union, Dict
6
- from langchain_core.tools import BaseTool
7
- from pydantic import BaseModel, Field, create_model
5
+ from typing import Any, Type, Optional, Dict, List, Literal, Union
6
+ from copy import deepcopy
7
+ from pathlib import Path
8
+
9
+ from langchain_core.tools import BaseTool, BaseToolkit
10
+ from langchain_core.messages import ToolCall
11
+ from pydantic import BaseModel, create_model, ConfigDict, Field
8
12
  from pydantic.fields import FieldInfo
9
13
 
10
14
  logger = logging.getLogger(__name__)
11
15
 
16
+ name = "pyodide"
17
+
18
+
19
+ def get_tools(tools_list: list, alita_client=None, llm=None, memory_store=None):
20
+ """
21
+ Get sandbox tools for the provided tool configurations.
22
+
23
+ Args:
24
+ tools_list: List of tool configurations
25
+ alita_client: Alita client instance for sandbox tools
26
+ llm: LLM client instance (unused for sandbox)
27
+ memory_store: Optional memory store instance (unused for sandbox)
28
+
29
+ Returns:
30
+ List of sandbox tools
31
+ """
32
+ all_tools = []
33
+
34
+ for tool in tools_list:
35
+ if tool.get('type') == 'sandbox' or tool.get('toolkit_name') == 'sandbox':
36
+ try:
37
+ toolkit_instance = SandboxToolkit.get_toolkit(
38
+ stateful=tool['settings'].get('stateful', False),
39
+ allow_net=tool['settings'].get('allow_net', True),
40
+ alita_client=alita_client,
41
+ toolkit_name=tool.get('toolkit_name', '')
42
+ )
43
+ all_tools.extend(toolkit_instance.get_tools())
44
+ except Exception as e:
45
+ logger.error(f"Error in sandbox toolkit get_tools: {e}")
46
+ logger.error(f"Tool config: {tool}")
47
+ raise
48
+
49
+ return all_tools
50
+
12
51
 
13
52
  def _is_deno_available() -> bool:
14
53
  """Check if Deno is available in the PATH"""
15
54
  try:
16
55
  result = subprocess.run(
17
- ["deno", "--version"],
18
- capture_output=True,
19
- text=True,
56
+ ["deno", "--version"],
57
+ capture_output=True,
58
+ text=True,
20
59
  timeout=10
21
60
  )
22
61
  return result.returncode == 0
@@ -25,43 +64,17 @@ def _is_deno_available() -> bool:
25
64
 
26
65
 
27
66
  def _setup_pyodide_cache_env() -> None:
28
- """Setup Pyodide caching environment variables for performance optimization"""
67
+ """Setup Pyodide caching environment variables for performance optimization [NO-OP]"""
29
68
  try:
30
- # Check if cache environment file exists and source it
31
- cache_env_file = os.path.expanduser("~/.pyodide_cache_env")
32
- if os.path.exists(cache_env_file):
33
- with open(cache_env_file, 'r') as f:
34
- for line in f:
35
- line = line.strip()
36
- if line.startswith('export ') and '=' in line:
37
- # Parse export VAR=value format
38
- var_assignment = line[7:] # Remove 'export '
39
- if '=' in var_assignment:
40
- key, value = var_assignment.split('=', 1)
41
- # Remove quotes if present
42
- value = value.strip('"').strip("'")
43
- os.environ[key] = value
44
- logger.debug(f"Set Pyodide cache env: {key}={value}")
45
-
46
- # Set default caching environment variables if not already set
47
- cache_defaults = {
48
- 'PYODIDE_PACKAGES_PATH': os.path.expanduser('~/.cache/pyodide'),
49
- 'DENO_DIR': os.path.expanduser('~/.cache/deno'),
50
- 'PYODIDE_CACHE_DIR': os.path.expanduser('~/.cache/pyodide'),
51
- }
52
-
53
- for key, default_value in cache_defaults.items():
54
- if key not in os.environ:
55
- os.environ[key] = default_value
56
- logger.debug(f"Set default Pyodide env: {key}={default_value}")
57
-
69
+ for key in ["SANDBOX_BASE", "DENO_DIR"]:
70
+ logger.info("Sandbox env: %s -> %s", key, os.environ.get(key, "n/a"))
58
71
  except Exception as e:
59
72
  logger.warning(f"Could not setup Pyodide cache environment: {e}")
60
73
 
61
74
 
62
75
  # Create input schema for the sandbox tool
63
76
  sandbox_tool_input = create_model(
64
- "SandboxToolInput",
77
+ "SandboxToolInput",
65
78
  code=(str, FieldInfo(description="Python code to execute in the sandbox environment"))
66
79
  )
67
80
 
@@ -72,7 +85,7 @@ class PyodideSandboxTool(BaseTool):
72
85
  This tool leverages langchain-sandbox to provide a safe environment for running untrusted Python code.
73
86
  Optimized for performance with caching and stateless execution by default.
74
87
  """
75
-
88
+
76
89
  name: str = "pyodide_sandbox"
77
90
  description: str = """Execute Python code in a secure sandbox environment using Pyodide.
78
91
  This tool allows safe execution of Python code without access to the host system.
@@ -81,7 +94,7 @@ class PyodideSandboxTool(BaseTool):
81
94
  - Perform calculations or data analysis
82
95
  - Test Python algorithms
83
96
  - Run code that requires isolation from the host system
84
-
97
+
85
98
  The sandbox supports most Python standard library modules and can install additional packages.
86
99
  Note: File access and some system operations are restricted for security.
87
100
  Optimized for performance with local caching (stateless by default for faster execution).
@@ -91,14 +104,37 @@ class PyodideSandboxTool(BaseTool):
91
104
  allow_net: bool = True
92
105
  session_bytes: Optional[bytes] = None
93
106
  session_metadata: Optional[Dict] = None
94
-
107
+ alita_client: Optional[Any] = None
108
+
95
109
  def __init__(self, **kwargs: Any) -> None:
96
110
  super().__init__(**kwargs)
97
111
  self._sandbox = None
98
112
  # Setup caching environment for optimal performance
99
113
  _setup_pyodide_cache_env()
100
114
  self._initialize_sandbox()
101
-
115
+
116
+ def _prepare_pyodide_input(self, code: str) -> str:
117
+ """Prepare input for PyodideSandboxTool by injecting state and alita_client into the code block."""
118
+ pyodide_predata = ""
119
+
120
+ # Add alita_client if available
121
+ if self.alita_client:
122
+ try:
123
+ # Get the directory of the current file and construct the path to sandbox_client.py
124
+ current_dir = Path(__file__).parent
125
+ sandbox_client_path = current_dir.parent / 'clients' / 'sandbox_client.py'
126
+
127
+ with open(sandbox_client_path, 'r') as f:
128
+ sandbox_client_code = f.read()
129
+ pyodide_predata += f"{sandbox_client_code}\n"
130
+ pyodide_predata += (f"alita_client = SandboxClient(base_url='{self.alita_client.base_url}',"
131
+ f"project_id={self.alita_client.project_id},"
132
+ f"auth_token='{self.alita_client.auth_token}')\n")
133
+ except FileNotFoundError:
134
+ logger.error(f"sandbox_client.py not found. Ensure the file exists.")
135
+
136
+ return f"#elitea simplified client\n{pyodide_predata}{code}"
137
+
102
138
  def _initialize_sandbox(self) -> None:
103
139
  """Initialize the PyodideSandbox instance with optimized settings"""
104
140
  try:
@@ -110,12 +146,22 @@ class PyodideSandboxTool(BaseTool):
110
146
  )
111
147
  logger.error(error_msg)
112
148
  raise RuntimeError(error_msg)
113
-
149
+
114
150
  from langchain_sandbox import PyodideSandbox
115
-
151
+
152
+ # Air-gapped settings
153
+ sandbox_base = os.environ.get("SANDBOX_BASE", os.path.expanduser('~/.cache/pyodide'))
154
+ sandbox_tmp = os.path.join(sandbox_base, "tmp")
155
+ deno_cache = os.environ.get("DENO_DIR", os.path.expanduser('~/.cache/deno'))
156
+
116
157
  # Configure sandbox with performance optimizations
117
158
  self._sandbox = PyodideSandbox(
118
159
  stateful=self.stateful,
160
+ #
161
+ allow_env=["SANDBOX_BASE"],
162
+ allow_read=[sandbox_base, sandbox_tmp, deno_cache],
163
+ allow_write=[sandbox_tmp, deno_cache],
164
+ #
119
165
  allow_net=self.allow_net,
120
166
  # Use auto node_modules_dir for better caching
121
167
  node_modules_dir="auto"
@@ -135,7 +181,7 @@ class PyodideSandboxTool(BaseTool):
135
181
  except Exception as e:
136
182
  logger.error(f"Failed to initialize PyodideSandbox: {e}")
137
183
  raise
138
-
184
+
139
185
  def _run(self, code: str) -> str:
140
186
  """
141
187
  Synchronous version - runs the async method in a new event loop
@@ -144,7 +190,10 @@ class PyodideSandboxTool(BaseTool):
144
190
  # Check if sandbox is initialized, if not try to initialize
145
191
  if self._sandbox is None:
146
192
  self._initialize_sandbox()
147
-
193
+
194
+ # Prepare code with state and client injection
195
+ prepared_code = self._prepare_pyodide_input(code)
196
+
148
197
  # Check if we're already in an async context
149
198
  try:
150
199
  loop = asyncio.get_running_loop()
@@ -152,11 +201,11 @@ class PyodideSandboxTool(BaseTool):
152
201
  # We'll need to use a different approach
153
202
  import concurrent.futures
154
203
  with concurrent.futures.ThreadPoolExecutor() as executor:
155
- future = executor.submit(asyncio.run, self._arun(code))
204
+ future = executor.submit(asyncio.run, self._arun(prepared_code))
156
205
  return future.result()
157
206
  except RuntimeError:
158
207
  # No running loop, safe to use asyncio.run
159
- return asyncio.run(self._arun(code))
208
+ return asyncio.run(self._arun(prepared_code))
160
209
  except (ImportError, RuntimeError) as e:
161
210
  # Handle specific dependency errors gracefully
162
211
  error_msg = str(e)
@@ -169,7 +218,7 @@ class PyodideSandboxTool(BaseTool):
169
218
  except Exception as e:
170
219
  logger.error(f"Error executing code in sandbox: {e}")
171
220
  return f"Error executing code: {str(e)}"
172
-
221
+
173
222
  async def _arun(self, code: str) -> str:
174
223
  """
175
224
  Execute Python code in the Pyodide sandbox
@@ -177,47 +226,45 @@ class PyodideSandboxTool(BaseTool):
177
226
  try:
178
227
  if self._sandbox is None:
179
228
  self._initialize_sandbox()
180
-
229
+
181
230
  # Execute the code with session state if available
182
231
  result = await self._sandbox.execute(
183
232
  code,
184
233
  session_bytes=self.session_bytes,
185
234
  session_metadata=self.session_metadata
186
235
  )
187
-
236
+
188
237
  # Update session state for stateful execution
189
238
  if self.stateful:
190
239
  self.session_bytes = result.session_bytes
191
240
  self.session_metadata = result.session_metadata
192
-
193
- # Format the output
194
- output_parts = []
195
-
241
+
242
+ result_dict = {}
243
+
196
244
  if result.result is not None:
197
- output_parts.append(f"Result: {result.result}")
198
-
245
+ result_dict["result"] = result.result
246
+
199
247
  if result.stdout:
200
- output_parts.append(f"Output: {result.stdout}")
201
-
248
+ result_dict["output"] = result.stdout
249
+
202
250
  if result.stderr:
203
- output_parts.append(f"Error: {result.stderr}")
204
-
251
+ result_dict["error"] = result.stderr
252
+
205
253
  if result.status == 'error':
206
- output_parts.append(f"Execution failed with status: {result.status}")
207
-
254
+ result_dict["status"] = "Execution failed"
255
+
208
256
  execution_info = f"Execution time: {result.execution_time:.2f}s"
209
257
  if result.session_metadata and 'packages' in result.session_metadata:
210
258
  packages = result.session_metadata.get('packages', [])
211
259
  if packages:
212
260
  execution_info += f", Packages: {', '.join(packages)}"
213
-
214
- output_parts.append(execution_info)
215
-
216
- return "\n".join(output_parts) if output_parts else "Code executed successfully (no output)"
217
-
261
+
262
+ result_dict["execution_info"] = execution_info
263
+ return result_dict
264
+
218
265
  except Exception as e:
219
266
  logger.error(f"Error executing code in sandbox: {e}")
220
- return f"Error executing code: {str(e)}"
267
+ return {"error": f"Error executing code: {str(e)}"}
221
268
 
222
269
 
223
270
  class StatefulPyodideSandboxTool(PyodideSandboxTool):
@@ -225,7 +272,7 @@ class StatefulPyodideSandboxTool(PyodideSandboxTool):
225
272
  A stateful version of the PyodideSandboxTool that maintains state between executions.
226
273
  This version preserves variables, imports, and function definitions across multiple tool calls.
227
274
  """
228
-
275
+
229
276
  name: str = "stateful_pyodide_sandbox"
230
277
  description: str = """Execute Python code in a stateful sandbox environment using Pyodide.
231
278
  This tool maintains state between executions, preserving variables, imports, and function definitions.
@@ -234,41 +281,95 @@ class StatefulPyodideSandboxTool(PyodideSandboxTool):
234
281
  - Maintain variables across multiple calls
235
282
  - Develop complex programs step by step
236
283
  - Preserve imported libraries and defined functions
237
-
284
+
238
285
  The sandbox supports most Python standard library modules and can install additional packages.
239
286
  Note: File access and some system operations are restricted for security.
240
287
  """
241
-
288
+
242
289
  def __init__(self, **kwargs: Any) -> None:
243
290
  kwargs['stateful'] = True # Force stateful mode
244
291
  super().__init__(**kwargs)
245
292
 
246
293
 
247
294
  # Factory function for creating sandbox tools
248
- def create_sandbox_tool(stateful: bool = False, allow_net: bool = True) -> BaseTool:
295
+ def create_sandbox_tool(stateful: bool = False, allow_net: bool = True, alita_client: Optional[Any] = None) -> BaseTool:
249
296
  """
250
297
  Factory function to create sandbox tools with specified configuration.
251
-
298
+
252
299
  Note: This tool requires Deno to be installed and available in PATH.
253
300
  For installation and optimization, run the bootstrap.sh script.
254
-
301
+
255
302
  Args:
256
303
  stateful: Whether to maintain state between executions (default: False for better performance)
257
304
  allow_net: Whether to allow network access (for package installation)
258
-
305
+
259
306
  Returns:
260
307
  Configured sandbox tool instance
261
-
308
+
262
309
  Raises:
263
310
  ImportError: If langchain-sandbox is not installed
264
311
  RuntimeError: If Deno is not found in PATH
265
-
312
+
266
313
  Performance Notes:
267
314
  - Stateless mode (default) is faster and avoids session state overhead
268
315
  - Run bootstrap.sh script to enable local caching and reduce initialization time
269
316
  - Cached wheels reduce package download time from ~4.76s to near-instant
270
317
  """
271
318
  if stateful:
272
- return StatefulPyodideSandboxTool(allow_net=allow_net)
319
+ return StatefulPyodideSandboxTool(allow_net=allow_net, alita_client=alita_client)
273
320
  else:
274
- return PyodideSandboxTool(stateful=False, allow_net=allow_net)
321
+ return PyodideSandboxTool(stateful=False, allow_net=allow_net, alita_client=alita_client)
322
+
323
+
324
+ class SandboxToolkit(BaseToolkit):
325
+ tools: List[BaseTool] = []
326
+
327
+ @staticmethod
328
+ def toolkit_config_schema() -> Type[BaseModel]:
329
+ # Create sample tools to get their schemas
330
+ sample_tools = [
331
+ PyodideSandboxTool(),
332
+ StatefulPyodideSandboxTool()
333
+ ]
334
+ selected_tools = {x.name: x.args_schema.model_json_schema() for x in sample_tools}
335
+
336
+ return create_model(
337
+ 'sandbox',
338
+ stateful=(bool, Field(default=False, description="Whether to maintain state between executions")),
339
+ allow_net=(bool, Field(default=True, description="Whether to allow network access for package installation")),
340
+ selected_tools=(List[Literal[tuple(selected_tools)]],
341
+ Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
342
+
343
+ __config__=ConfigDict(json_schema_extra={
344
+ 'metadata': {
345
+ "label": "Python Sandbox",
346
+ "icon_url": "sandbox.svg",
347
+ "hidden": False,
348
+ "categories": ["code", "execution", "internal_tool"],
349
+ "extra_categories": ["python", "pyodide", "sandbox", "code execution"],
350
+ }
351
+ })
352
+ )
353
+
354
+ @classmethod
355
+ def get_toolkit(cls, stateful: bool = False, allow_net: bool = True, alita_client=None, **kwargs):
356
+ """
357
+ Get toolkit with sandbox tools.
358
+
359
+ Args:
360
+ stateful: Whether to maintain state between executions
361
+ allow_net: Whether to allow network access
362
+ alita_client: Alita client instance for sandbox tools
363
+ **kwargs: Additional arguments
364
+ """
365
+ tools = []
366
+
367
+ if stateful:
368
+ tools.append(StatefulPyodideSandboxTool(allow_net=allow_net, alita_client=alita_client))
369
+ else:
370
+ tools.append(PyodideSandboxTool(stateful=False, allow_net=allow_net, alita_client=alita_client))
371
+
372
+ return cls(tools=tools)
373
+
374
+ def get_tools(self):
375
+ return self.tools
@@ -207,9 +207,9 @@ class VectorStoreWrapper(BaseToolApiWrapper):
207
207
  tool_name="_remove_collection"
208
208
  )
209
209
 
210
- def _get_indexed_ids(self, collection_suffix: Optional[str] = '') -> List[str]:
210
+ def _get_indexed_ids(self, index_name: Optional[str] = '') -> List[str]:
211
211
  """Get all indexed document IDs from vectorstore"""
212
- return self.vector_adapter.get_indexed_ids(self, collection_suffix)
212
+ return self.vector_adapter.get_indexed_ids(self, index_name)
213
213
 
214
214
  def list_collections(self) -> Any:
215
215
  """List all collections in the vectorstore.
@@ -233,7 +233,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
233
233
  return {"collections": [], "message": "No indexed collections"}
234
234
  return cols
235
235
 
236
- def _clean_collection(self, collection_suffix: str = ''):
236
+ def _clean_collection(self, index_name: str = ''):
237
237
  """
238
238
  Clean the vectorstore collection by deleting all indexed data.
239
239
  """
@@ -241,15 +241,15 @@ class VectorStoreWrapper(BaseToolApiWrapper):
241
241
  f"Cleaning collection '{self.dataset}'",
242
242
  tool_name="_clean_collection"
243
243
  )
244
- self.vector_adapter.clean_collection(self, collection_suffix)
244
+ self.vector_adapter.clean_collection(self, index_name)
245
245
  self._log_data(
246
246
  f"Collection '{self.dataset}' has been cleaned. ",
247
247
  tool_name="_clean_collection"
248
248
  )
249
249
 
250
- def _get_code_indexed_data(self, collection_suffix: str) -> Dict[str, Dict[str, Any]]:
250
+ def _get_code_indexed_data(self, index_name: str) -> Dict[str, Dict[str, Any]]:
251
251
  """ Get all indexed data from vectorstore for code content """
252
- return self.vector_adapter.get_code_indexed_data(self, collection_suffix)
252
+ return self.vector_adapter.get_code_indexed_data(self, index_name)
253
253
 
254
254
  def _add_to_collection(self, entry_id, new_collection_value):
255
255
  """Add a new collection name to the `collection` key in the `metadata` column."""
@@ -258,7 +258,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
258
258
  def _reduce_duplicates(
259
259
  self,
260
260
  documents: Generator[Any, None, None],
261
- collection_suffix: str,
261
+ index_name: str,
262
262
  get_indexed_data: Callable,
263
263
  key_fn: Callable,
264
264
  compare_fn: Callable,
@@ -267,7 +267,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
267
267
  ) -> List[Any]:
268
268
  """Generic duplicate reduction logic for documents."""
269
269
  self._log_data(log_msg, tool_name="index_documents")
270
- indexed_data = get_indexed_data(collection_suffix)
270
+ indexed_data = get_indexed_data(index_name)
271
271
  indexed_keys = set(indexed_data.keys())
272
272
  if not indexed_keys:
273
273
  self._log_data("Vectorstore is empty, indexing all incoming documents", tool_name="index_documents")
@@ -279,14 +279,14 @@ class VectorStoreWrapper(BaseToolApiWrapper):
279
279
  for document in documents:
280
280
  key = key_fn(document)
281
281
  key = key if isinstance(key, str) else str(key)
282
- if key in indexed_keys and collection_suffix == indexed_data[key]['metadata'].get('collection'):
282
+ if key in indexed_keys and index_name == indexed_data[key]['metadata'].get('collection'):
283
283
  if compare_fn(document, indexed_data[key]):
284
284
  # Disabled addition of new collection to already indexed documents
285
285
  # # check metadata.collection and update if needed
286
286
  # for update_collection_id in remove_ids_fn(indexed_data, key):
287
287
  # self._add_to_collection(
288
288
  # update_collection_id,
289
- # collection_suffix
289
+ # index_name
290
290
  # )
291
291
  continue
292
292
  final_docs.append(document)
@@ -303,10 +303,10 @@ class VectorStoreWrapper(BaseToolApiWrapper):
303
303
 
304
304
  return final_docs
305
305
 
306
- def _reduce_code_duplicates(self, documents: Generator[Any, None, None], collection_suffix: str) -> List[Any]:
306
+ def _reduce_code_duplicates(self, documents: Generator[Any, None, None], index_name: str) -> List[Any]:
307
307
  return self._reduce_duplicates(
308
308
  documents,
309
- collection_suffix,
309
+ index_name,
310
310
  self._get_code_indexed_data,
311
311
  lambda doc: doc.metadata.get('filename'),
312
312
  lambda doc, idx: (
@@ -318,7 +318,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
318
318
  log_msg="Verification of code documents to index started"
319
319
  )
320
320
 
321
- def index_documents(self, documents: Generator[Document, None, None], collection_suffix: str, progress_step: int = 20, clean_index: bool = True, is_code: bool = True):
321
+ def index_documents(self, documents: Generator[Document, None, None], index_name: str, progress_step: int = 20, clean_index: bool = True, is_code: bool = True):
322
322
  """ Index documents in the vectorstore.
323
323
 
324
324
  Args:
@@ -329,13 +329,13 @@ class VectorStoreWrapper(BaseToolApiWrapper):
329
329
 
330
330
  from ..langchain.interfaces.llm_processor import add_documents
331
331
 
332
- self._log_tool_event(message=f"Starting the indexing... Parameters: {collection_suffix=}, {clean_index=}, {is_code}", tool_name="index_documents")
332
+ self._log_tool_event(message=f"Starting the indexing... Parameters: {index_name=}, {clean_index=}, {is_code}", tool_name="index_documents")
333
333
  # pre-process documents if needed (find duplicates, etc.)
334
334
  if clean_index:
335
335
  logger.info("Cleaning index before re-indexing all documents.")
336
336
  self._log_data("Cleaning index before re-indexing all documents. Previous index will be removed", tool_name="index_documents")
337
337
  try:
338
- self._clean_collection(collection_suffix)
338
+ self._clean_collection(index_name)
339
339
  self.vectoradapter.persist()
340
340
  self.vectoradapter.vacuum()
341
341
  self._log_data("Previous index has been removed",
@@ -349,7 +349,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
349
349
  message="Filter for duplicates",
350
350
  tool_name="index_documents")
351
351
  # remove duplicates based on metadata 'id' and 'updated_on' or 'commit_hash' fields
352
- documents = self._reduce_code_duplicates(documents, collection_suffix)
352
+ documents = self._reduce_code_duplicates(documents, index_name)
353
353
  self._log_tool_event(
354
354
  message="All the duplicates were filtered out. Proceeding with indexing.",
355
355
  tool_name="index_documents")
@@ -377,13 +377,13 @@ class VectorStoreWrapper(BaseToolApiWrapper):
377
377
  self._log_tool_event(message=f"Documents for indexing were processed. Total documents: {len(documents)}",
378
378
  tool_name="index_documents")
379
379
 
380
- # if collection_suffix is provided, add it to metadata of each document
381
- if collection_suffix:
380
+ # if index_name is provided, add it to metadata of each document
381
+ if index_name:
382
382
  for doc in documents:
383
383
  if not doc.metadata.get('collection'):
384
- doc.metadata['collection'] = collection_suffix
384
+ doc.metadata['collection'] = index_name
385
385
  else:
386
- doc.metadata['collection'] += f";{collection_suffix}"
386
+ doc.metadata['collection'] += f";{index_name}"
387
387
 
388
388
  total_docs = len(documents)
389
389
  documents_count = 0
@@ -414,7 +414,8 @@ class VectorStoreWrapper(BaseToolApiWrapper):
414
414
  return {"status": "error", "message": f"Error: {format_exc()}"}
415
415
  if _documents:
416
416
  add_documents(vectorstore=self.vectorstore, documents=_documents)
417
- return {"status": "ok", "message": f"successfully indexed {documents_count} documents"}
417
+ return {"status": "ok", "message": f"successfully indexed {documents_count} documents" if documents_count > 0
418
+ else "No new documents to index."}
418
419
 
419
420
  def search_documents(self, query:str, doctype: str = 'code',
420
421
  filter:dict|str={}, cut_off: float=0.5,