alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__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 (278) 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 +156 -0
  6. alita_sdk/cli/agent_loader.py +245 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3113 -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 +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/testcases/__init__.py +94 -0
  23. alita_sdk/cli/testcases/data_generation.py +119 -0
  24. alita_sdk/cli/testcases/discovery.py +96 -0
  25. alita_sdk/cli/testcases/executor.py +84 -0
  26. alita_sdk/cli/testcases/logger.py +85 -0
  27. alita_sdk/cli/testcases/parser.py +172 -0
  28. alita_sdk/cli/testcases/prompts.py +91 -0
  29. alita_sdk/cli/testcases/reporting.py +125 -0
  30. alita_sdk/cli/testcases/setup.py +108 -0
  31. alita_sdk/cli/testcases/test_runner.py +282 -0
  32. alita_sdk/cli/testcases/utils.py +39 -0
  33. alita_sdk/cli/testcases/validation.py +90 -0
  34. alita_sdk/cli/testcases/workflow.py +196 -0
  35. alita_sdk/cli/toolkit.py +327 -0
  36. alita_sdk/cli/toolkit_loader.py +85 -0
  37. alita_sdk/cli/tools/__init__.py +43 -0
  38. alita_sdk/cli/tools/approval.py +224 -0
  39. alita_sdk/cli/tools/filesystem.py +1751 -0
  40. alita_sdk/cli/tools/planning.py +389 -0
  41. alita_sdk/cli/tools/terminal.py +414 -0
  42. alita_sdk/community/__init__.py +72 -12
  43. alita_sdk/community/inventory/__init__.py +236 -0
  44. alita_sdk/community/inventory/config.py +257 -0
  45. alita_sdk/community/inventory/enrichment.py +2137 -0
  46. alita_sdk/community/inventory/extractors.py +1469 -0
  47. alita_sdk/community/inventory/ingestion.py +3172 -0
  48. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  49. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  50. alita_sdk/community/inventory/parsers/base.py +295 -0
  51. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  52. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  53. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  54. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  55. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  56. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  57. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  58. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  59. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  60. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  61. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  62. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  63. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  64. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  65. alita_sdk/community/inventory/patterns/loader.py +348 -0
  66. alita_sdk/community/inventory/patterns/registry.py +198 -0
  67. alita_sdk/community/inventory/presets.py +535 -0
  68. alita_sdk/community/inventory/retrieval.py +1403 -0
  69. alita_sdk/community/inventory/toolkit.py +173 -0
  70. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  71. alita_sdk/community/inventory/visualize.py +1370 -0
  72. alita_sdk/configurations/__init__.py +1 -1
  73. alita_sdk/configurations/ado.py +141 -20
  74. alita_sdk/configurations/bitbucket.py +94 -2
  75. alita_sdk/configurations/confluence.py +130 -1
  76. alita_sdk/configurations/figma.py +76 -0
  77. alita_sdk/configurations/gitlab.py +91 -0
  78. alita_sdk/configurations/jira.py +103 -0
  79. alita_sdk/configurations/openapi.py +329 -0
  80. alita_sdk/configurations/qtest.py +72 -1
  81. alita_sdk/configurations/report_portal.py +96 -0
  82. alita_sdk/configurations/sharepoint.py +148 -0
  83. alita_sdk/configurations/testio.py +83 -0
  84. alita_sdk/configurations/testrail.py +88 -0
  85. alita_sdk/configurations/xray.py +93 -0
  86. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  87. alita_sdk/configurations/zephyr_essential.py +75 -0
  88. alita_sdk/runtime/clients/artifact.py +3 -3
  89. alita_sdk/runtime/clients/client.py +388 -46
  90. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  91. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  92. alita_sdk/runtime/clients/sandbox_client.py +8 -21
  93. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  94. alita_sdk/runtime/langchain/assistant.py +157 -39
  95. alita_sdk/runtime/langchain/constants.py +647 -1
  96. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  97. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
  100. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  101. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  102. alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
  103. alita_sdk/runtime/langchain/langraph_agent.py +405 -84
  104. alita_sdk/runtime/langchain/utils.py +106 -7
  105. alita_sdk/runtime/llms/preloaded.py +2 -6
  106. alita_sdk/runtime/models/mcp_models.py +61 -0
  107. alita_sdk/runtime/skills/__init__.py +91 -0
  108. alita_sdk/runtime/skills/callbacks.py +498 -0
  109. alita_sdk/runtime/skills/discovery.py +540 -0
  110. alita_sdk/runtime/skills/executor.py +610 -0
  111. alita_sdk/runtime/skills/input_builder.py +371 -0
  112. alita_sdk/runtime/skills/models.py +330 -0
  113. alita_sdk/runtime/skills/registry.py +355 -0
  114. alita_sdk/runtime/skills/skill_runner.py +330 -0
  115. alita_sdk/runtime/toolkits/__init__.py +31 -0
  116. alita_sdk/runtime/toolkits/application.py +29 -10
  117. alita_sdk/runtime/toolkits/artifact.py +20 -11
  118. alita_sdk/runtime/toolkits/datasource.py +13 -6
  119. alita_sdk/runtime/toolkits/mcp.py +783 -0
  120. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  121. alita_sdk/runtime/toolkits/planning.py +178 -0
  122. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  123. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  124. alita_sdk/runtime/toolkits/tools.py +356 -69
  125. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  126. alita_sdk/runtime/tools/__init__.py +10 -3
  127. alita_sdk/runtime/tools/application.py +27 -6
  128. alita_sdk/runtime/tools/artifact.py +511 -28
  129. alita_sdk/runtime/tools/data_analysis.py +183 -0
  130. alita_sdk/runtime/tools/function.py +67 -35
  131. alita_sdk/runtime/tools/graph.py +10 -4
  132. alita_sdk/runtime/tools/image_generation.py +148 -46
  133. alita_sdk/runtime/tools/llm.py +1003 -128
  134. alita_sdk/runtime/tools/loop.py +3 -1
  135. alita_sdk/runtime/tools/loop_output.py +3 -1
  136. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  137. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  138. alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
  139. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  140. alita_sdk/runtime/tools/planning/models.py +246 -0
  141. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  142. alita_sdk/runtime/tools/router.py +2 -4
  143. alita_sdk/runtime/tools/sandbox.py +65 -48
  144. alita_sdk/runtime/tools/skill_router.py +776 -0
  145. alita_sdk/runtime/tools/tool.py +3 -1
  146. alita_sdk/runtime/tools/vectorstore.py +9 -3
  147. alita_sdk/runtime/tools/vectorstore_base.py +70 -14
  148. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  149. alita_sdk/runtime/utils/constants.py +5 -1
  150. alita_sdk/runtime/utils/mcp_client.py +492 -0
  151. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  152. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  153. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  154. alita_sdk/runtime/utils/serialization.py +155 -0
  155. alita_sdk/runtime/utils/streamlit.py +40 -13
  156. alita_sdk/runtime/utils/toolkit_utils.py +30 -9
  157. alita_sdk/runtime/utils/utils.py +36 -0
  158. alita_sdk/tools/__init__.py +134 -35
  159. alita_sdk/tools/ado/repos/__init__.py +51 -32
  160. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  161. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  162. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  163. alita_sdk/tools/ado/utils.py +1 -18
  164. alita_sdk/tools/ado/wiki/__init__.py +25 -12
  165. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  166. alita_sdk/tools/ado/work_item/__init__.py +26 -13
  167. alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
  168. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  169. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  170. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  171. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  172. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  173. alita_sdk/tools/base/tool.py +5 -1
  174. alita_sdk/tools/base_indexer_toolkit.py +271 -84
  175. alita_sdk/tools/bitbucket/__init__.py +17 -11
  176. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  177. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  178. alita_sdk/tools/browser/__init__.py +5 -4
  179. alita_sdk/tools/carrier/__init__.py +5 -6
  180. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  181. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  182. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  183. alita_sdk/tools/chunkers/__init__.py +3 -1
  184. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  185. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  186. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  187. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  188. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  189. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  190. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  191. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  192. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  193. alita_sdk/tools/code/linter/__init__.py +10 -8
  194. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  195. alita_sdk/tools/code/sonar/__init__.py +11 -8
  196. alita_sdk/tools/code_indexer_toolkit.py +82 -22
  197. alita_sdk/tools/confluence/__init__.py +22 -16
  198. alita_sdk/tools/confluence/api_wrapper.py +107 -30
  199. alita_sdk/tools/confluence/loader.py +14 -2
  200. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  201. alita_sdk/tools/elastic/__init__.py +11 -8
  202. alita_sdk/tools/elitea_base.py +493 -30
  203. alita_sdk/tools/figma/__init__.py +58 -11
  204. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  205. alita_sdk/tools/figma/figma_client.py +73 -0
  206. alita_sdk/tools/figma/toon_tools.py +2748 -0
  207. alita_sdk/tools/github/__init__.py +14 -15
  208. alita_sdk/tools/github/github_client.py +224 -100
  209. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  210. alita_sdk/tools/github/schemas.py +14 -5
  211. alita_sdk/tools/github/tool.py +5 -1
  212. alita_sdk/tools/github/tool_prompts.py +9 -22
  213. alita_sdk/tools/gitlab/__init__.py +16 -11
  214. alita_sdk/tools/gitlab/api_wrapper.py +218 -48
  215. alita_sdk/tools/gitlab_org/__init__.py +10 -9
  216. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  217. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  218. alita_sdk/tools/google/bigquery/tool.py +5 -1
  219. alita_sdk/tools/google_places/__init__.py +11 -8
  220. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  221. alita_sdk/tools/jira/__init__.py +17 -10
  222. alita_sdk/tools/jira/api_wrapper.py +92 -41
  223. alita_sdk/tools/keycloak/__init__.py +11 -8
  224. alita_sdk/tools/localgit/__init__.py +9 -3
  225. alita_sdk/tools/localgit/local_git.py +62 -54
  226. alita_sdk/tools/localgit/tool.py +5 -1
  227. alita_sdk/tools/memory/__init__.py +12 -4
  228. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  229. alita_sdk/tools/ocr/__init__.py +11 -8
  230. alita_sdk/tools/openapi/__init__.py +491 -106
  231. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  232. alita_sdk/tools/openapi/tool.py +20 -0
  233. alita_sdk/tools/pandas/__init__.py +20 -12
  234. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  235. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  236. alita_sdk/tools/postman/__init__.py +10 -9
  237. alita_sdk/tools/pptx/__init__.py +11 -10
  238. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  239. alita_sdk/tools/qtest/__init__.py +31 -11
  240. alita_sdk/tools/qtest/api_wrapper.py +2135 -86
  241. alita_sdk/tools/rally/__init__.py +10 -9
  242. alita_sdk/tools/rally/api_wrapper.py +1 -1
  243. alita_sdk/tools/report_portal/__init__.py +12 -8
  244. alita_sdk/tools/salesforce/__init__.py +10 -8
  245. alita_sdk/tools/servicenow/__init__.py +17 -15
  246. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  247. alita_sdk/tools/sharepoint/__init__.py +10 -7
  248. alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
  249. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  250. alita_sdk/tools/sharepoint/utils.py +8 -2
  251. alita_sdk/tools/slack/__init__.py +10 -7
  252. alita_sdk/tools/slack/api_wrapper.py +2 -2
  253. alita_sdk/tools/sql/__init__.py +12 -9
  254. alita_sdk/tools/testio/__init__.py +10 -7
  255. alita_sdk/tools/testrail/__init__.py +11 -10
  256. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  257. alita_sdk/tools/utils/__init__.py +9 -4
  258. alita_sdk/tools/utils/content_parser.py +103 -18
  259. alita_sdk/tools/utils/text_operations.py +410 -0
  260. alita_sdk/tools/utils/tool_prompts.py +79 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
  262. alita_sdk/tools/xray/__init__.py +13 -9
  263. alita_sdk/tools/yagmail/__init__.py +9 -3
  264. alita_sdk/tools/zephyr/__init__.py +10 -7
  265. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
  266. alita_sdk/tools/zephyr_essential/__init__.py +10 -7
  267. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  268. alita_sdk/tools/zephyr_essential/client.py +2 -2
  269. alita_sdk/tools/zephyr_scale/__init__.py +11 -8
  270. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  271. alita_sdk/tools/zephyr_squad/__init__.py +10 -7
  272. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
  273. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  274. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  275. alita_sdk-0.3.379.dist-info/RECORD +0 -360
  276. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ """
2
+ Serialization utilities for safe JSON encoding of complex objects.
3
+
4
+ Handles Pydantic models, LangChain messages, datetime objects, and other
5
+ non-standard types that may appear in state variables.
6
+ """
7
+ import json
8
+ import logging
9
+ from datetime import datetime, date
10
+ from typing import Any
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def _convert_to_serializable(obj: Any, _seen: set = None) -> Any:
16
+ """
17
+ Recursively convert an object to JSON-serializable primitives.
18
+
19
+ Handles nested dicts and lists that may contain non-serializable objects.
20
+ Uses a seen set to prevent infinite recursion with circular references.
21
+
22
+ Args:
23
+ obj: Any object to convert
24
+ _seen: Internal set to track seen object ids (for circular reference detection)
25
+
26
+ Returns:
27
+ JSON-serializable representation of the object
28
+ """
29
+ # Initialize seen set for circular reference detection
30
+ if _seen is None:
31
+ _seen = set()
32
+
33
+ # Check for circular references (only for mutable objects)
34
+ obj_id = id(obj)
35
+ if isinstance(obj, (dict, list, set)) and obj_id in _seen:
36
+ return f"<circular reference: {type(obj).__name__}>"
37
+
38
+ # Primitives - return as-is
39
+ if obj is None or isinstance(obj, (str, int, float, bool)):
40
+ return obj
41
+
42
+ # Add to seen set for mutable containers
43
+ if isinstance(obj, (dict, list, set)):
44
+ _seen = _seen | {obj_id} # Create new set to avoid mutation issues
45
+
46
+ # Dict - recursively process all values
47
+ if isinstance(obj, dict):
48
+ return {
49
+ _convert_to_serializable(k, _seen): _convert_to_serializable(v, _seen)
50
+ for k, v in obj.items()
51
+ }
52
+
53
+ # List/tuple - recursively process all items
54
+ if isinstance(obj, (list, tuple)):
55
+ return [_convert_to_serializable(item, _seen) for item in obj]
56
+
57
+ # Set - convert to list and process
58
+ if isinstance(obj, set):
59
+ return [_convert_to_serializable(item, _seen) for item in obj]
60
+
61
+ # Bytes - decode to string
62
+ if isinstance(obj, bytes):
63
+ try:
64
+ return obj.decode('utf-8')
65
+ except UnicodeDecodeError:
66
+ return obj.decode('utf-8', errors='replace')
67
+
68
+ # Datetime objects
69
+ if isinstance(obj, datetime):
70
+ return obj.isoformat()
71
+ if isinstance(obj, date):
72
+ return obj.isoformat()
73
+
74
+ # Pydantic BaseModel (v2) - check for model_dump method
75
+ if hasattr(obj, 'model_dump') and callable(getattr(obj, 'model_dump')):
76
+ try:
77
+ return _convert_to_serializable(obj.model_dump(), _seen)
78
+ except Exception as e:
79
+ logger.debug(f"Failed to call model_dump on {type(obj).__name__}: {e}")
80
+
81
+ # Pydantic BaseModel (v1) - check for dict method
82
+ if hasattr(obj, 'dict') and callable(getattr(obj, 'dict')) and hasattr(obj, '__fields__'):
83
+ try:
84
+ return _convert_to_serializable(obj.dict(), _seen)
85
+ except Exception as e:
86
+ logger.debug(f"Failed to call dict on {type(obj).__name__}: {e}")
87
+
88
+ # LangChain BaseMessage - extract key fields
89
+ if hasattr(obj, 'type') and hasattr(obj, 'content'):
90
+ try:
91
+ result = {
92
+ "type": obj.type,
93
+ "content": _convert_to_serializable(obj.content, _seen),
94
+ }
95
+ if hasattr(obj, 'additional_kwargs') and obj.additional_kwargs:
96
+ result["additional_kwargs"] = _convert_to_serializable(obj.additional_kwargs, _seen)
97
+ if hasattr(obj, 'name') and obj.name:
98
+ result["name"] = obj.name
99
+ return result
100
+ except Exception as e:
101
+ logger.debug(f"Failed to extract message fields from {type(obj).__name__}: {e}")
102
+
103
+ # Objects with __dict__ attribute (custom classes)
104
+ if hasattr(obj, '__dict__'):
105
+ try:
106
+ return _convert_to_serializable(obj.__dict__, _seen)
107
+ except Exception as e:
108
+ logger.debug(f"Failed to serialize __dict__ of {type(obj).__name__}: {e}")
109
+
110
+ # UUID objects
111
+ if hasattr(obj, 'hex') and hasattr(obj, 'int'):
112
+ return str(obj)
113
+
114
+ # Enum objects
115
+ if hasattr(obj, 'value') and hasattr(obj, 'name') and hasattr(obj.__class__, '__members__'):
116
+ return obj.value
117
+
118
+ # Last resort - convert to string
119
+ try:
120
+ return str(obj)
121
+ except Exception:
122
+ return f"<non-serializable: {type(obj).__name__}>"
123
+
124
+
125
+ def safe_serialize(obj: Any, **kwargs) -> str:
126
+ """
127
+ Safely serialize any object to a JSON string.
128
+
129
+ Pre-processes the entire object tree to convert non-serializable
130
+ objects before passing to json.dumps. This ensures nested dicts
131
+ and lists with non-standard objects are handled correctly.
132
+
133
+ Args:
134
+ obj: Any object to serialize
135
+ **kwargs: Additional arguments passed to json.dumps
136
+ (e.g., indent, sort_keys)
137
+
138
+ Returns:
139
+ JSON string representation of the object
140
+
141
+ Example:
142
+ >>> from pydantic import BaseModel
143
+ >>> class User(BaseModel):
144
+ ... name: str
145
+ >>> state = {"user": User(name="Alice"), "count": 5}
146
+ >>> safe_serialize(state)
147
+ '{"user": {"name": "Alice"}, "count": 5}'
148
+ """
149
+ # Pre-process the entire object tree
150
+ serializable = _convert_to_serializable(obj)
151
+
152
+ # Set defaults
153
+ kwargs.setdefault('ensure_ascii', False)
154
+
155
+ return json.dumps(serializable, **kwargs)
@@ -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,24 @@ 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
62
+ # Note: 'name' is always set for provider-based toolkits (used by provider_worker.utils.tools)
51
63
  tool_config = {
52
64
  'id': toolkit_config.get('id', random.randint(1, 1000000)),
53
- 'type': toolkit_config.get('type', toolkit_name.lower()),
65
+ 'type': toolkit_config.get('type', toolkit_type),
54
66
  'settings': settings,
55
- 'toolkit_name': toolkit_name
67
+ 'name': toolkit_name, # Always pass name for provider toolkits
68
+ 'toolkit_name': toolkit_name if (use_prefix or toolkit_type == 'mcp') else None
56
69
  }
