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
@@ -0,0 +1,124 @@
1
+ """
2
+ MCP Tools Discovery Utility.
3
+ Provides a standalone function to discover tools from remote MCP servers.
4
+ Supports both SSE (Server-Sent Events) and Streamable HTTP transports with auto-detection.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from .mcp_oauth import McpAuthorizationRequired
12
+ from .mcp_client import McpClient
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def discover_mcp_tools(
18
+ url: str,
19
+ headers: Optional[Dict[str, str]] = None,
20
+ timeout: int = 60,
21
+ session_id: Optional[str] = None,
22
+ ) -> List[Dict[str, Any]]:
23
+ """
24
+ Discover available tools from a remote MCP server.
25
+
26
+ This function connects to a remote MCP server and retrieves the list of
27
+ available tools using the MCP protocol. Automatically detects and uses
28
+ the appropriate transport (SSE or Streamable HTTP).
29
+
30
+ Args:
31
+ url: MCP server HTTP URL (http:// or https://)
32
+ headers: Optional HTTP headers for authentication
33
+ timeout: Request timeout in seconds (default: 60)
34
+ session_id: Optional session ID for stateful connections
35
+
36
+ Returns:
37
+ List of tool definitions, each containing:
38
+ - name: Tool name
39
+ - description: Tool description
40
+ - inputSchema: JSON schema for tool input parameters
41
+
42
+ Raises:
43
+ McpAuthorizationRequired: If the server requires OAuth authorization (401)
44
+ Exception: For other connection or protocol errors
45
+
46
+ Example:
47
+ >>> tools = discover_mcp_tools(
48
+ ... url="https://mcp.example.com/sse",
49
+ ... headers={"Authorization": "Bearer token123"}
50
+ ... )
51
+ >>> print(f"Found {len(tools)} tools")
52
+ """
53
+ logger.info(f"[MCP Discovery] Starting tool discovery from {url}")
54
+
55
+ try:
56
+ # Run the async discovery in a new event loop
57
+ tools_list = asyncio.run(
58
+ _discover_tools_async(url, headers, timeout, session_id)
59
+ )
60
+ logger.info(f"[MCP Discovery] Successfully discovered {len(tools_list)} tools from {url}")
61
+ return tools_list
62
+
63
+ except McpAuthorizationRequired:
64
+ # Re-raise auth exceptions directly
65
+ logger.info(f"[MCP Discovery] Authorization required for {url}")
66
+ raise
67
+
68
+ except Exception as e:
69
+ logger.error(f"[MCP Discovery] Failed to discover tools from {url}: {e}")
70
+ raise
71
+
72
+
73
+ async def _discover_tools_async(
74
+ url: str,
75
+ headers: Optional[Dict[str, str]],
76
+ timeout: int,
77
+ session_id: Optional[str],
78
+ ) -> List[Dict[str, Any]]:
79
+ """
80
+ Async implementation of tool discovery using unified MCP client.
81
+ """
82
+ all_tools = []
83
+
84
+ # Create unified MCP client (auto-detects transport)
85
+ client = McpClient(
86
+ url=url,
87
+ session_id=session_id,
88
+ headers=headers,
89
+ timeout=timeout
90
+ )
91
+
92
+ async with client:
93
+ # Initialize MCP session
94
+ await client.initialize()
95
+ logger.debug(f"[MCP Discovery] Session initialized (transport={client.detected_transport})")
96
+
97
+ # Get tools list
98
+ tools = await client.list_tools()
99
+ logger.debug(f"[MCP Discovery] Received {len(tools)} tools")
100
+
101
+ # Convert tools to standard format
102
+ for tool in tools:
103
+ tool_def = {
104
+ 'name': tool.get('name'),
105
+ 'description': tool.get('description', ''),
106
+ 'inputSchema': tool.get('inputSchema', {}),
107
+ }
108
+ all_tools.append(tool_def)
109
+
110
+ return all_tools
111
+
112
+
113
+ async def discover_mcp_tools_async(
114
+ url: str,
115
+ headers: Optional[Dict[str, str]] = None,
116
+ timeout: int = 60,
117
+ session_id: Optional[str] = None,
118
+ ) -> List[Dict[str, Any]]:
119
+ """
120
+ Async version of discover_mcp_tools.
121
+
122
+ See discover_mcp_tools for full documentation.
123
+ """
124
+ return await _discover_tools_async(url, headers, timeout, session_id)
@@ -287,7 +287,6 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
287
287
  model_config={
288
288
  "temperature": 0.1,
289
289
  "max_tokens": 1000,
290
- "top_p": 1.0
291
290
  }
292
291
  )
293
292
  except Exception as e:
@@ -868,10 +867,24 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
868
867
  label = f"{'🔒 ' if is_secret else ''}{'*' if is_required else ''}{field_name.replace('_', ' ').title()}"
869
868
 
870
869
  if field_type == 'string':
871
- if is_secret:
870
+ # Check if this is an enum field
871
+ if field_schema.get('enum'):
872
+ # Dropdown for enum values
873
+ options = field_schema['enum']
874
+ default_index = 0
875
+ if default_value and str(default_value) in options:
876
+ default_index = options.index(str(default_value))
877
+ toolkit_config_values[field_name] = st.selectbox(
878
+ label,
879
+ options=options,
880
+ index=default_index,
881
+ help=field_description,
882
+ key=f"config_{field_name}_{selected_toolkit_idx}"
883
+ )
884
+ elif is_secret:
872
885
  toolkit_config_values[field_name] = st.text_input(
873
886
  label,
874
- value=str(default_value) if default_value else '',
887
+ value=str(default_value) if default_value else '',
875
888
  help=field_description,
876
889
  type="password",
877
890
  key=f"config_{field_name}_{selected_toolkit_idx}"
@@ -879,7 +892,7 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
879
892
  else:
880
893
  toolkit_config_values[field_name] = st.text_input(
881
894
  label,
882
- value=str(default_value) if default_value else '',
895
+ value=str(default_value) if default_value else '',
883
896
  help=field_description,
884
897
  key=f"config_{field_name}_{selected_toolkit_idx}"
885
898
  )
@@ -971,6 +984,23 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
971
984
  key=f"config_{field_name}_{selected_toolkit_idx}"
972
985
  )
973
986
  toolkit_config_values[field_name] = [line.strip() for line in array_input.split('\n') if line.strip()]
987
+ elif field_type == 'object':
988
+ # Handle object/dict types (like headers)
989
+ obj_input = st.text_area(
990
+ f"{label} (JSON object)",
991
+ value=json.dumps(default_value) if isinstance(default_value, dict) else str(default_value) if default_value else '',
992
+ help=f"{field_description} - Enter as JSON object, e.g. {{\"Authorization\": \"Bearer token\"}}",
993
+ placeholder='{"key": "value"}',
994
+ key=f"config_{field_name}_{selected_toolkit_idx}"
995
+ )
996
+ try:
997
+ if obj_input.strip():
998
+ toolkit_config_values[field_name] = json.loads(obj_input)
999
+ else:
1000
+ toolkit_config_values[field_name] = None
1001
+ except json.JSONDecodeError as e:
1002
+ st.error(f"Invalid JSON format for {field_name}: {e}")
1003
+ toolkit_config_values[field_name] = None
974
1004
  else:
975
1005
  st.info("This toolkit doesn't require additional configuration.")
976
1006
 
@@ -1225,7 +1255,6 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
1225
1255
  model_config={
1226
1256
  "temperature": 0.1,
1227
1257
  "max_tokens": 1000,
1228
- "top_p": 1.0
1229
1258
  }
1230
1259
  )
1231
1260
  except Exception as e:
@@ -1356,20 +1385,18 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
1356
1385
  help="Maximum number of tokens in the AI response"
1357
1386
  )
1358
1387
 
1359
- top_p = st.slider(
1360
- "Top-p:",
1361
- min_value=0.1,
1362
- max_value=1.0,
1363
- value=1.0,
1364
- step=0.1,
1365
- help="Controls diversity via nucleus sampling"
1388
+ reasoning_effort = st.selectbox(
1389
+ "Reasoning effort:",
1390
+ options=['null', 'low', 'medium', 'high'],
1391
+ index=0,
1392
+ help="Higher effort better reasoning, slower response"
1366
1393
  )
1367
1394
 
1368
1395
  # Create LLM config
1369
1396
  llm_config = {
1370
1397
  'max_tokens': max_tokens,
1371
1398
  'temperature': temperature,
1372
- 'top_p': top_p
1399
+ 'reasoning_effort': reasoning_effort
1373
1400
  }
1374
1401
 
1375
1402
  col1, col2 = st.columns([3, 1])
@@ -12,7 +12,9 @@ logger = logging.getLogger(__name__)
12
12
 
13
13
  def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
14
14
  llm_client: Any,
15
- alita_client: Optional[Any] = None) -> List[Any]:
15
+ alita_client: Optional[Any] = None,
16
+ mcp_tokens: Optional[Dict[str, Any]] = None,
17
+ use_prefix: bool = False) -> List[Any]:
16
18
  """
17
19
  Instantiate a toolkit with LLM client support.
18
20
 
@@ -22,20 +24,25 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
22
24
  Args:
23
25
  toolkit_config: Configuration dictionary for the toolkit
24
26
  llm_client: LLM client instance for tools that need LLM capabilities
25
- client: Optional additional client instance
27
+ alita_client: Optional additional client instance
28
+ mcp_tokens: Optional dictionary of MCP OAuth tokens by server URL
29
+ use_prefix: If True, tools get prefixed with toolkit_name to prevent collisions
30
+ (for agent use). If False, tools use base names only (for testing interface).
31
+ Default False for backward compatibility with testing.
26
32
 
27
33
  Returns:
28
34
  List of instantiated tools from the toolkit
29
35
 
30
36
  Raises:
31
37
  ValueError: If required configuration or client is missing
38
+ McpAuthorizationRequired: If MCP server requires OAuth authorization
32
39
  Exception: If toolkit instantiation fails
33
40
  """
41
+ toolkit_name = toolkit_config.get('toolkit_name', 'unknown')
34
42
  try:
35
43
  from ..toolkits.tools import get_tools
36
44
 
37
- toolkit_name = toolkit_config.get('toolkit_name')
38
- if not toolkit_name:
45
+ if not toolkit_name or toolkit_name == 'unknown':
39
46
  raise ValueError("toolkit_name is required in configuration")
40
47
 
41
48
  if not llm_client:
@@ -46,18 +53,22 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
46
53
  # Log the configuration being used
47
54
  logger.info(f"Instantiating toolkit {toolkit_name} with LLM client")
48
55
  logger.debug(f"Toolkit {toolkit_name} configuration: {toolkit_config}")
49
-
56
+
57
+ # Use toolkit type from config, or fall back to lowercase toolkit name
58
+ toolkit_type = toolkit_config.get('type', toolkit_name.lower())
59
+
50
60
  # Create a tool configuration dict with required fields
61
+ # Note: MCP toolkit always requires toolkit_name, other toolkits respect use_prefix flag
51
62
  tool_config = {
52
63
  'id': toolkit_config.get('id', random.randint(1, 1000000)),
53
- 'type': toolkit_config.get('type', toolkit_name.lower()),
64
+ 'type': toolkit_config.get('type', toolkit_type),
54
65
  'settings': settings,
55
- 'toolkit_name': toolkit_name
66
+ 'toolkit_name': toolkit_name if (use_prefix or toolkit_type == 'mcp') else None
56
67
  }
57
68
 
58
69
  # Get tools using the toolkit configuration with clients
59
- # Parameter order: get_tools(tools_list, alita_client, llm, memory_store)
60
- tools = get_tools([tool_config], alita_client, llm_client)
70
+ # Parameter order: get_tools(tools_list, alita_client, llm, memory_store, debug_mode, mcp_tokens)
71
+ tools = get_tools([tool_config], alita_client, llm_client, mcp_tokens=mcp_tokens)
61
72
 
62
73
  if not tools:
63
74
  logger.warning(f"No tools returned for toolkit {toolkit_name}")
@@ -67,6 +78,14 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
67
78
  return tools
68
79
 
69
80
  except Exception as e:
81
+ # Re-raise McpAuthorizationRequired without logging as error
82
+ from ..utils.mcp_oauth import McpAuthorizationRequired
83
+
84
+ if isinstance(e, McpAuthorizationRequired):
85
+ logger.info(f"Toolkit {toolkit_name} requires MCP OAuth authorization")
86
+ raise
87
+
88
+ # Log and re-raise other errors
70
89
  logger.error(f"Error instantiating toolkit {toolkit_name} with client: {str(e)}")
71
90
  raise
72
91
 
@@ -1,6 +1,8 @@
1
1
  import re
2
2
  from enum import Enum
3
3
 
4
+ # DEPRECATED: Tool names no longer use prefixes
5
+ # Kept for backward compatibility only
4
6
  TOOLKIT_SPLITTER = "___"
5
7
 
6
8
  class IndexerKeywords(Enum):
@@ -11,6 +13,10 @@ class IndexerKeywords(Enum):
11
13
  UPDATED_ON = 'updated_on'
12
14
  CONTENT_IN_BYTES = 'loader_content'
13
15
  CONTENT_FILE_NAME = 'loader_content_type'
16
+ INDEX_META_TYPE = 'index_meta'
17
+ INDEX_META_IN_PROGRESS = 'in_progress'
18
+ INDEX_META_COMPLETED = 'completed'
19
+ INDEX_META_FAILED = 'failed'
14
20
 
15
21
  # This pattern matches characters that are NOT alphanumeric, underscores, or hyphens
16
22
  clean_string_pattern = re.compile(r'[^a-zA-Z0-9_.-]')
@@ -20,3 +26,9 @@ def clean_string(s: str) -> str:
20
26
  # Replace these characters with an empty string
21
27
  cleaned_string = re.sub(clean_string_pattern, '', s)
22
28
  return cleaned_string
29
+
30
+
31
+ def clean_node_str(s: str) -> str:
32
+ """Cleans a node string by removing all non-alphanumeric characters except underscores and spaces."""
33
+ cleaned_string = re.sub(r'[^\w\s]', '', s)
34
+ return cleaned_string
@@ -13,6 +13,30 @@ AVAILABLE_TOOLS = {}
13
13
  AVAILABLE_TOOLKITS = {}
14
14
  FAILED_IMPORTS = {}
15
15
 
16
+
17
+ def _inject_toolkit_id(tool_conf: dict, toolkit_tools) -> None:
18
+ """Inject `toolkit_id` into tools that expose `api_wrapper.toolkit_id`.
19
+
20
+ This reads 'id' from the tool configuration and, if it is an integer,
21
+ assigns it to the 'toolkit_id' attribute of the 'api_wrapper' for each
22
+ tool in 'toolkit_tools' that supports it.
23
+
24
+ Args:
25
+ tool_conf: Raw tool configuration item from 'tools_list'.
26
+ toolkit_tools: List of instantiated tools produced by a toolkit.
27
+ """
28
+ toolkit_id = tool_conf.get('id')
29
+ if isinstance(toolkit_id, int):
30
+ for t in toolkit_tools:
31
+ if hasattr(t, 'api_wrapper') and hasattr(t.api_wrapper, 'toolkit_id'):
32
+ t.api_wrapper.toolkit_id = toolkit_id
33
+ else:
34
+ logger.error(
35
+ f"Toolkit ID is missing or not an integer for tool "
36
+ f"`{tool_conf.get('type', '')}` with name `{tool_conf.get('name', '')}`"
37
+ )
38
+
39
+
16
40
  def _safe_import_tool(tool_name, module_path, get_tools_name=None, toolkit_class_name=None):
17
41
  """Safely import a tool module and register available functions/classes."""
18
42
  try:
@@ -34,6 +58,7 @@ def _safe_import_tool(tool_name, module_path, get_tools_name=None, toolkit_class
34
58
  FAILED_IMPORTS[tool_name] = str(e)
35
59
  logger.debug(f"Failed to import {tool_name}: {e}")
36
60
 
61
+
37
62
  # Safe imports for all tools
38
63
  _safe_import_tool('github', 'github', 'get_tools', 'AlitaGitHubToolkit')
39
64
  _safe_import_tool('openapi', 'openapi', 'get_tools')
@@ -90,62 +115,81 @@ available_count = len(AVAILABLE_TOOLS)
90
115
  total_attempted = len(AVAILABLE_TOOLS) + len(FAILED_IMPORTS)
91
116
  logger.info(f"Tool imports completed: {available_count}/{total_attempted} successful")
92
117
 
118
+ # Import community module to trigger community toolkit registration
119
+ try:
120
+ from alita_sdk import community # noqa: F401
121
+ logger.debug("Community toolkits registered successfully")
122
+ except ImportError as e:
123
+ logger.debug(f"Community module not available: {e}")
124
+
125
+
93
126
  def get_tools(tools_list, alita, llm, store: Optional[BaseStore] = None, *args, **kwargs):
94
127
  tools = []
128
+
95
129
  for tool in tools_list:
96
- # validate tool name syntax - it cannot be started with _
97
- for tool_name in tool.get('settings', {}).get('selected_tools', []):
98
- if isinstance(tool_name, str) and tool_name.startswith('_'):
99
- raise ValueError(f"Tool name '{tool_name}' from toolkit '{tool.get('type', '')}' cannot start with '_'")
100
-
101
- tool['settings']['alita'] = alita
102
- tool['settings']['llm'] = llm
103
- tool['settings']['store'] = store
104
- tool_type = tool['type']
130
+ toolkit_tools = []
131
+ settings = tool.get('settings')
105
132
 
106
- # Handle special cases for ADO tools
107
- if tool_type in ['ado_boards', 'ado_wiki', 'ado_plans']:
108
- tools.extend(AVAILABLE_TOOLS['ado']['get_tools'](tool_type, tool))
133
+ # Skip tools without settings early
134
+ if not settings:
135
+ logger.warning(f"Tool '{tool.get('type', '')}' has no settings, skipping...")
136
+ continue
109
137
 
110
- # Check if tool is available and has get_tools function
111
- elif tool_type in AVAILABLE_TOOLS and 'get_tools' in AVAILABLE_TOOLS[tool_type]:
112
- try:
113
- get_tools_func = AVAILABLE_TOOLS[tool_type]['get_tools']
114
- tools.extend(get_tools_func(tool))
138
+ # Validate tool names once
139
+ selected_tools = settings.get('selected_tools', [])
140
+ invalid_tools = [name for name in selected_tools if isinstance(name, str) and name.startswith('_')]
141
+ if invalid_tools:
142
+ raise ValueError(f"Tool names {invalid_tools} from toolkit '{tool.get('type', '')}' cannot start with '_'")
115
143
 
116
- except Exception as e:
117
- logger.error(f"Error getting tools for {tool_type}: {e}")
118
- raise ToolException(f"Error getting tools for {tool_type}: {e}")
144
+ # Cache tool type and add common settings
145
+ tool_type = tool['type']
146
+ settings['alita'] = alita
147
+ settings['llm'] = llm
148
+ settings['store'] = store
119
149
 
120
- # Handle ADO repos special case (it might be requested as azure_devops_repos)
150
+ # Set pgvector collection schema if present
151
+ if settings.get('pgvector_configuration'):
152
+ # Use tool id if available, otherwise use toolkit_name or type as fallback
153
+ collection_id = tool.get('id') or tool.get('toolkit_name') or tool_type
154
+ settings['pgvector_configuration']['collection_schema'] = str(collection_id)
155
+
156
+ # Handle ADO special cases
157
+ if tool_type in ['ado_boards', 'ado_wiki', 'ado_plans']:
158
+ toolkit_tools.extend(AVAILABLE_TOOLS['ado']['get_tools'](tool_type, tool))
121
159
  elif tool_type in ['ado_repos', 'azure_devops_repos'] and 'ado_repos' in AVAILABLE_TOOLS:
122
160
  try:
123
- get_tools_func = AVAILABLE_TOOLS['ado_repos']['get_tools']
124
- tools.extend(get_tools_func(tool))
161
+ toolkit_tools.extend(AVAILABLE_TOOLS['ado_repos']['get_tools'](tool))
125
162
  except Exception as e:
126
163
  logger.error(f"Error getting ADO repos tools: {e}")
127
-
128
- # Handle custom modules
129
- elif tool.get("settings", {}).get("module"):
164
+ elif tool_type == 'mcp':
165
+ logger.debug(f"Skipping MCP toolkit '{tool.get('toolkit_name')}' - handled by runtime toolkit system")
166
+ elif tool_type == 'planning':
167
+ logger.debug(f"Skipping planning toolkit '{tool.get('toolkit_name')}' - handled by runtime toolkit system")
168
+ elif tool_type in AVAILABLE_TOOLS and 'get_tools' in AVAILABLE_TOOLS[tool_type]:
169
+ try:
170
+ toolkit_tools.extend(AVAILABLE_TOOLS[tool_type]['get_tools'](tool))
171
+ except Exception as e:
172
+ logger.error(f"Error getting tools for {tool_type}: {e}")
173
+ raise ToolException(f"Error getting tools for {tool_type}: {e}")
174
+ elif settings.get("module"):
130
175
  try:
131
- settings = tool.get("settings", {})
132
176
  mod = import_module(settings.pop("module"))
133
177
  tkitclass = getattr(mod, settings.pop("class"))
134
- #
135
- get_toolkit_params = tool["settings"].copy()
178
+ get_toolkit_params = settings.copy()
136
179
  get_toolkit_params["name"] = tool.get("name")
137
- #
138
180
  toolkit = tkitclass.get_toolkit(**get_toolkit_params)
139
- tools.extend(toolkit.get_tools())
181
+ toolkit_tools.extend(toolkit.get_tools())
140
182
  except Exception as e:
141
183
  logger.error(f"Error in getting custom toolkit: {e}")
142
-
143
184
  else:
144
- # Tool not available or not found
145
185
  if tool_type in FAILED_IMPORTS:
146
186
  logger.warning(f"Tool '{tool_type}' is not available: {FAILED_IMPORTS[tool_type]}")
147
187
  else:
148
188
  logger.warning(f"Unknown tool type: {tool_type}")
189
+ #
190
+ # Always inject toolkit_id to each tool
191
+ _inject_toolkit_id(tool, toolkit_tools)
192
+ tools.extend(toolkit_tools)
149
193
 
150
194
  return tools
151
195
 
@@ -10,7 +10,7 @@ from ....configurations.ado import AdoReposConfiguration
10
10
  from ....configurations.pgvector import PgVectorConfiguration
11
11
  from ...base.tool import BaseAction
12
12
  from .repos_wrapper import ReposApiWrapper
13
- from ...utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
13
+ from ...utils import clean_string, get_max_toolkit_length, check_connection_response
14
14
 
15
15
  name = "ado_repos"
16
16
 
@@ -38,12 +38,10 @@ def get_tools(tool):
38
38
 
39
39
  class AzureDevOpsReposToolkit(BaseToolkit):
40
40
  tools: List[BaseTool] = []
41
- toolkit_max_length: int = 0
42
41
 
43
42
  @staticmethod
44
43
  def toolkit_config_schema() -> BaseModel:
45
44
  selected_tools = {x['name']: x['args_schema'].schema() for x in ReposApiWrapper.model_construct().get_available_tools()}
46
- AzureDevOpsReposToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
47
45
  m = create_model(
48
46
  name,
49
47
  ado_repos_configuration=(AdoReposConfiguration, Field(description="Ado Repos configuration", default=None,
@@ -98,16 +96,19 @@ class AzureDevOpsReposToolkit(BaseToolkit):
98
96
  azure_devops_repos_wrapper = ReposApiWrapper(**wrapper_payload)
99
97
  available_tools = azure_devops_repos_wrapper.get_available_tools()
100
98
  tools = []
101
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
102
99
  for tool in available_tools:
103
100
  if selected_tools:
104
101
  if tool["name"] not in selected_tools:
105
102
  continue
103
+ description = tool["description"] + f"\nADO instance: {azure_devops_repos_wrapper.organization_url}/{azure_devops_repos_wrapper.project}"
104
+ if toolkit_name:
105
+ description = f"{description}\nToolkit: {toolkit_name}"
106
+ description = description[:1000]
106
107
  tools.append(
107
108
  BaseAction(
108
109
  api_wrapper=azure_devops_repos_wrapper,
109
- name=prefix + tool["name"],
110
- description=tool["description"] + f"\nADO instance: {azure_devops_repos_wrapper.organization_url}/{azure_devops_repos_wrapper.project}",
110
+ name=tool["name"],
111
+ description=description,
111
112
  args_schema=tool["args_schema"],
112
113
  )
113
114
  )
@@ -24,7 +24,8 @@ from msrest.authentication import BasicAuthentication
24
24
  from pydantic import Field, PrivateAttr, create_model, model_validator, SecretStr
25
25
 
26
26
  from ..utils import extract_old_new_pairs, generate_diff, get_content_from_generator
27
- from ...elitea_base import BaseCodeToolApiWrapper
27
+ from ...code_indexer_toolkit import CodeIndexerToolkit
28
+ from ...utils.available_tools_decorator import extend_with_parent_available_tools
28
29
 
29
30
  logger = logging.getLogger(__name__)
30
31
 
@@ -110,8 +111,7 @@ class ArgsSchema(Enum):
110
111
  Field(
111
112
  description=(
112
113
  "Branch to be used for read file operation."
113
- ),
114
- default=None
114
+ )
115
115
  ),
116
116
  )
117
117
  )
@@ -242,7 +242,7 @@ class ArgsSchema(Enum):
242
242
  )
243
243
 
244
244
 
245
- class ReposApiWrapper(BaseCodeToolApiWrapper):
245
+ class ReposApiWrapper(CodeIndexerToolkit):
246
246
  # TODO use ado_repos_configuration fields
247
247
  organization_url: Optional[str]
248
248
  project: Optional[str]
@@ -293,7 +293,7 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
293
293
  if not branch_exists(active_branch):
294
294
  raise ToolException(f"The active branch '{active_branch}' does not exist.")
295
295
 
296
- return values
296
+ return super().validate_toolkit(values)
297
297
 
298
298
  def _get_commits(self, file_path: str, branch: str, top: int = None) -> List[GitCommitRef]:
299
299
  """
@@ -644,6 +644,9 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
644
644
 
645
645
  return dumps(data)
646
646
 
647
+ def download_file(self, path):
648
+ return b"".join(self._client.get_item_content(self.repository_id, path=path, project=self.project, download=True))
649
+
647
650
  def get_file_content(self, commit_id, path):
648
651
  version_descriptor = GitVersionDescriptor(
649
652
  version=commit_id, version_type="commit"
@@ -1171,9 +1174,10 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
1171
1174
  except Exception as e:
1172
1175
  return ToolException(f"Unable to retrieve commits due to error:\n{str(e)}")
1173
1176
 
1177
+ @extend_with_parent_available_tools
1174
1178
  def get_available_tools(self):
1175
1179
  """Return a list of available tools."""
1176
- tools = [
1180
+ return [
1177
1181
  {
1178
1182
  "ref": self.list_branches_in_repo,
1179
1183
  "name": "list_branches_in_repo",
@@ -1264,8 +1268,4 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
1264
1268
  "description": self.get_commits.__doc__,
1265
1269
  "args_schema": ArgsSchema.GetCommits.value,
1266
1270
  },
1267
- ] # Add vector search tools from base class (includes index_data + search tools)
1268
- vector_search_tools = self._get_vector_search_tools()
1269
- tools.extend(vector_search_tools)
1270
-
1271
- return tools
1271
+ ]
@@ -10,7 +10,7 @@ from ....configurations.ado import AdoConfiguration
10
10
  from ....configurations.pgvector import PgVectorConfiguration
11
11
  from .test_plan_wrapper import TestPlanApiWrapper
12
12
  from ...base.tool import BaseAction
13
- from ...utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
13
+ from ...utils import clean_string, get_max_toolkit_length, check_connection_response
14
14
 
15
15
 
16
16
  name = "azure_devops_plans"
@@ -19,15 +19,12 @@ name_alias = "ado_plans"
19
19
 
20
20
  class AzureDevOpsPlansToolkit(BaseToolkit):
21
21
  tools: List[BaseTool] = []
22
- toolkit_max_length: int = 0
23
22
 
24
23
  @staticmethod
25
24
  def toolkit_config_schema() -> BaseModel:
26
25
  selected_tools = {x['name']: x['args_schema'].schema() for x in TestPlanApiWrapper.model_construct().get_available_tools()}
27
- AzureDevOpsPlansToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
28
26
  m = create_model(
29
27
  name_alias,
30
- name=(str, Field(description="Toolkit name", json_schema_extra={'toolkit_name': True, 'max_toolkit_length': AzureDevOpsPlansToolkit.toolkit_max_length})),
31
28
  ado_configuration=(AdoConfiguration, Field(description="Ado configuration", json_schema_extra={'configuration_types': ['ado']})),
32
29
  limit=(Optional[int], Field(description="ADO plans limit used for limitation of the list with results", default=5)),
33
30
  # indexer settings
@@ -97,16 +94,19 @@ class AzureDevOpsPlansToolkit(BaseToolkit):
97
94
  azure_devops_api_wrapper = TestPlanApiWrapper(**wrapper_payload)
98
95
  available_tools = azure_devops_api_wrapper.get_available_tools()
99
96
  tools = []
100
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
101
97
  for tool in available_tools:
102
98
  if selected_tools:
103
99
  if tool["name"] not in selected_tools:
104
100
  continue
105
101
  print(tool)
102
+ description = tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}"
103
+ if toolkit_name:
104
+ description = f"{description}\nToolkit: {toolkit_name}"
105
+ description = description[:1000]
106
106
  tools.append(BaseAction(
107
107
  api_wrapper=azure_devops_api_wrapper,
108
- name=prefix + tool["name"],
109
- description=tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}",
108
+ name=tool["name"],
109
+ description=description,
110
110
  args_schema=tool["args_schema"]
111
111
  ))
112
112
  return cls(tools=tools)