alita-sdk 0.3.257__py3-none-any.whl → 0.3.562__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 +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 +1073 -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 +72 -12
  30. alita_sdk/community/inventory/__init__.py +236 -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/toolkit_utils.py +176 -0
  58. alita_sdk/community/inventory/visualize.py +1370 -0
  59. alita_sdk/configurations/__init__.py +11 -0
  60. alita_sdk/configurations/ado.py +148 -2
  61. alita_sdk/configurations/azure_search.py +1 -1
  62. alita_sdk/configurations/bigquery.py +1 -1
  63. alita_sdk/configurations/bitbucket.py +94 -2
  64. alita_sdk/configurations/browser.py +18 -0
  65. alita_sdk/configurations/carrier.py +19 -0
  66. alita_sdk/configurations/confluence.py +130 -1
  67. alita_sdk/configurations/delta_lake.py +1 -1
  68. alita_sdk/configurations/figma.py +76 -5
  69. alita_sdk/configurations/github.py +65 -1
  70. alita_sdk/configurations/gitlab.py +81 -0
  71. alita_sdk/configurations/google_places.py +17 -0
  72. alita_sdk/configurations/jira.py +103 -0
  73. alita_sdk/configurations/openapi.py +111 -0
  74. alita_sdk/configurations/postman.py +1 -1
  75. alita_sdk/configurations/qtest.py +72 -3
  76. alita_sdk/configurations/report_portal.py +115 -0
  77. alita_sdk/configurations/salesforce.py +19 -0
  78. alita_sdk/configurations/service_now.py +1 -12
  79. alita_sdk/configurations/sharepoint.py +167 -0
  80. alita_sdk/configurations/sonar.py +18 -0
  81. alita_sdk/configurations/sql.py +20 -0
  82. alita_sdk/configurations/testio.py +101 -0
  83. alita_sdk/configurations/testrail.py +88 -0
  84. alita_sdk/configurations/xray.py +94 -1
  85. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  86. alita_sdk/configurations/zephyr_essential.py +95 -0
  87. alita_sdk/runtime/clients/artifact.py +21 -4
  88. alita_sdk/runtime/clients/client.py +458 -67
  89. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  90. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  91. alita_sdk/runtime/clients/sandbox_client.py +352 -0
  92. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  93. alita_sdk/runtime/langchain/assistant.py +183 -43
  94. alita_sdk/runtime/langchain/constants.py +647 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  96. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  97. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
  100. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  101. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  102. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  103. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  104. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  105. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  106. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  107. alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
  108. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  109. alita_sdk/runtime/langchain/langraph_agent.py +407 -92
  110. alita_sdk/runtime/langchain/utils.py +102 -8
  111. alita_sdk/runtime/llms/preloaded.py +2 -6
  112. alita_sdk/runtime/models/mcp_models.py +61 -0
  113. alita_sdk/runtime/skills/__init__.py +91 -0
  114. alita_sdk/runtime/skills/callbacks.py +498 -0
  115. alita_sdk/runtime/skills/discovery.py +540 -0
  116. alita_sdk/runtime/skills/executor.py +610 -0
  117. alita_sdk/runtime/skills/input_builder.py +371 -0
  118. alita_sdk/runtime/skills/models.py +330 -0
  119. alita_sdk/runtime/skills/registry.py +355 -0
  120. alita_sdk/runtime/skills/skill_runner.py +330 -0
  121. alita_sdk/runtime/toolkits/__init__.py +28 -0
  122. alita_sdk/runtime/toolkits/application.py +14 -4
  123. alita_sdk/runtime/toolkits/artifact.py +24 -9
  124. alita_sdk/runtime/toolkits/datasource.py +13 -6
  125. alita_sdk/runtime/toolkits/mcp.py +780 -0
  126. alita_sdk/runtime/toolkits/planning.py +178 -0
  127. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  128. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  129. alita_sdk/runtime/toolkits/tools.py +314 -70
  130. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  131. alita_sdk/runtime/tools/__init__.py +24 -0
  132. alita_sdk/runtime/tools/application.py +16 -4
  133. alita_sdk/runtime/tools/artifact.py +367 -33
  134. alita_sdk/runtime/tools/data_analysis.py +183 -0
  135. alita_sdk/runtime/tools/function.py +100 -4
  136. alita_sdk/runtime/tools/graph.py +81 -0
  137. alita_sdk/runtime/tools/image_generation.py +218 -0
  138. alita_sdk/runtime/tools/llm.py +1013 -177
  139. alita_sdk/runtime/tools/loop.py +3 -1
  140. alita_sdk/runtime/tools/loop_output.py +3 -1
  141. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  142. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  143. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  144. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  145. alita_sdk/runtime/tools/planning/models.py +246 -0
  146. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  147. alita_sdk/runtime/tools/router.py +2 -1
  148. alita_sdk/runtime/tools/sandbox.py +375 -0
  149. alita_sdk/runtime/tools/skill_router.py +776 -0
  150. alita_sdk/runtime/tools/tool.py +3 -1
  151. alita_sdk/runtime/tools/vectorstore.py +69 -65
  152. alita_sdk/runtime/tools/vectorstore_base.py +163 -90
  153. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  154. alita_sdk/runtime/utils/mcp_client.py +492 -0
  155. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  156. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  157. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  158. alita_sdk/runtime/utils/streamlit.py +41 -14
  159. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  160. alita_sdk/runtime/utils/utils.py +48 -0
  161. alita_sdk/tools/__init__.py +135 -37
  162. alita_sdk/tools/ado/__init__.py +2 -2
  163. alita_sdk/tools/ado/repos/__init__.py +15 -19
  164. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
  165. alita_sdk/tools/ado/test_plan/__init__.py +26 -8
  166. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
  167. alita_sdk/tools/ado/wiki/__init__.py +27 -12
  168. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
  169. alita_sdk/tools/ado/work_item/__init__.py +27 -12
  170. alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
  171. alita_sdk/tools/advanced_jira_mining/__init__.py +12 -8
  172. alita_sdk/tools/aws/delta_lake/__init__.py +14 -11
  173. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  174. alita_sdk/tools/azure_ai/search/__init__.py +13 -8
  175. alita_sdk/tools/base/tool.py +5 -1
  176. alita_sdk/tools/base_indexer_toolkit.py +454 -110
  177. alita_sdk/tools/bitbucket/__init__.py +27 -19
  178. alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
  179. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  180. alita_sdk/tools/browser/__init__.py +41 -16
  181. alita_sdk/tools/browser/crawler.py +3 -1
  182. alita_sdk/tools/browser/utils.py +15 -6
  183. alita_sdk/tools/carrier/__init__.py +18 -17
  184. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  185. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  186. alita_sdk/tools/chunkers/__init__.py +3 -1
  187. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  188. alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
  189. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  190. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  191. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  192. alita_sdk/tools/cloud/aws/__init__.py +11 -7
  193. alita_sdk/tools/cloud/azure/__init__.py +11 -7
  194. alita_sdk/tools/cloud/gcp/__init__.py +11 -7
  195. alita_sdk/tools/cloud/k8s/__init__.py +11 -7
  196. alita_sdk/tools/code/linter/__init__.py +9 -8
  197. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  198. alita_sdk/tools/code/sonar/__init__.py +20 -13
  199. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  200. alita_sdk/tools/confluence/__init__.py +21 -14
  201. alita_sdk/tools/confluence/api_wrapper.py +197 -58
  202. alita_sdk/tools/confluence/loader.py +14 -2
  203. alita_sdk/tools/custom_open_api/__init__.py +11 -5
  204. alita_sdk/tools/elastic/__init__.py +10 -8
  205. alita_sdk/tools/elitea_base.py +546 -64
  206. alita_sdk/tools/figma/__init__.py +11 -8
  207. alita_sdk/tools/figma/api_wrapper.py +352 -153
  208. alita_sdk/tools/github/__init__.py +17 -17
  209. alita_sdk/tools/github/api_wrapper.py +9 -26
  210. alita_sdk/tools/github/github_client.py +81 -12
  211. alita_sdk/tools/github/schemas.py +2 -1
  212. alita_sdk/tools/github/tool.py +5 -1
  213. alita_sdk/tools/gitlab/__init__.py +18 -13
  214. alita_sdk/tools/gitlab/api_wrapper.py +224 -80
  215. alita_sdk/tools/gitlab_org/__init__.py +13 -10
  216. alita_sdk/tools/google/bigquery/__init__.py +13 -13
  217. alita_sdk/tools/google/bigquery/tool.py +5 -1
  218. alita_sdk/tools/google_places/__init__.py +20 -11
  219. alita_sdk/tools/jira/__init__.py +21 -11
  220. alita_sdk/tools/jira/api_wrapper.py +315 -168
  221. alita_sdk/tools/keycloak/__init__.py +10 -8
  222. alita_sdk/tools/localgit/__init__.py +8 -3
  223. alita_sdk/tools/localgit/local_git.py +62 -54
  224. alita_sdk/tools/localgit/tool.py +5 -1
  225. alita_sdk/tools/memory/__init__.py +38 -14
  226. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  227. alita_sdk/tools/ocr/__init__.py +10 -8
  228. alita_sdk/tools/openapi/__init__.py +281 -108
  229. alita_sdk/tools/openapi/api_wrapper.py +883 -0
  230. alita_sdk/tools/openapi/tool.py +20 -0
  231. alita_sdk/tools/pandas/__init__.py +18 -11
  232. alita_sdk/tools/pandas/api_wrapper.py +40 -45
  233. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  234. alita_sdk/tools/postman/__init__.py +10 -11
  235. alita_sdk/tools/postman/api_wrapper.py +19 -8
  236. alita_sdk/tools/postman/postman_analysis.py +8 -1
  237. alita_sdk/tools/pptx/__init__.py +10 -10
  238. alita_sdk/tools/qtest/__init__.py +21 -14
  239. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  240. alita_sdk/tools/rally/__init__.py +12 -10
  241. alita_sdk/tools/report_portal/__init__.py +22 -16
  242. alita_sdk/tools/salesforce/__init__.py +21 -16
  243. alita_sdk/tools/servicenow/__init__.py +20 -16
  244. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  245. alita_sdk/tools/sharepoint/__init__.py +16 -14
  246. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  247. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  248. alita_sdk/tools/sharepoint/utils.py +8 -2
  249. alita_sdk/tools/slack/__init__.py +11 -7
  250. alita_sdk/tools/sql/__init__.py +21 -19
  251. alita_sdk/tools/sql/api_wrapper.py +71 -23
  252. alita_sdk/tools/testio/__init__.py +20 -13
  253. alita_sdk/tools/testrail/__init__.py +12 -11
  254. alita_sdk/tools/testrail/api_wrapper.py +214 -46
  255. alita_sdk/tools/utils/__init__.py +28 -4
  256. alita_sdk/tools/utils/content_parser.py +182 -62
  257. alita_sdk/tools/utils/text_operations.py +254 -0
  258. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  259. alita_sdk/tools/xray/__init__.py +17 -14
  260. alita_sdk/tools/xray/api_wrapper.py +58 -113
  261. alita_sdk/tools/yagmail/__init__.py +8 -3
  262. alita_sdk/tools/zephyr/__init__.py +11 -7
  263. alita_sdk/tools/zephyr_enterprise/__init__.py +15 -9
  264. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
  265. alita_sdk/tools/zephyr_essential/__init__.py +15 -10
  266. alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
  267. alita_sdk/tools/zephyr_essential/client.py +6 -4
  268. alita_sdk/tools/zephyr_scale/__init__.py +12 -8
  269. alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
  270. alita_sdk/tools/zephyr_squad/__init__.py +11 -7
  271. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/METADATA +184 -37
  272. alita_sdk-0.3.562.dist-info/RECORD +450 -0
  273. alita_sdk-0.3.562.dist-info/entry_points.txt +2 -0
  274. alita_sdk/tools/bitbucket/tools.py +0 -304
  275. alita_sdk-0.3.257.dist-info/RECORD +0 -343
  276. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,776 @@