57
70
 
58
71
  # 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)
72
+ # Parameter order: get_tools(tools_list, alita_client, llm, memory_store, debug_mode, mcp_tokens)
73
+ tools = get_tools([tool_config], alita_client, llm_client, mcp_tokens=mcp_tokens)
61
74
 
62
75
  if not tools:
63
76
  logger.warning(f"No tools returned for toolkit {toolkit_name}")
@@ -67,6 +80,14 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
67
80
  return tools
68
81
 
69
82
  except Exception as e:
83
+ # Re-raise McpAuthorizationRequired without logging as error
84
+ from ..utils.mcp_oauth import McpAuthorizationRequired
85
+
86
+ if isinstance(e, McpAuthorizationRequired):
87
+ logger.info(f"Toolkit {toolkit_name} requires MCP OAuth authorization")
88
+ raise
89
+
90
+ # Log and re-raise other errors
70
91
  logger.error(f"Error instantiating toolkit {toolkit_name} with client: {str(e)}")
71
92
  raise
72
93
 
@@ -1,6 +1,11 @@
1
+ import base64
2
+ import logging
1
3
  import re
2
4
  from enum import Enum
5
+ from typing import Any
3
6
 
7
+ # DEPRECATED: Tool names no longer use prefixes
8
+ # Kept for backward compatibility only
4
9
  TOOLKIT_SPLITTER = "___"
