alita-sdk 0.3.376__py3-none-any.whl → 0.3.435__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.

Potentially problematic release.


This version of alita-sdk might be problematic. Click here for more details.

Files changed (60) hide show
  1. alita_sdk/configurations/bitbucket.py +95 -0
  2. alita_sdk/configurations/confluence.py +96 -1
  3. alita_sdk/configurations/gitlab.py +79 -0
  4. alita_sdk/configurations/jira.py +103 -0
  5. alita_sdk/configurations/testrail.py +88 -0
  6. alita_sdk/configurations/xray.py +93 -0
  7. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  8. alita_sdk/configurations/zephyr_essential.py +75 -0
  9. alita_sdk/runtime/clients/client.py +9 -4
  10. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  11. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  12. alita_sdk/runtime/clients/sandbox_client.py +8 -0
  13. alita_sdk/runtime/langchain/assistant.py +41 -38
  14. alita_sdk/runtime/langchain/constants.py +5 -1
  15. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  16. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
  17. alita_sdk/runtime/langchain/document_loaders/constants.py +28 -12
  18. alita_sdk/runtime/langchain/langraph_agent.py +91 -27
  19. alita_sdk/runtime/langchain/utils.py +24 -4
  20. alita_sdk/runtime/models/mcp_models.py +57 -0
  21. alita_sdk/runtime/toolkits/__init__.py +24 -0
  22. alita_sdk/runtime/toolkits/application.py +8 -1
  23. alita_sdk/runtime/toolkits/mcp.py +787 -0
  24. alita_sdk/runtime/toolkits/tools.py +98 -50
  25. alita_sdk/runtime/tools/__init__.py +7 -2
  26. alita_sdk/runtime/tools/application.py +7 -0
  27. alita_sdk/runtime/tools/function.py +20 -28
  28. alita_sdk/runtime/tools/graph.py +10 -4
  29. alita_sdk/runtime/tools/image_generation.py +104 -8
  30. alita_sdk/runtime/tools/llm.py +146 -114
  31. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  32. alita_sdk/runtime/tools/mcp_server_tool.py +79 -10
  33. alita_sdk/runtime/tools/sandbox.py +166 -63
  34. alita_sdk/runtime/tools/vectorstore.py +3 -2
  35. alita_sdk/runtime/tools/vectorstore_base.py +4 -3
  36. alita_sdk/runtime/utils/streamlit.py +34 -3
  37. alita_sdk/runtime/utils/toolkit_utils.py +5 -2
  38. alita_sdk/runtime/utils/utils.py +1 -0
  39. alita_sdk/tools/__init__.py +48 -31
  40. alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
  41. alita_sdk/tools/base_indexer_toolkit.py +75 -66
  42. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  43. alita_sdk/tools/code_indexer_toolkit.py +13 -3
  44. alita_sdk/tools/confluence/api_wrapper.py +29 -7
  45. alita_sdk/tools/confluence/loader.py +10 -0
  46. alita_sdk/tools/elitea_base.py +7 -7
  47. alita_sdk/tools/gitlab/api_wrapper.py +11 -7
  48. alita_sdk/tools/jira/api_wrapper.py +1 -1
  49. alita_sdk/tools/openapi/__init__.py +10 -1
  50. alita_sdk/tools/qtest/api_wrapper.py +522 -74
  51. alita_sdk/tools/sharepoint/api_wrapper.py +104 -33
  52. alita_sdk/tools/sharepoint/authorization_helper.py +175 -1
  53. alita_sdk/tools/sharepoint/utils.py +8 -2
  54. alita_sdk/tools/utils/content_parser.py +27 -16
  55. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +19 -6
  56. {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.435.dist-info}/METADATA +1 -1
  57. {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.435.dist-info}/RECORD +60 -55
  58. {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.435.dist-info}/WHEEL +0 -0
  59. {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.435.dist-info}/licenses/LICENSE +0 -0
  60. {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.435.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ from logging import getLogger
3
3
  from typing import Any, Type, Literal, Optional, Union, List
4
4
 
5
5
  from langchain_core.tools import BaseTool
6
- from pydantic import BaseModel, Field, create_model, EmailStr, constr
6
+ from pydantic import BaseModel, Field, create_model, EmailStr, constr, ConfigDict
7
7
 
8
8
  from ...tools.utils import TOOLKIT_SPLITTER
9
9
 
@@ -15,9 +15,61 @@ class McpServerTool(BaseTool):
15
15
  description: str
16
16
  args_schema: Optional[Type[BaseModel]] = None
17
17
  return_type: str = "str"
18
- client: Any
18
+ client: Any = Field(default=None, exclude=True) # Exclude from serialization
19
19
  server: str
20
20
  tool_timeout_sec: int = 60
21
+ is_prompt: bool = False # Flag to indicate if this is a prompt tool
22
+ prompt_name: Optional[str] = None # Original prompt name if this is a prompt
23
+
24
+ model_config = ConfigDict(arbitrary_types_allowed=True)
25
+
26
+ def __getstate__(self):
27
+ """Custom serialization to exclude non-serializable objects."""
28
+ state = self.__dict__.copy()
29
+ # Remove the client since it contains threading objects that can't be pickled
30
+ state['client'] = None
31
+ # Store args_schema as a schema dict instead of the dynamic class
32
+ if hasattr(self, 'args_schema') and self.args_schema is not None:
33
+ # Convert the Pydantic model back to schema dict for pickling
34
+ try:
35
+ state['_args_schema_dict'] = self.args_schema.model_json_schema()
36
+ state['args_schema'] = None
37
+ except Exception as e:
38
+ logger.warning(f"Failed to serialize args_schema: {e}")
39
+ # If conversion fails, just remove it
40
+ state['args_schema'] = None
41
+ state['_args_schema_dict'] = {}
42
+ return state
43
+
44
+ def __setstate__(self, state):
45
+ """Custom deserialization to handle missing objects."""
46
+ # Restore the args_schema from the stored schema dict
47
+ args_schema_dict = state.pop('_args_schema_dict', {})
48
+
49
+ # Initialize required Pydantic internal attributes
50
+ if '__pydantic_fields_set__' not in state:
51
+ state['__pydantic_fields_set__'] = set(state.keys())
52
+ if '__pydantic_extra__' not in state:
53
+ state['__pydantic_extra__'] = None
54
+ if '__pydantic_private__' not in state:
55
+ state['__pydantic_private__'] = None
56
+
57
+ # Directly update the object's __dict__ to bypass Pydantic validation
58
+ self.__dict__.update(state)
59
+
60
+ # Recreate the args_schema from the stored dict if available
61
+ if args_schema_dict:
62
+ try:
63
+ recreated_schema = self.create_pydantic_model_from_schema(args_schema_dict)
64
+ self.__dict__['args_schema'] = recreated_schema
65
+ except Exception as e:
66
+ logger.warning(f"Failed to recreate args_schema: {e}")
67
+ self.__dict__['args_schema'] = None
68
+ else:
69
+ self.__dict__['args_schema'] = None
70
+
71
+ # Note: client will be None after unpickling
72
+ # The toolkit should reinitialize the client when needed
21
73
 
22
74
 
23
75
  @staticmethod
@@ -90,14 +142,31 @@ class McpServerTool(BaseTool):
90
142
  return create_model(model_name, **fields)
91
143
 
92
144
  def _run(self, *args, **kwargs):
93
- call_data = {
94
- "server": self.server,
95
- "tool_timeout_sec": self.tool_timeout_sec,
96
- "tool_call_id": str(uuid.uuid4()),
97
- "params": {
98
- "name": self.name.rsplit(TOOLKIT_SPLITTER)[1] if TOOLKIT_SPLITTER in self.name else self.name,
99
- "arguments": kwargs
145
+ # Extract the actual tool/prompt name (remove toolkit prefix)
146
+ actual_name = self.name.rsplit(TOOLKIT_SPLITTER)[1] if TOOLKIT_SPLITTER in self.name else self.name
147
+
148
+ if self.is_prompt:
149
+ # For prompts, use prompts/get endpoint
150
+ call_data = {
151
+ "server": self.server,
152
+ "tool_timeout_sec": self.tool_timeout_sec,
153
+ "tool_call_id": str(uuid.uuid4()),
154
+ "method": "prompts/get",
155
+ "params": {
156
+ "name": self.prompt_name or actual_name.replace("prompt_", ""),
157
+ "arguments": kwargs.get("arguments", kwargs)
158
+ }
159
+ }
160
+ else:
161
+ # For regular tools, use tools/call endpoint
162
+ call_data = {
163
+ "server": self.server,
164
+ "tool_timeout_sec": self.tool_timeout_sec,
165
+ "tool_call_id": str(uuid.uuid4()),
166
+ "params": {
167
+ "name": actual_name,
168
+ "arguments": kwargs
169
+ }
100
170
  }
101
- }
102
171
 
103
172
  return self.client.mcp_tool_call(call_data)
@@ -2,21 +2,60 @@ import asyncio
2
2
  import logging
3
3
  import subprocess
4
4
  import os
5
- from typing import Any, Type, Optional, Dict
6
- from langchain_core.tools import BaseTool
7
- from pydantic import BaseModel, 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,19 +226,19 @@ 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
-
241
+
193
242
  result_dict = {}
194
243
 
195
244
  if result.result is not None:
@@ -212,10 +261,10 @@ class PyodideSandboxTool(BaseTool):
212
261
 
213
262
  result_dict["execution_info"] = execution_info
214
263
  return result_dict
215
-
264
+
216
265
  except Exception as e:
217
266
  logger.error(f"Error executing code in sandbox: {e}")
218
- return f"Error executing code: {str(e)}"
267
+ return {"error": f"Error executing code: {str(e)}"}
219
268
 
220
269
 
221
270
  class StatefulPyodideSandboxTool(PyodideSandboxTool):
@@ -223,7 +272,7 @@ class StatefulPyodideSandboxTool(PyodideSandboxTool):
223
272
  A stateful version of the PyodideSandboxTool that maintains state between executions.
224
273
  This version preserves variables, imports, and function definitions across multiple tool calls.
225
274
  """
226
-
275
+
227
276
  name: str = "stateful_pyodide_sandbox"
228
277
  description: str = """Execute Python code in a stateful sandbox environment using Pyodide.
229
278
  This tool maintains state between executions, preserving variables, imports, and function definitions.
@@ -232,41 +281,95 @@ class StatefulPyodideSandboxTool(PyodideSandboxTool):
232
281
  - Maintain variables across multiple calls
233
282
  - Develop complex programs step by step
234
283
  - Preserve imported libraries and defined functions
235
-
284
+
236
285
  The sandbox supports most Python standard library modules and can install additional packages.
237
286
  Note: File access and some system operations are restricted for security.
238
287
  """
239
-
288
+
240
289
  def __init__(self, **kwargs: Any) -> None:
241
290
  kwargs['stateful'] = True # Force stateful mode
242
291
  super().__init__(**kwargs)
243
292
 
244
293
 
245
294
  # Factory function for creating sandbox tools
246
- 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:
247
296
  """
248
297
  Factory function to create sandbox tools with specified configuration.
249
-
298
+
250
299
  Note: This tool requires Deno to be installed and available in PATH.
251
300
  For installation and optimization, run the bootstrap.sh script.
252
-
301
+
253
302
  Args:
254
303
  stateful: Whether to maintain state between executions (default: False for better performance)
255
304
  allow_net: Whether to allow network access (for package installation)
256
-
305
+
257
306
  Returns:
258
307
  Configured sandbox tool instance
259
-
308
+
260
309
  Raises:
261
310
  ImportError: If langchain-sandbox is not installed
262
311
  RuntimeError: If Deno is not found in PATH
263
-
312
+
264
313
  Performance Notes:
265
314
  - Stateless mode (default) is faster and avoids session state overhead
266
315
  - Run bootstrap.sh script to enable local caching and reduce initialization time
267
316
  - Cached wheels reduce package download time from ~4.76s to near-instant
268
317
  """
269
318
  if stateful:
270
- return StatefulPyodideSandboxTool(allow_net=allow_net)
319
+ return StatefulPyodideSandboxTool(allow_net=allow_net, alita_client=alita_client)
271
320
  else:
272
- 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
@@ -215,7 +215,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
215
215
  """List all collections in the vectorstore.
216
216
  Returns a list of collection names, or if no collections exist,
217
217
  returns a dict with an empty list and a message."""
218
- raw = self.vector_adapter.list_indexes(self)
218
+ raw = self.vector_adapter.list_collections(self)
219
219
  # Normalize raw result to a list of names
220
220
  if not raw:
221
221
  # No collections found
@@ -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,
@@ -208,10 +208,10 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
208
208
  logger.error(f"Error during similarity search: {str(e)}")
209
209
  raise ToolException(f"Search failed: {str(e)}")
210
210
 
211
- def list_indexes(self) -> List[str]:
211
+ def list_collections(self) -> List[str]:
212
212
  """List all collections in the vectorstore."""
213
213
 
214
- collections = self.vector_adapter.list_indexes(self)
214
+ collections = self.vector_adapter.list_collections(self)
215
215
  if not collections:
216
216
  return "No indexed collections"
217
217
  return collections
@@ -308,7 +308,8 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
308
308
  return {"status": "error", "message": f"Error: {format_exc()}"}
309
309
  if _documents:
310
310
  add_documents(vectorstore=self.vectorstore, documents=_documents)
311
- return {"status": "ok", "message": f"successfully indexed {documents_count} documents"}
311
+ return {"status": "ok", "message": f"successfully indexed {documents_count} documents" if documents_count > 0
312
+ else "no documents to index"}
312
313
 
313
314
  def search_documents(self, query:str, doctype: str = 'code',
314
315
  filter:dict|str={}, cut_off: float=0.5,
@@ -868,10 +868,24 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
868
868
  label = f"{'🔒 ' if is_secret else ''}{'*' if is_required else ''}{field_name.replace('_', ' ').title()}"
869
869
 
870
870
  if field_type == 'string':
871
- if is_secret:
871
+ # Check if this is an enum field
872
+ if field_schema.get('enum'):
873
+ # Dropdown for enum values
874
+ options = field_schema['enum']
875
+ default_index = 0
876
+ if default_value and str(default_value) in options:
877
+ default_index = options.index(str(default_value))
878
+ toolkit_config_values[field_name] = st.selectbox(
879
+ label,
880
+ options=options,
881
+ index=default_index,
882
+ help=field_description,
883
+ key=f"config_{field_name}_{selected_toolkit_idx}"
884
+ )
885
+ elif is_secret:
872
886
  toolkit_config_values[field_name] = st.text_input(
873
887
  label,
874
- value=str(default_value) if default_value else '',
888
+ value=str(default_value) if default_value else '',
875
889
  help=field_description,
876
890
  type="password",
877
891
  key=f"config_{field_name}_{selected_toolkit_idx}"
@@ -879,7 +893,7 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
879
893
  else:
880
894
  toolkit_config_values[field_name] = st.text_input(
881
895
  label,
882
- value=str(default_value) if default_value else '',
896
+ value=str(default_value) if default_value else '',
883
897
  help=field_description,
884
898
  key=f"config_{field_name}_{selected_toolkit_idx}"
885
899
  )
@@ -971,6 +985,23 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
971
985
  key=f"config_{field_name}_{selected_toolkit_idx}"
972
986
  )
973
987
  toolkit_config_values[field_name] = [line.strip() for line in array_input.split('\n') if line.strip()]
988
+ elif field_type == 'object':
989
+ # Handle object/dict types (like headers)
990
+ obj_input = st.text_area(
991
+ f"{label} (JSON object)",
992
+ value=json.dumps(default_value) if isinstance(default_value, dict) else str(default_value) if default_value else '',
993
+ help=f"{field_description} - Enter as JSON object, e.g. {{\"Authorization\": \"Bearer token\"}}",
994
+ placeholder='{"key": "value"}',
995
+ key=f"config_{field_name}_{selected_toolkit_idx}"
996
+ )
997
+ try:
998
+ if obj_input.strip():
999
+ toolkit_config_values[field_name] = json.loads(obj_input)
1000
+ else:
1001
+ toolkit_config_values[field_name] = None
1002
+ except json.JSONDecodeError as e:
1003
+ st.error(f"Invalid JSON format for {field_name}: {e}")
1004
+ toolkit_config_values[field_name] = None
974
1005
  else:
975
1006
  st.info("This toolkit doesn't require additional configuration.")
976
1007
 
@@ -46,11 +46,14 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
46
46
  # Log the configuration being used
47
47
  logger.info(f"Instantiating toolkit {toolkit_name} with LLM client")
48
48
  logger.debug(f"Toolkit {toolkit_name} configuration: {toolkit_config}")
49
-
49
+
50
+ # Use toolkit type from config, or fall back to lowercase toolkit name
51
+ toolkit_type = toolkit_config.get('type', toolkit_name.lower())
52
+
50
53
  # Create a tool configuration dict with required fields
51
54
  tool_config = {
52
55
  'id': toolkit_config.get('id', random.randint(1, 1000000)),
53
- 'type': toolkit_config.get('type', toolkit_name.lower()),
56
+ 'type': toolkit_config.get('type', toolkit_type),
54
57
  'settings': settings,
55
58
  'toolkit_name': toolkit_name
56
59
  }
@@ -14,6 +14,7 @@ class IndexerKeywords(Enum):
14
14
  INDEX_META_TYPE = 'index_meta'
15
15
  INDEX_META_IN_PROGRESS = 'in_progress'
16
16
  INDEX_META_COMPLETED = 'completed'
17
+ INDEX_META_FAILED = 'failed'
17
18
 
18
19
  # This pattern matches characters that are NOT alphanumeric, underscores, or hyphens
19
20
  clean_string_pattern = re.compile(r'[^a-zA-Z0-9_.-]')