1
+ """
2
+ Skill Router Wrapper for LangChain integration.
3
+
4
+ This module provides a wrapper that exposes the skills registry system
5
+ through multiple focused tools for listing, describing, and executing skills.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from typing import Any, Dict, List, Literal, Optional
11
+
12
+ from pydantic import BaseModel, Field, ConfigDict
13
+
14
+ from alita_sdk.tools.elitea_base import BaseToolApiWrapper
15
+ from ..skills import (
16
+ SkillsRegistry, get_default_registry, SkillType,
17
+ SkillExecutionResult, SkillStatus
18
+ )
19
+ from ..skills.executor import SkillExecutor
20
+ from ..skills.callbacks import CallbackManager, create_default_callbacks
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ # Input Schemas for the three separate tools
26
+ class ListSkillsInput(BaseModel):
27
+ """Input schema for listing skills."""
28
+
29
+ skill_type: Optional[Literal["graph", "agent"]] = Field(
30
+ default=None,
31
+ description="Filter skills by type when listing"
32
+ )
33
+
34
+ capability: Optional[str] = Field(
35
+ default=None,
36
+ description="Filter skills by capability when listing"
37
+ )
38
+
39
+ tag: Optional[str] = Field(
40
+ default=None,
41
+ description="Filter skills by tag when listing"
42
+ )
43
+
44
+
45
+ class DescribeSkillInput(BaseModel):
46
+ """Input schema for describing a skill."""
47
+
48
+ skill_name: str = Field(
49
+ description="Name of the skill to describe (required)"
50
+ )
51
+
52
+
53
+ class ExecuteSkillInput(BaseModel):
54
+ """Input schema for executing a skill."""
55
+
56
+ skill_name: Optional[str] = Field(
57
+ default=None,
58
+ description="Name of the skill to execute (optional - if not provided, LLM will select best skill)"
59
+ )
60
+
61
+ task: str = Field(
62
+ description="Task or question for the skill (required)"
63
+ )
64
+
65
+ # Agent-specific inputs
66
+ context: Optional[Dict[str, Any]] = Field(
67
+ default=None,
68
+ description="Variables/context for agent skills (key-value pairs)"
69
+ )
70
+
71
+ chat_history: Optional[List[Dict[str, str]]] = Field(
72
+ default=None,
73
+ description="Chat conversation history for agent skills (list of {role: 'user'|'assistant', content: 'message'})"
74
+ )
75
+
76
+ # Graph-specific inputs
77
+ state_variables: Optional[Dict[str, Any]] = Field(
78
+ default=None,
79
+ description="State variables for graph skills (key-value pairs)"
80
+ )
81
+
82
+ # Execution options
83
+ execution_mode: Optional[Literal["subprocess", "remote"]] = Field(
84
+ default=None,
85
+ description="Override execution mode (subprocess or remote)"
86
+ )
87
+
88
+ enable_callbacks: bool = Field(
89
+ default=False,
90
+ description="Enable real-time callback events during execution"
91
+ )
92
+
93
+
94
+ class SkillRouterWrapper(BaseToolApiWrapper):
95
+ """
96
+ Wrapper for skill registry operations.
97
+
98
+ This wrapper provides methods for listing, describing, and executing skills
99
+ with intelligent routing capabilities and proper input handling.
100
+ """
101
+
102
+ model_config = ConfigDict(arbitrary_types_allowed=True)
103
+
104
+ # Declare Pydantic fields for the wrapper components
105
+ registry: Optional[SkillsRegistry] = Field(default=None)
106
+ executor: Optional[SkillExecutor] = Field(default=None)
107
+ enable_callbacks: bool = Field(default=True)
108
+
109
+ # Router-level configuration
110
+ default_timeout: Optional[int] = Field(default=None)
111
+ default_execution_mode: Optional[str] = Field(default=None)
112
+ llm: Optional[Any] = Field(default=None, description="LLM for intelligent skill selection")
113
+ custom_prompt: Optional[str] = Field(default=None, description="Custom prompt for skill routing")
114
+
115
+ # Private attribute for callback manager (not a Pydantic field)
116
+ _callback_manager: Optional[CallbackManager] = None
117
+
118
+ def __init__(
119
+ self,
120
+ registry: Optional[SkillsRegistry] = None,
121
+ alita_client=None,
122
+ llm=None,
123
+ enable_callbacks: bool = True,
124
+ default_timeout: Optional[int] = None,
125
+ default_execution_mode: Optional[str] = None,
126
+ custom_prompt: Optional[str] = None,
127
+ **kwargs
128
+ ):
129
+ """
130
+ Initialize the Skill Router wrapper.
131
+
132
+ Args:
133
+ registry: Skills registry instance. If None, uses default registry.
134
+ alita_client: AlitaClient for LLM access and remote execution.
135
+ llm: Language model for intelligent skill selection.
136
+ enable_callbacks: Whether to enable callback system by default.
137
+ default_timeout: Default timeout for skill execution.
138
+ default_execution_mode: Default execution mode for skills.
139
+ custom_prompt: Custom prompt for skill routing.
140
+ **kwargs: Additional arguments.
141
+ """
142
+ # Initialize components
143
+ registry_instance = registry or get_default_registry()
144
+ executor_instance = SkillExecutor(alita_client)
145
+
146
+ # Initialize the Pydantic model with declared fields only
147
+ super().__init__(
148
+ registry=registry_instance,
149
+ executor=executor_instance,
150
+ enable_callbacks=enable_callbacks,
151
+ default_timeout=default_timeout,
152
+ default_execution_mode=default_execution_mode,
153
+ llm=llm,
154
+ custom_prompt=custom_prompt,
155
+ **kwargs
156
+ )
157
+
158
+ # Initialize callback manager as private attribute
159
+ self._callback_manager = CallbackManager()
160
+ if enable_callbacks:
161
+ # Add default callbacks
162
+ for callback in create_default_callbacks():
163
+ self._callback_manager.add_callback(callback)
164
+
165
+ logger.info("SkillRouterWrapper initialized")
166
+
167
+ def get_available_tools(self):
168
+ """
169
+ Get the list of available tools provided by this wrapper.
170
+
171
+ Returns:
172
+ List of tool definitions with name, description, args_schema, and ref.
173
+ """
174
+ return [
175
+ {
176
+ "name": "list_skills",
177
+ "description": """List all available skills in the registry with optional filtering.
178
+
179
+ Use this tool to discover what skills are available for execution. You can filter by:
180
+ - skill_type: Filter by 'agent' or 'graph' skills
181
+ - capability: Filter by specific capability (e.g., 'jira', 'analysis')
182
+ - tag: Filter by tag
183
+
184
+ Skills come in two types:
185
+ - Agent skills: Conversational, use variables and chat history
186
+ - Graph skills: State-based workflows, use state variables
187
+
188
+ Examples:
189
+ - List all skills: {}
190
+ - List agent skills: {"skill_type": "agent"}
191
+ - List skills with Jira capability: {"capability": "jira"}
192
+ """,
193
+ "args_schema": ListSkillsInput,
194
+ "ref": self._handle_list
195
+ },
196
+ {
197
+ "name": "describe_skill",
198
+ "description": """Get detailed information about a specific skill.
199
+
200
+ Use this tool to understand a skill's inputs, capabilities, and how to execute it.
201
+ Provides complete documentation including:
202
+ - Input schema and required/optional parameters
203
+ - Capabilities and tags
204
+ - Execution configuration
205
+ - Usage examples
206
+
207
+ Example: {"skill_name": "jira_triage"}
208
+ """,
209
+ "args_schema": DescribeSkillInput,
210
+ "ref": self._handle_describe
211
+ },
212
+ {
213
+ "name": "execute_skill",
214
+ "description": """Execute a specialized skill to perform a complex task.
215
+
216
+ This tool provides access to specialized skills with intelligent routing capabilities.
217
+ When skill_name is not specified, the LLM will automatically select the best skill
218
+ based on the task description.
219
+
220
+ Skills come in two types:
221
+ - Agent skills: Use 'context' and 'chat_history' parameters
222
+ - Graph skills: Use 'state_variables' parameter
223
+
224
+ Execution modes:
225
+ - subprocess: Run skills locally in isolated processes (filesystem skills)
226
+ - remote: Run skills via platform APIs (platform-hosted skills)
227
+
228
+ Examples:
229
+ - Auto-select skill: {"task": "Analyze issue PROJ-123"}
230
+ - Explicit skill: {"skill_name": "jira_triage", "task": "Analyze issue", "context": {"issue_key": "PROJ-123"}}
231
+ - Override mode: {"skill_name": "my_skill", "task": "Process data", "execution_mode": "remote"}
232
+ """,
233
+ "args_schema": ExecuteSkillInput,
234
+ "ref": self._handle_execute
235
+ }
236
+ ]
237
+
238
+ def _handle_list(
239
+ self,
240
+ skill_type: Optional[str] = None,
241
+ capability: Optional[str] = None,
242
+ tag: Optional[str] = None
243
+ ) -> str:
244
+ """Handle list skills operation."""
245
+ try:
246
+ # Get all skills
247
+ skills = self.registry.list()
248
+
249
+ # Apply filters
250
+ if skill_type:
251
+ skills = [s for s in skills if (s.skill_type.value if hasattr(s.skill_type, 'value') else str(s.skill_type)) == skill_type]
252
+ if capability:
253
+ skills = [s for s in skills if capability in s.capabilities]
254
+ if tag:
255
+ skills = [s for s in skills if tag in s.tags]
256
+
257
+ if not skills:
258
+ return "No skills found matching the specified criteria."
259
+
260
+ # Format results
261
+ result = f"Found {len(skills)} skills:\n\n"
262
+
263
+ for skill in skills:
264
+ skill_type_str = skill.skill_type.value if hasattr(skill.skill_type, 'value') else str(skill.skill_type)
265
+ result += f"**{skill.name}** ({skill_type_str})\n"
266
+ result += f" Description: {skill.description}\n"
267
+
268
+ if skill.capabilities:
269
+ result += f" Capabilities: {', '.join(skill.capabilities)}\n"
270
+
271
+ if skill.tags:
272
+ result += f" Tags: {', '.join(skill.tags)}\n"
273
+
274
+ result += f" Version: {skill.version}\n\n"
275
+
276
+ # Add summary stats
277
+ stats = self.registry.get_registry_stats()
278
+ result += f"\n**Registry Stats:**\n"
279
+ result += f"- Total skills: {stats['total_skills']}\n"
280
+ result += f"- Graph skills: {stats['graph_skills']}\n"
281
+ result += f"- Agent skills: {stats['agent_skills']}\n"
282
+ result += f"- Unique capabilities: {stats['unique_capabilities']}\n"
283
+
284
+ return result
285
+
286
+ except Exception as e:
287
+ return f"Error listing skills: {str(e)}"
288
+
289
+ def _handle_describe(self, skill_name: str) -> str:
290
+ """Handle describe skill operation."""
291
+ try:
292
+ skill = self.registry.get(skill_name)
293
+ if not skill:
294
+ available_skills = [s.name for s in self.registry.list()]
295
+ return (f"Skill '{skill_name}' not found. "
296
+ f"Available skills: {', '.join(available_skills)}")
297
+
298
+ # Build detailed description
299
+ skill_type_str = skill.skill_type.value if hasattr(skill.skill_type, 'value') else str(skill.skill_type)
300
+ result = f"# {skill.name} ({skill_type_str} skill)\n\n"
301
+ result += f"**Description:** {skill.description}\n\n"
302
+
303
+ # Basic info
304
+ result += f"**Version:** {skill.version}\n"
305
+ execution_mode_str = skill.execution.mode.value if hasattr(skill.execution.mode, 'value') else str(skill.execution.mode)
306
+ result += f"**Execution Mode:** {execution_mode_str}\n"
307
+ result += f"**Timeout:** {skill.execution.timeout}s\n\n"
308
+
309
+ # Capabilities and tags
310
+ if skill.capabilities:
311
+ result += f"**Capabilities:** {', '.join(skill.capabilities)}\n"
312
+ if skill.tags:
313
+ result += f"**Tags:** {', '.join(skill.tags)}\n\n"
314
+
315
+ # Input schema
316
+ result += "**Input Schema:**\n"
317
+ if skill.skill_type == SkillType.AGENT:
318
+ result += "- **task** (required): Task or question for the skill\n"
319
+ result += "- **context** (optional): Variables as key-value pairs\n"
320
+ result += "- **chat_history** (optional): Conversation history\n"
321
+
322
+ if skill.inputs.variables:
323
+ result += "\n**Available Variables:**\n"
324
+ for var_name, var_def in skill.inputs.variables.items():
325
+ var_type = var_def.get('type', 'any')
326
+ var_desc = var_def.get('description', '')
327
+ required = ' (required)' if var_def.get('required') else ''
328
+ result += f"- **{var_name}** ({var_type}){required}: {var_desc}\n"
329
+
330
+ else: # GRAPH
331
+ result += "- **task** (required): Task description (becomes 'input' state variable)\n"
332
+ result += "- **state_variables** (optional): State variables as key-value pairs\n"
333
+
334
+ if skill.inputs.state_variables:
335
+ result += "\n**Available State Variables:**\n"
336
+ for var_name, var_def in skill.inputs.state_variables.items():
337
+ var_type = var_def.get('type', 'any')
338
+ var_desc = var_def.get('description', '')
339
+ required = ' (required)' if var_def.get('required') else ''
340
+ result += f"- **{var_name}** ({var_type}){required}: {var_desc}\n"
341
+
342
+ # LLM settings
343
+ if skill.model or skill.temperature or skill.max_tokens:
344
+ result += "\n**LLM Configuration:**\n"
345
+ if skill.model:
346
+ result += f"- Model: {skill.model}\n"
347
+ if skill.temperature:
348
+ result += f"- Temperature: {skill.temperature}\n"
349
+ if skill.max_tokens:
350
+ result += f"- Max tokens: {skill.max_tokens}\n"
351
+
352
+ # Usage example
353
+ result += "\n**Usage Example:**\n"
354
+ if skill.skill_type == SkillType.AGENT:
355
+ example = {
356
+ "skill_name": skill.name,
357
+ "task": f"Your task description here",
358
+ "context": {"key": "value"}
359
+ }
360
+ else:
361
+ example = {
362
+ "skill_name": skill.name,
363
+ "task": f"Your task description here",
364
+ "state_variables": {"key": "value"}
365
+ }
366
+
367
+ result += f"```json\n{json.dumps(example, indent=2)}\n```"
368
+
369
+ return result
370
+
371
+ except Exception as e:
372
+ return f"Error describing skill: {str(e)}"
373
+
374
+ def _handle_execute(
375
+ self,
376
+ task: str,
377
+ skill_name: Optional[str] = None,
378
+ context: Optional[Dict[str, Any]] = None,
379
+ chat_history: Optional[List[Dict[str, str]]] = None,
380
+ state_variables: Optional[Dict[str, Any]] = None,
381
+ execution_mode: Optional[str] = None,
382
+ enable_callbacks: bool = False
383
+ ) -> str:
384
+ """Handle execute skill operation with comprehensive input validation."""
385
+ # If no skill_name provided, use LLM to intelligently select the best skill
386
+ if not skill_name:
387
+ skill_name = self._select_skill_with_llm(task)
388
+ if not skill_name:
389
+ return "Error: No appropriate skill found for the given task."
390
+
391
+ try:
392
+ # Get skill metadata
393
+ skill = self.registry.get(skill_name)
394
+ if not skill:
395
+ available_skills = [s.name for s in self.registry.list()]
396
+ return (f"Skill '{skill_name}' not found. "
397
+ f"Available skills: {', '.join(available_skills)}")
398
+
399
+ # Validate inputs against skill expectations
400
+ validation_result = self._validate_skill_inputs(skill, context, state_variables, chat_history)
401
+ if validation_result:
402
+ return validation_result # Return validation error message
403
+
404
+ # Apply router-level defaults and overrides
405
+ # 1. Apply router-level default timeout if not already set
406
+ if self.default_timeout and skill.execution.timeout == 300: # 300 is system default
407
+ skill.execution.timeout = self.default_timeout
408
+
409
+ # 2. Apply router-level default execution mode if not already set
410
+ if self.default_execution_mode and not execution_mode:
411
+ execution_mode = self.default_execution_mode
412
+
413
+ # 3. Override execution mode if specified in input (takes priority)
414
+ if execution_mode:
415
+ skill.execution.mode = execution_mode
416
+
417
+ # Determine input based on skill type
418
+ if skill.skill_type == SkillType.AGENT:
419
+ skill_context = context
420
+ skill_chat_history = chat_history
421
+ else: # GRAPH
422
+ # For graphs, merge task into state variables
423
+ skill_context = state_variables or {}
424
+ skill_chat_history = None
425
+
426
+ # Execute the skill
427
+ result = self.executor.execute_skill(
428
+ metadata=skill,
429
+ task=task,
430
+ context=skill_context,
431
+ chat_history=skill_chat_history
432
+ )
433
+
434
+ # Format result for LLM consumption
435
+ return self._format_execution_result(result)
436
+
437
+ except Exception as e:
438
+ logger.error(f"Skill execution failed: {e}")
439
+ return f"Error executing skill '{skill_name}': {str(e)}"
440
+
441
+ def _select_skill_with_llm(self, task: str) -> Optional[str]:
442
+ """Use LLM to intelligently select the best skill for a given task."""
443
+ if not self.llm or not task:
444
+ logger.warning("LLM or task not available for skill selection")
445
+ return None
446
+
447
+ try:
448
+ # Get available skills for LLM to choose from
449
+ skills = self.registry.list()
450
+ if not skills:
451
+ logger.warning("No skills available for selection")
452
+ return None
453
+
454
+ # Format skills list for the prompt
455
+ skills_list = []
456
+ for skill in skills:
457
+ skill_info = f"- {skill.name}"
458
+ if skill.description:
459
+ skill_info += f": {skill.description}"
460
+ if skill.capabilities:
461
+ skill_info += f" (capabilities: {', '.join(skill.capabilities)})"
462
+ skills_list.append(skill_info)
463
+
464
+ skills_text = "\n".join(skills_list)
465
+
466
+ # Create routing prompt - use custom_prompt if provided, otherwise default
467
+ if self.custom_prompt:
468
+ # If custom prompt provided, combine it with routing instructions
469
+ routing_template = f"""{self.custom_prompt}
470
+
471
+ Your task is to analyze the user's request and select the most appropriate skill from the available options.
472
+
473
+ Available skills:
474
+ {{skills_list}}
475
+
476
+ Task: {{task}}
477
+
478
+ IMPORTANT: Respond with only the skill name that best matches the task. If no skill is appropriate, respond with "no_match"."""
479
+ else:
480
+ # Use default routing prompt
481
+ routing_template = """You are a skill router. Analyze the user's task and select the most appropriate skill.
482
+
483
+ Available skills:
484
+ {skills_list}
485
+
486
+ Task: {task}
487
+
488
+ Respond with only the skill name that best matches the task. If no skill is appropriate, respond with "no_match"."""
489
+
490
+ # Format the routing prompt
491
+ formatted_prompt = routing_template.format(
492
+ skills_list=skills_text,
493
+ task=task
494
+ )
495
+
496
+ logger.info(f"Using LLM to select skill for task: {task}")
497
+
498
+ # Get LLM response
499
+ response = self.llm.invoke(formatted_prompt)
500
+ if hasattr(response, 'content'):
501
+ selected_skill = response.content.strip()
502
+ else:
503
+ selected_skill = str(response).strip()
504
+
505
+ # Validate that the selected skill exists
506
+ if selected_skill == "no_match":
507
+ logger.info("LLM determined no skill matches the task")
508
+ return None
509
+
510
+ # Check if the selected skill actually exists
511
+ skill = self.registry.get(selected_skill)
512
+ if skill:
513
+ logger.info(f"LLM selected skill: {selected_skill}")
514
+ return selected_skill
515
+ else:
516
+ logger.warning(f"LLM selected non-existent skill: {selected_skill}")
517
+ return None
518
+
519
+ except Exception as e:
520
+ logger.error(f"Error in LLM skill selection: {e}")
521
+ return None
522
+
523
+ def _validate_skill_inputs(
524
+ self,
525
+ skill,
526
+ context: Optional[Dict[str, Any]],
527
+ state_variables: Optional[Dict[str, Any]],
528
+ chat_history: Optional[List[Dict[str, str]]]
529
+ ) -> Optional[str]:
530
+ """
531
+ Validate inputs against skill expectations and provide graceful error messages.
532
+
533
+ Returns:
534
+ None if validation passes, error message string if validation fails.
535
+ """
536
+ try:
537
+ if skill.skill_type == SkillType.AGENT:
538
+ return self._validate_agent_inputs(skill, context, chat_history)
539
+ else: # GRAPH
540
+ return self._validate_graph_inputs(skill, state_variables)
541
+ except Exception as e:
542
+ logger.error(f"Input validation error: {e}")
543
+ return f"Error validating inputs: {str(e)}"
544
+
545
+ def _validate_agent_inputs(
546
+ self,
547
+ skill,
548
+ context: Optional[Dict[str, Any]],
549
+ chat_history: Optional[List[Dict[str, str]]]
550
+ ) -> Optional[str]:
551
+ """Validate inputs for agent skills."""
552
+ # Validate chat history format if provided
553
+ if chat_history and not isinstance(chat_history, list):
554
+ return (f"❌ **Invalid chat_history format for skill '{skill.name}'**\n\n"
555
+ f"Chat history must be a list of messages.\n"
556
+ f"Expected format: [{{'role': 'user', 'content': 'message'}}]\n"
557
+ f"Got: {type(chat_history).__name__}")
558
+
559
+ if chat_history:
560
+ for i, msg in enumerate(chat_history):
561
+ if not isinstance(msg, dict) or 'role' not in msg or 'content' not in msg:
562
+ return (f"❌ **Invalid chat_history format for skill '{skill.name}'**\n\n"
563
+ f"Message at index {i} is invalid.\n"
564
+ f"Each message must have 'role' and 'content' keys.\n"
565
+ f"Expected: {{'role': 'user'|'assistant', 'content': 'message text'}}\n"
566
+ f"Got: {msg}")
567
+
568
+ if not hasattr(skill.inputs, 'variables') or not skill.inputs.variables:
569
+ return None # No input requirements defined
570
+
571
+ provided_context = context or {}
572
+ missing_required = []
573
+ invalid_types = []
574
+
575
+ # Check each required variable
576
+ for var_name, var_def in skill.inputs.variables.items():
577
+ is_required = var_def.get('required', False)
578
+ expected_type = var_def.get('type', 'any')
579
+
580
+ if is_required and var_name not in provided_context:
581
+ missing_required.append({
582
+ 'name': var_name,
583
+ 'type': expected_type,
584
+ 'description': var_def.get('description', '')
585
+ })
586
+ elif var_name in provided_context and expected_type != 'any':
587
+ # Validate type if specified and value is provided
588
+ provided_value = provided_context[var_name]
589
+ if not self._validate_type(provided_value, expected_type):
590
+ invalid_types.append({
591
+ 'name': var_name,
592
+ 'expected': expected_type,
593
+ 'provided': type(provided_value).__name__,
594
+ 'value': str(provided_value)[:50]
595
+ })
596
+
597
+ # Build error message if there are issues
598
+ if missing_required or invalid_types:
599
+ error_msg = f"❌ **Input validation failed for skill '{skill.name}'**\n\n"
600
+
601
+ if missing_required:
602
+ error_msg += "**Missing required variables:**\n"
603
+ for var in missing_required:
604
+ error_msg += f"- **{var['name']}** ({var['type']}): {var['description']}\n"
605
+ error_msg += "\n"
606
+
607
+ if invalid_types:
608
+ error_msg += "**Type mismatches:**\n"
609
+ for var in invalid_types:
610
+ error_msg += f"- **{var['name']}**: expected {var['expected']}, got {var['provided']} ('{var['value']}')\n"
611
+ error_msg += "\n"
612
+
613
+ # Provide correct usage example
614
+ error_msg += "**Correct usage:**\n"
615
+ example_context = {}
616
+ for var_name, var_def in skill.inputs.variables.items():
617
+ example_value = self._get_example_value(var_def.get('type', 'string'))
618
+ example_context[var_name] = example_value
619
+
620
+ example = {
621
+ "skill_name": skill.name,
622
+ "task": "Your task description here",
623
+ "context": example_context
624
+ }
625
+ error_msg += f"```json\n{json.dumps(example, indent=2)}\n```"
626
+
627
+ return error_msg
628
+
629
+ return None
630
+
631
+ def _validate_graph_inputs(
632
+ self,
633
+ skill,
634
+ state_variables: Optional[Dict[str, Any]]
635
+ ) -> Optional[str]:
636
+ """Validate inputs for graph skills."""
637
+ if not hasattr(skill.inputs, 'state_variables') or not skill.inputs.state_variables:
638
+ return None # No input requirements defined
639
+
640
+ provided_state = state_variables or {}
641
+ missing_required = []
642
+ invalid_types = []
643
+
644
+ # Check each required state variable
645
+ for var_name, var_def in skill.inputs.state_variables.items():
646
+ is_required = var_def.get('required', False)
647
+ expected_type = var_def.get('type', 'any')
648
+
649
+ if is_required and var_name not in provided_state:
650
+ missing_required.append({
651
+ 'name': var_name,
652
+ 'type': expected_type,
653
+ 'description': var_def.get('description', '')
654
+ })
655
+ elif var_name in provided_state and expected_type != 'any':
656
+ # Validate type if specified and value is provided
657
+ provided_value = provided_state[var_name]
658
+ if not self._validate_type(provided_value, expected_type):
659
+ invalid_types.append({
660
+ 'name': var_name,
661
+ 'expected': expected_type,
662
+ 'provided': type(provided_value).__name__,
663
+ 'value': str(provided_value)[:50]
664
+ })
665
+
666
+ # Build error message if there are issues
667
+ if missing_required or invalid_types:
668
+ error_msg = f"❌ **Input validation failed for skill '{skill.name}'**\n\n"
669
+
670
+ if missing_required:
671
+ error_msg += "**Missing required state variables:**\n"
672
+ for var in missing_required:
673
+ error_msg += f"- **{var['name']}** ({var['type']}): {var['description']}\n"
674
+ error_msg += "\n"
675
+
676
+ if invalid_types:
677
+ error_msg += "**Type mismatches:**\n"
678
+ for var in invalid_types:
679
+ error_msg += f"- **{var['name']}**: expected {var['expected']}, got {var['provided']} ('{var['value']}')\n"
680
+ error_msg += "\n"
681
+
682
+ # Provide correct usage example
683
+ error_msg += "**Correct usage:**\n"
684
+ example_state = {}
685
+ for var_name, var_def in skill.inputs.state_variables.items():
686
+ example_value = self._get_example_value(var_def.get('type', 'string'))
687
+ example_state[var_name] = example_value
688
+
689
+ example = {
690
+ "skill_name": skill.name,
691
+ "task": "Your task description here",
692
+ "state_variables": example_state
693
+ }
694
+ error_msg += f"```json\n{json.dumps(example, indent=2)}\n```"
695
+
696
+ return error_msg
697
+
698
+ return None
699
+
700
+ def _validate_type(self, value: Any, expected_type: str) -> bool:
701
+ """Validate that a value matches the expected type."""
702
+ type_mapping = {
703
+ 'string': str,
704
+ 'str': str,
705
+ 'integer': int,
706
+ 'int': int,
707
+ 'number': (int, float),
708
+ 'float': float,
709
+ 'boolean': bool,
710
+ 'bool': bool,
711
+ 'list': list,
712
+ 'array': list,
713
+ 'dict': dict,
714
+ 'object': dict,
715
+ 'any': object # Accept anything
716
+ }
717
+
718
+ expected_py_type = type_mapping.get(expected_type.lower(), str)
719
+ return isinstance(value, expected_py_type)
720
+
721
+ def _get_example_value(self, var_type: str) -> Any:
722
+ """Generate example values for different types."""
723
+ examples = {
724
+ 'string': 'example_value',
725
+ 'str': 'example_value',
726
+ 'integer': 42,
727
+ 'int': 42,
728
+ 'number': 42.0,
729
+ 'float': 42.0,
730
+ 'boolean': True,
731
+ 'bool': True,
732
+ 'list': ['item1', 'item2'],
733
+ 'array': ['item1', 'item2'],
734
+ 'dict': {'key': 'value'},
735
+ 'object': {'key': 'value'},
736
+ 'any': 'example_value'
737
+ }
738
+ return examples.get(var_type.lower(), 'example_value')
739
+
740
+ def _format_execution_result(self, result: SkillExecutionResult) -> str:
741
+ """
742
+ Format execution result for LLM consumption.
743
+
744
+ Args:
745
+ result: SkillExecutionResult to format.
746
+
747
+ Returns:
748
+ Formatted string result.
749
+ """
750
+ if result.status == SkillStatus.ERROR:
751
+ formatted = f"❌ **Skill '{result.skill_name}' failed**\n\n"
752
+ formatted += f"Error: {result.error_details or 'Unknown error'}\n"
753
+ formatted += f"Duration: {result.duration:.1f}s"
754
+ return formatted
755
+
756
+ # Start with the main output
757
+ formatted = result.output_text
758
+
759
+ # Add execution metadata
760
+ formatted += f"\n\n---\n"
761
+ formatted += f"**Execution Summary:**\n"
762
+ skill_type_str = result.skill_type.value if hasattr(result.skill_type, 'value') else str(result.skill_type)
763
+ status_str = result.status.value if hasattr(result.status, 'value') else str(result.status)
764
+ execution_mode_str = result.execution_mode.value if hasattr(result.execution_mode, 'value') else str(result.execution_mode)
765
+ formatted += f"- Skill: {result.skill_name} ({skill_type_str})\n"
766
+ formatted += f"- Status: {status_str}\n"
767
+ formatted += f"- Duration: {result.duration:.1f}s\n"
768
+ formatted += f"- Mode: {execution_mode_str}\n"
769
+
770
+ # Add file references if any
771
+ if result.output_files:
772
+ formatted += f"\n**Generated Files:**\n"
773
+ for file_ref in result.output_files:
774
+ formatted += f"- {file_ref}\n"
775
+
776
+ return formatted