5
10
 
6
11
  class IndexerKeywords(Enum):
@@ -30,3 +35,34 @@ def clean_node_str(s: str) -> str:
30
35
  """Cleans a node string by removing all non-alphanumeric characters except underscores and spaces."""
31
36
  cleaned_string = re.sub(r'[^\w\s]', '', s)
32
37
  return cleaned_string
38
+
39
+
40
+ def resolve_image_from_cache(client: Any, cached_image_id: str) -> bytes:
41
+ """
42
+ Resolve cached_image_id from client's image cache and return decoded binary data.
43
+
44
+ Args:
45
+ client: AlitaClient instance with _generated_images_cache attribute
46
+ cached_image_id: The cached image ID to resolve
47
+
48
+ Returns:
49
+ bytes: Decoded binary image data
50
+
51
+ Raises:
52
+ ValueError: If cached_image_id not found or decoding fails
53
+ """
54
+ cache = getattr(client, '_generated_images_cache', {})
55
+
56
+ if cached_image_id not in cache:
57
+ raise ValueError(f"Image reference '{cached_image_id}' not found. The image may have expired.")
58
+
59
+ cached_data = cache[cached_image_id]
60
+ base64_data = cached_data.get('base64_data', '')
61
+ logging.debug(f"Resolved cached_image_id '{cached_image_id}' from cache (length: {len(base64_data)} chars)")
62
+ # Decode base64 to binary data for image files
63
+ try:
64
+ binary_data = base64.b64decode(base64_data)
65
+ logging.debug(f"Decoded base64 to binary data ({len(binary_data)} bytes)")
66
+ return binary_data
67
+ except Exception as e:
68
+ raise ValueError(f"Failed to decode image data for '{cached_image_id}': {e}")
@@ -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:
@@ -21,6 +45,12 @@ def _safe_import_tool(tool_name, module_path, get_tools_name=None, toolkit_class
21
45
  imported = {}
22
46
  if get_tools_name and hasattr(module, get_tools_name):
23
47
  imported['get_tools'] = getattr(module, get_tools_name)
48
+
49
+ if hasattr(module, 'get_toolkit'):
50
+ imported['get_toolkit'] = getattr(module, 'get_toolkit')
51
+
52
+ if hasattr(module, 'get_toolkit_available_tools'):
53
+ imported['get_toolkit_available_tools'] = getattr(module, 'get_toolkit_available_tools')
24
54
 
25
55
  if toolkit_class_name and hasattr(module, toolkit_class_name):
26
56
  imported['toolkit_class'] = getattr(module, toolkit_class_name)
@@ -34,9 +64,10 @@ def _safe_import_tool(tool_name, module_path, get_tools_name=None, toolkit_class
34
64
  FAILED_IMPORTS[tool_name] = str(e)
35
65
  logger.debug(f"Failed to import {tool_name}: {e}")
36
66
 
67
+
37
68
  # Safe imports for all tools
38
69
  _safe_import_tool('github', 'github', 'get_tools', 'AlitaGitHubToolkit')
39
- _safe_import_tool('openapi', 'openapi', 'get_tools')
70
+ _safe_import_tool('openapi', 'openapi', 'get_tools', 'AlitaOpenAPIToolkit')
40
71
  _safe_import_tool('jira', 'jira', 'get_tools', 'JiraToolkit')
41
72
  _safe_import_tool('confluence', 'confluence', 'get_tools', 'ConfluenceToolkit')
42
73
  _safe_import_tool('service_now', 'servicenow', 'get_tools', 'ServiceNowToolkit')
@@ -71,7 +102,7 @@ _safe_import_tool('k8s', 'cloud.k8s', None, 'KubernetesToolkit')
71
102
  _safe_import_tool('elastic', 'elastic', None, 'ElasticToolkit')
72
103
  _safe_import_tool('keycloak', 'keycloak', None, 'KeycloakToolkit')
73
104
  _safe_import_tool('localgit', 'localgit', None, 'AlitaLocalGitToolkit')
74
- _safe_import_tool('pandas', 'pandas', 'get_tools', 'PandasToolkit')
105
+ # pandas toolkit removed - use Data Analysis internal tool instead
75
106
  _safe_import_tool('azure_search', 'azure_ai.search', 'get_tools', 'AzureSearchToolkit')
76
107
  _safe_import_tool('figma', 'figma', 'get_tools', 'FigmaToolkit')
77
108
  _safe_import_tool('salesforce', 'salesforce', 'get_tools', 'SalesforceToolkit')
@@ -90,64 +121,83 @@ available_count = len(AVAILABLE_TOOLS)
90
121
  total_attempted = len(AVAILABLE_TOOLS) + len(FAILED_IMPORTS)
91
122
  logger.info(f"Tool imports completed: {available_count}/{total_attempted} successful")
92
123
 
124
+ # Import community module to trigger community toolkit registration
125
+ try:
126
+ from alita_sdk import community # noqa: F401
127
+ logger.debug("Community toolkits registered successfully")
128
+ except ImportError as e:
129
+ logger.debug(f"Community module not available: {e}")
130
+
131
+
93
132
  def get_tools(tools_list, alita, llm, store: Optional[BaseStore] = None, *args, **kwargs):
94
133
  tools = []
134
+
95
135
  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
- if not tool.get('settings'):
136
+ toolkit_tools = []
137
+ settings = tool.get('settings')
138
+
139
+ # Skip tools without settings early
140
+ if not settings:
101
141
  logger.warning(f"Tool '{tool.get('type', '')}' has no settings, skipping...")
102
142
  continue
103
- tool['settings']['alita'] = alita
104
- tool['settings']['llm'] = llm
105
- tool['settings']['store'] = store
106
- tool_type = tool['type']
107
143
 
108
- # Handle special cases for ADO tools
109
- if tool_type in ['ado_boards', 'ado_wiki', 'ado_plans']:
110
- tools.extend(AVAILABLE_TOOLS['ado']['get_tools'](tool_type, tool))
144
+ # Validate tool names once
145
+ selected_tools = settings.get('selected_tools', [])
146
+ invalid_tools = [name for name in selected_tools if isinstance(name, str) and name.startswith('_')]
147
+ if invalid_tools:
148
+ raise ValueError(f"Tool names {invalid_tools} from toolkit '{tool.get('type', '')}' cannot start with '_'")
111
149
 
112
- # Check if tool is available and has get_tools function
113
- elif tool_type in AVAILABLE_TOOLS and 'get_tools' in AVAILABLE_TOOLS[tool_type]:
114
- try:
115
- get_tools_func = AVAILABLE_TOOLS[tool_type]['get_tools']
116
- tools.extend(get_tools_func(tool))
150
+ # Cache tool type and add common settings
151
+ tool_type = tool['type']
152
+ settings['alita'] = alita
153
+ settings['llm'] = llm
154
+ settings['store'] = store
117
155
 
118
- except Exception as e:
119
- logger.error(f"Error getting tools for {tool_type}: {e}")
120
- raise ToolException(f"Error getting tools for {tool_type}: {e}")
156
+ # Set pgvector collection schema if present
157
+ if settings.get('pgvector_configuration'):
158
+ # Use tool id if available, otherwise use toolkit_name or type as fallback
159
+ collection_id = tool.get('id') or tool.get('toolkit_name') or tool_type
160
+ settings['pgvector_configuration']['collection_schema'] = str(collection_id)
121
161
 
122
- # Handle ADO repos special case (it might be requested as azure_devops_repos)
162
+ # Handle ADO special cases
163
+ if tool_type in ['ado_boards', 'ado_wiki', 'ado_plans']:
164
+ toolkit_tools.extend(AVAILABLE_TOOLS['ado']['get_tools'](tool_type, tool))
123
165
  elif tool_type in ['ado_repos', 'azure_devops_repos'] and 'ado_repos' in AVAILABLE_TOOLS:
124
166
  try:
125
- get_tools_func = AVAILABLE_TOOLS['ado_repos']['get_tools']
126
- tools.extend(get_tools_func(tool))
167
+ toolkit_tools.extend(AVAILABLE_TOOLS['ado_repos']['get_tools'](tool))
127
168
  except Exception as e:
128
169
  logger.error(f"Error getting ADO repos tools: {e}")
129
-
130
- # Handle custom modules
131
- elif tool.get("settings", {}).get("module"):
170
+ elif tool_type == 'mcp':
171
+ logger.debug(f"Skipping MCP toolkit '{tool.get('toolkit_name')}' - handled by runtime toolkit system")
172
+ elif tool_type == 'planning':
173
+ logger.debug(f"Skipping planning toolkit '{tool.get('toolkit_name')}' - handled by runtime toolkit system")
174
+ elif tool_type in AVAILABLE_TOOLS and 'get_tools' in AVAILABLE_TOOLS[tool_type]:
175
+ try:
176
+ toolkit_tools.extend(AVAILABLE_TOOLS[tool_type]['get_tools'](tool))
177
+ except Exception as e:
178
+ logger.error(f"Error getting tools for {tool_type}: {e}")
179
+ raise ToolException(f"Error getting tools for {tool_type}: {e}")
180
+ elif settings.get("module"):
132
181
  try:
133
- settings = tool.get("settings", {})
134
182
  mod = import_module(settings.pop("module"))
135
183
  tkitclass = getattr(mod, settings.pop("class"))
136
- #
137
- get_toolkit_params = tool["settings"].copy()
184
+ get_toolkit_params = settings.copy()
138
185
  get_toolkit_params["name"] = tool.get("name")
139
- #
140
186
  toolkit = tkitclass.get_toolkit(**get_toolkit_params)
141
- tools.extend(toolkit.get_tools())
187
+ toolkit_tools.extend(toolkit.get_tools())
142
188
  except Exception as e:
189
+ import traceback
143
190
  logger.error(f"Error in getting custom toolkit: {e}")
144
-
191
+ logger.error(f"Traceback:\n{traceback.format_exc()}")
145
192
  else:
146
- # Tool not available or not found
147
193
  if tool_type in FAILED_IMPORTS:
148
194
  logger.warning(f"Tool '{tool_type}' is not available: {FAILED_IMPORTS[tool_type]}")
149
195
  else:
150
196
  logger.warning(f"Unknown tool type: {tool_type}")
197
+ #
198
+ # Always inject toolkit_id to each tool
199
+ _inject_toolkit_id(tool, toolkit_tools)
200
+ tools.extend(toolkit_tools)
151
201
 
152
202
  return tools
153
203
 
@@ -167,6 +217,18 @@ def get_toolkits():
167
217
  logger.info(f"Successfully loaded {len(toolkit_configs)} toolkit configurations")
168
218
  return toolkit_configs
169
219
 
220
+ def instantiate_toolkit(tool_config):
221
+ """Instantiate a toolkit from its configuration."""
222
+ tool_type = tool_config.get('type')
223
+
224
+ if tool_type in AVAILABLE_TOOLS:
225
+ tool_module = AVAILABLE_TOOLS[tool_type]
226
+
227
+ if 'get_toolkit' in tool_module:
228
+ return tool_module['get_toolkit'](tool_config)
229
+
230
+ raise ValueError(f"Toolkit type '{tool_type}' does not support direct instantiation or is not available.")
231
+
170
232
  def get_available_tools():
171
233
  """Return list of available tool types."""
172
234
  return list(AVAILABLE_TOOLS.keys())
@@ -183,6 +245,42 @@ def get_available_toolkit_models():
183
245
  """Return dict with available toolkit classes."""
184
246
  return deepcopy(AVAILABLE_TOOLS)
185
247
 
248
+
249
+ def get_toolkit_available_tools(toolkit_type: str, settings: dict) -> dict:
250
+ """Return dynamic available tools + per-tool JSON schemas for a toolkit instance.
251
+
252
+ This is the single SDK entrypoint used by backend services (e.g. indexer_worker)
253
+ when the UI needs spec/instance-dependent tool enumeration. Toolkits that don't
254
+ support dynamic enumeration should return an empty payload.
255
+
256
+ Args:
257
+ toolkit_type: toolkit type string (e.g. 'openapi')
258
+ settings: persisted toolkit settings
259
+
260
+ Returns:
261
+ {
262
+ "tools": [{"name": str, "description": str}],
263
+ "args_schemas": {"tool_name": <json schema dict>}
264
+ }
265
+ """
266
+ toolkit_type = (toolkit_type or '').strip().lower()
267
+ if not isinstance(settings, dict):
268
+ settings = {}
269
+
270
+ tool_module = AVAILABLE_TOOLS.get(toolkit_type) or {}
271
+ enumerator = tool_module.get('get_toolkit_available_tools')
272
+ if not callable(enumerator):
273
+ return {"tools": [], "args_schemas": {}}
274
+
275
+ try:
276
+ result = enumerator(settings)
277
+ if not isinstance(result, dict):
278
+ return {"tools": [], "args_schemas": {}, "error": "Invalid response from toolkit enumerator"}
279
+ return result
280
+ except Exception as e: # pylint: disable=W0718
281
+ logger.exception("Failed to compute available tools for toolkit_type=%s", toolkit_type)
282
+ return {"tools": [], "args_schemas": {}, "error": str(e)}
283
+
186
284
  def diagnose_imports():
187
285
  """Print diagnostic information about tool imports."""
188
286
  available_count = len(AVAILABLE_TOOLS)
@@ -219,6 +317,7 @@ def diagnose_imports():
219
317
  __all__ = [
220
318
  'get_tools',
221
319
  'get_toolkits',
320
+ 'get_toolkit_available_tools',
222
321
  'get_available_tools',
223
322
  'get_failed_imports',
224
323
  'get_available_toolkits',