alita-sdk 0.3.462__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 (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ import logging
3
3
  import re
4
4
  import requests
5
5
  from typing import Generator, Literal, Optional
6
+ from urllib.parse import unquote
6
7
 
7
8
  from azure.devops.connection import Connection
8
9
  from azure.devops.exceptions import AzureDevOpsServiceError
@@ -13,7 +14,7 @@ from azure.devops.v7_0.wiki.models import GitVersionDescriptor
13
14
  from langchain_core.documents import Document
14
15
  from langchain_core.tools import ToolException
15
16
  from msrest.authentication import BasicAuthentication
16
- from pydantic import create_model, PrivateAttr, SecretStr
17
+ from pydantic import create_model, PrivateAttr, SecretStr, BaseModel
17
18
  from pydantic import model_validator
18
19
  from pydantic.fields import Field
19
20
 
@@ -47,13 +48,35 @@ GetPageByIdInput = create_model(
47
48
  Field(description="Prompt which is used for image description", default=None))
48
49
  )
49
50
 
51
+
52
+ class GetPageInput(BaseModel):
53
+ """Input schema for get_wiki_page tool with validation."""
54
+ wiki_identified: str = Field(description="Wiki ID or wiki name")
55
+ page_path: Optional[str] = Field(default=None, description="Wiki page path (e.g., '/MB_Heading/MB_2')")
56
+ page_id: Optional[int] = Field(default=None, description="Wiki page ID")
57
+ include_content: Optional[bool] = Field(default=False, description="Whether to include page content in the response. If True, content will be processed for image descriptions.")
58
+ image_description_prompt: Optional[str] = Field(default=None, description="Prompt which is used for image description when include_content is True")
59
+ recursion_level: Optional[str] = Field(default="oneLevel", description="Level of recursion to retrieve sub-pages. Options: 'none' (no subpages), 'oneLevel' (direct children only), 'full' (all descendants). Defaults to 'oneLevel'.")
60
+
61
+ @model_validator(mode='before')
62
+ @classmethod
63
+ def validate_inputs(cls, values):
64
+ """Validator to ensure at least one of page_path or page_id is provided."""
65
+ page_path = values.get('page_path')
66
+ page_id = values.get('page_id')
67
+ if not page_path and not page_id:
68
+ raise ValueError("At least one of 'page_path' or 'page_id' must be provided")
69
+ return values
70
+
71
+
50
72
  ModifyPageInput = create_model(
51
73
  "ModifyPageInput",
52
74
  wiki_identified=(str, Field(description="Wiki ID or wiki name")),
53
75
  page_name=(str, Field(description="Wiki page name")),
54
76
  page_content=(str, Field(description="Wiki page content")),
55
- version_identifier=(str, Field(description="Version string identifier (name of tag/branch, SHA1 of commit)")),
56
- version_type=(Optional[str], Field(description="Version type (branch, tag, or commit). Determines how Id is interpreted", default="branch"))
77
+ version_identifier=(str, Field(description="Version string identifier (name of tag/branch, SHA1 of commit). Usually for wiki the branch is 'wikiMaster'")),
78
+ version_type=(Optional[str], Field(description="Version type (branch, tag, or commit). Determines how Id is interpreted", default="branch")),
79
+ expanded=(Optional[bool], Field(description="Whether to return the full page object or just its simplified version.", default=False))
57
80
  )
58
81
 
59
82
  RenamePageInput = create_model(
@@ -66,6 +89,76 @@ RenamePageInput = create_model(
66
89
  )
67
90
 
68
91
 
92
+ def _format_wiki_page_response(wiki_page_response, expanded: bool = False, include_content: bool = False):
93
+ """Format wiki page response.
94
+
95
+ Args:
96
+ wiki_page_response: The WikiPageResponse object from Azure DevOps API
97
+ expanded: If True, returns comprehensive page metadata. If False, returns simplified format.
98
+ include_content: If True and expanded=True, includes the page content in the response.
99
+
100
+ Returns:
101
+ Dictionary with eTag and page information. Format depends on expanded parameter.
102
+ """
103
+ try:
104
+ if expanded:
105
+ # Comprehensive metadata format
106
+ page = wiki_page_response.page
107
+
108
+ # Process sub_pages if present
109
+ sub_pages = []
110
+ if page and hasattr(page, 'sub_pages') and page.sub_pages:
111
+ for sub_page in page.sub_pages:
112
+ sub_page_dict = {
113
+ 'id': sub_page.id if hasattr(sub_page, 'id') else None,
114
+ 'path': sub_page.path if hasattr(sub_page, 'path') else None,
115
+ 'order': sub_page.order if hasattr(sub_page, 'order') else None,
116
+ 'git_item_path': sub_page.git_item_path if hasattr(sub_page, 'git_item_path') else None,
117
+ 'url': sub_page.url if hasattr(sub_page, 'url') else None,
118
+ 'remote_url': sub_page.remote_url if hasattr(sub_page, 'remote_url') else None,
119
+ }
120
+ # Recursively process nested sub_pages if present
121
+ if hasattr(sub_page, 'sub_pages') and sub_page.sub_pages:
122
+ sub_page_dict['sub_pages'] = [
123
+ {
124
+ 'id': sp.id if hasattr(sp, 'id') else None,
125
+ 'path': sp.path if hasattr(sp, 'path') else None,
126
+ 'order': sp.order if hasattr(sp, 'order') else None,
127
+ }
128
+ for sp in sub_page.sub_pages
129
+ ]
130
+ sub_pages.append(sub_page_dict)
131
+
132
+ result = {
133
+ 'eTag': wiki_page_response.eTag,
134
+ 'page': {
135
+ 'id': page.id if page else None,
136
+ 'path': page.path if page else None,
137
+ 'git_item_path': page.git_item_path if page and hasattr(page, 'git_item_path') else None,
138
+ 'remote_url': page.remote_url if page and hasattr(page, 'remote_url') else None,
139
+ 'url': page.url if page else None,
140
+ 'order': page.order if page and hasattr(page, 'order') else None,
141
+ 'is_parent_page': page.is_parent_page if page and hasattr(page, 'is_parent_page') else None,
142
+ 'is_non_conformant': page.is_non_conformant if page and hasattr(page, 'is_non_conformant') else None,
143
+ 'sub_pages': sub_pages,
144
+ }
145
+ }
146
+ # Include content if requested
147
+ if include_content and page and hasattr(page, 'content'):
148
+ result['page']['content'] = page.content
149
+ return result
150
+ else:
151
+ # Simplified format for backward compatibility
152
+ return {
153
+ "eTag": wiki_page_response.eTag,
154
+ "id": wiki_page_response.page.id,
155
+ "page": wiki_page_response.page.url
156
+ }
157
+ except Exception as e:
158
+ logger.error(f"Unable to format wiki page response: {wiki_page_response}, error: {str(e)}")
159
+ return wiki_page_response
160
+
161
+
69
162
  class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
70
163
  # TODO use ado_configuration instead of organization_url, project and token
71
164
  organization_url: str
@@ -91,7 +184,29 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
91
184
  cls._core_client = connection.clients.get_core_client()
92
185
 
93
186
  except Exception as e:
94
- return ImportError(f"Failed to connect to Azure DevOps: {e}")
187
+ error_msg = str(e).lower()
188
+ if "expired" in error_msg or "token" in error_msg and ("invalid" in error_msg or "unauthorized" in error_msg):
189
+ raise ValueError(
190
+ "Azure DevOps connection failed: Your access token has expired or is invalid. "
191
+ "Please refresh your token in the toolkit configuration."
192
+ )
193
+ elif "401" in error_msg or "unauthorized" in error_msg:
194
+ raise ValueError(
195
+ "Azure DevOps connection failed: Authentication failed. "
196
+ "Please check your credentials in the toolkit configuration."
197
+ )
198
+ elif "404" in error_msg or "not found" in error_msg:
199
+ raise ValueError(
200
+ "Azure DevOps connection failed: Organization or project not found. "
201
+ "Please verify your organization URL and project name."
202
+ )
203
+ elif "timeout" in error_msg or "timed out" in error_msg:
204
+ raise ValueError(
205
+ "Azure DevOps connection failed: Connection timed out. "
206
+ "Please check your network connection and try again."
207
+ )
208
+ else:
209
+ raise ValueError(f"Azure DevOps connection failed: {e}")
95
210
 
96
211
  return super().validate_toolkit(values)
97
212
 
@@ -108,7 +223,7 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
108
223
  try:
109
224
  return self._process_images(self._client.get_page(project=self.project, wiki_identifier=wiki_identified, path=page_name,
110
225
  include_content=True).page.content,
111
- image_description_prompt=image_description_prompt)
226
+ image_description_prompt=image_description_prompt, wiki_identified=wiki_identified)
112
227
  except Exception as e:
113
228
  logger.error(f"Error during the attempt to extract wiki page: {str(e)}")
114
229
  return ToolException(f"Error during the attempt to extract wiki page: {str(e)}")
@@ -118,22 +233,176 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
118
233
  try:
119
234
  return self._process_images(self._client.get_page_by_id(project=self.project, wiki_identifier=wiki_identified, id=page_id,
120
235
  include_content=True).page.content,
121
- image_description_prompt=image_description_prompt)
236
+ image_description_prompt=image_description_prompt, wiki_identified=wiki_identified)
122
237
  except Exception as e:
123
238
  logger.error(f"Error during the attempt to extract wiki page: {str(e)}")
124
239
  return ToolException(f"Error during the attempt to extract wiki page: {str(e)}")
125
240
 
126
- def _process_images(self, page_content: str, image_description_prompt=None):
241
+ def get_wiki_page(self, wiki_identified: str, page_path: Optional[str] = None, page_id: Optional[int] = None,
242
+ include_content: bool = False, image_description_prompt: Optional[str] = None,
243
+ recursion_level: str = "oneLevel"):
244
+ """Get wiki page metadata and optionally content.
245
+
246
+ Retrieves comprehensive metadata for a wiki page including eTag, id, path, git_item_path,
247
+ remote_url, url, sub_pages, order, and other properties. Optionally includes page content.
248
+ Supports lookup by either page_id (takes precedence) or page_path.
249
+
250
+ Args:
251
+ wiki_identified: Wiki ID or wiki name
252
+ page_path: Wiki page path (e.g., '/MB_Heading/MB_2'). Optional if page_id is provided.
253
+ page_id: Wiki page ID. Optional if page_path is provided. Takes precedence over page_path.
254
+ include_content: Whether to include page content in response. Defaults to False (metadata only).
255
+ image_description_prompt: Optional prompt for image description when include_content is True.
256
+ recursion_level: Level of recursion to retrieve sub-pages. Options: 'none' (no subpages),
257
+ 'oneLevel' (direct children only), 'full' (all descendants). Defaults to 'oneLevel'.
258
+
259
+ Returns:
260
+ Dictionary containing eTag and comprehensive page metadata including id, path, git_item_path,
261
+ remote_url, url, sub_pages, order, is_parent_page, is_non_conformant, and optionally content.
262
+
263
+ Raises:
264
+ ToolException: If page/wiki not found, authentication fails, or other errors occur.
265
+ """
266
+ try:
267
+ # Validate that at least one identifier is provided
268
+ if not page_path and not page_id:
269
+ raise ToolException("At least one of 'page_path' or 'page_id' must be provided")
270
+
271
+ # Fetch page using page_id (priority) or page_path
272
+ if page_id:
273
+ logger.info(f"Fetching wiki page by ID: {page_id} from wiki: {wiki_identified}")
274
+ wiki_page_response = self._client.get_page_by_id(
275
+ project=self.project,
276
+ wiki_identifier=wiki_identified,
277
+ id=page_id,
278
+ include_content=include_content,
279
+ recursion_level=recursion_level
280
+ )
281
+ else:
282
+ logger.info(f"Fetching wiki page by path: {page_path} from wiki: {wiki_identified}")
283
+ wiki_page_response = self._client.get_page(
284
+ project=self.project,
285
+ wiki_identifier=wiki_identified,
286
+ path=page_path,
287
+ include_content=include_content,
288
+ recursion_level=recursion_level
289
+ )
290
+
291
+ # Format response with comprehensive metadata
292
+ result = _format_wiki_page_response(
293
+ wiki_page_response,
294
+ expanded=True,
295
+ include_content=include_content
296
+ )
297
+
298
+ # Process images in content if requested
299
+ if include_content and result.get('page', {}).get('content'):
300
+ processed_content = self._process_images(
301
+ result['page']['content'],
302
+ image_description_prompt=image_description_prompt,
303
+ wiki_identified=wiki_identified
304
+ )
305
+ result['page']['content'] = processed_content
306
+
307
+ return result
308
+
309
+ except AzureDevOpsServiceError as e:
310
+ error_msg = str(e).lower()
311
+
312
+ # Page not found errors
313
+ if "404" in error_msg or "not found" in error_msg or "does not exist" in error_msg:
314
+ identifier = f"ID {page_id}" if page_id else f"path '{page_path}'"
315
+ logger.error(f"Page {identifier} not found in wiki '{wiki_identified}': {str(e)}")
316
+ return ToolException(
317
+ f"Page {identifier} not found in wiki '{wiki_identified}'. "
318
+ f"Please verify the page exists and the identifier is correct."
319
+ )
320
+
321
+ # Path validation errors
322
+ elif "path" in error_msg and ("correct" in error_msg or "invalid" in error_msg):
323
+ logger.error(f"Invalid page path '{page_path}' in wiki '{wiki_identified}': {str(e)}")
324
+ return ToolException(
325
+ f"Invalid page path '{page_path}'. Please ensure the path format is correct (e.g., '/PageName')."
326
+ )
327
+
328
+ # Wiki not found errors
329
+ elif "wiki" in error_msg and ("not found" in error_msg or "does not exist" in error_msg):
330
+ logger.error(f"Wiki '{wiki_identified}' not found: {str(e)}")
331
+ return ToolException(
332
+ f"Wiki '{wiki_identified}' not found. Please verify the wiki identifier is correct."
333
+ )
334
+
335
+ # Authentication/authorization errors
336
+ elif "401" in error_msg or "unauthorized" in error_msg or "authentication" in error_msg:
337
+ logger.error(f"Authentication failed for wiki '{wiki_identified}': {str(e)}")
338
+ return ToolException(
339
+ f"Authentication failed. Please check your access token is valid and has permission to access wiki '{wiki_identified}'."
340
+ )
341
+
342
+ elif "403" in error_msg or "forbidden" in error_msg or "permission" in error_msg:
343
+ logger.error(f"Permission denied for wiki '{wiki_identified}': {str(e)}")
344
+ return ToolException(
345
+ f"Permission denied. You do not have access to wiki '{wiki_identified}' or page {page_id if page_id else page_path}."
346
+ )
347
+
348
+ # Generic Azure DevOps service errors
349
+ else:
350
+ logger.error(f"Azure DevOps service error while fetching page: {str(e)}")
351
+ return ToolException(f"Error accessing wiki page: {str(e)}")
352
+
353
+ except ValueError as e:
354
+ logger.error(f"Validation error: {str(e)}")
355
+ return ToolException(f"Validation error: {str(e)}")
356
+
357
+ except Exception as e:
358
+ error_msg = str(e).lower()
359
+
360
+ # Timeout errors
361
+ if "timeout" in error_msg or "timed out" in error_msg:
362
+ logger.error(f"Connection timeout while fetching page: {str(e)}")
363
+ return ToolException(
364
+ f"Connection timeout. Please check your network connection and try again."
365
+ )
366
+
367
+ # Generic errors
368
+ logger.error(f"Unexpected error during wiki page retrieval: {str(e)}")
369
+ return ToolException(f"Unexpected error during wiki page retrieval: {str(e)}")
370
+
371
+ def _process_images(self, page_content: str, wiki_identified: str, image_description_prompt=None):
127
372
 
128
373
  image_pattern = r"!\[(.*?)\]\((.*?)\)"
129
374
  matches = re.findall(image_pattern, page_content)
130
375
 
376
+ # Initialize repos_wrapper once for all attachments in this page
377
+ repos_wrapper = None
378
+ has_attachments = any(url.startswith("/.attachments/") for _, url in matches)
379
+
380
+ if has_attachments:
381
+ try:
382
+ wiki_master_branch = "wikiMaster"
383
+ wiki = self._client.get_wiki(project=self.project, wiki_identifier=wiki_identified)
384
+ repository_id = wiki.repository_id
385
+ repos_wrapper = ReposApiWrapper(
386
+ organization_url=self.organization_url,
387
+ project=self.project,
388
+ token=self.token.get_secret_value(),
389
+ repository_id=repository_id,
390
+ base_branch=wiki_master_branch,
391
+ active_branch=wiki_master_branch,
392
+ llm=self.llm
393
+ )
394
+ except Exception as e:
395
+ logger.error(f"Failed to initialize repos wrapper for wiki '{wiki_identified}': {str(e)}")
396
+
131
397
  for image_name, image_url in matches:
132
398
  if image_url.startswith("/.attachments/"):
133
399
  try:
400
+ if repos_wrapper is None:
401
+ raise Exception("Repos wrapper not initialized")
134
402
  description = self.process_attachment(attachment_url=image_url,
135
403
  attachment_name=image_name,
136
- image_description_prompt=image_description_prompt)
404
+ image_description_prompt=image_description_prompt,
405
+ repos_wrapper=repos_wrapper)
137
406
  except Exception as e:
138
407
  logger.error(f"Error parsing attachment: {str(e)}")
139
408
  description = f"Error parsing attachment: {image_url}"
@@ -156,15 +425,9 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
156
425
  page_content = page_content.replace(f"![{image_name}]({image_url})", new_image_markdown)
157
426
  return page_content
158
427
 
159
- def process_attachment(self, attachment_url, attachment_name, image_description_prompt):
160
- wiki_master_branch = "wikiMaster"
161
- repos_wrapper = ReposApiWrapper(organization_url=self.organization_url,
162
- project=self.project,
163
- token=self.token.get_secret_value(),
164
- repository_id="Test_agent.wiki",
165
- base_branch=wiki_master_branch,
166
- active_branch=wiki_master_branch)
167
- attachment_content = repos_wrapper.download_file(path=attachment_url)
428
+ def process_attachment(self, attachment_url, attachment_name, repos_wrapper, image_description_prompt):
429
+ file_path = unquote(attachment_url.lstrip('/'))
430
+ attachment_content = repos_wrapper.download_file(path=file_path)
168
431
  return parse_file_content(
169
432
  file_content=attachment_content,
170
433
  file_name=attachment_name,
@@ -226,7 +489,7 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
226
489
  logger.error(f"Unable to rename wiki page: {str(e)}")
227
490
  return ToolException(f"Unable to rename wiki page: {str(e)}")
228
491
 
229
- def modify_wiki_page(self, wiki_identified: str, page_name: str, page_content: str, version_identifier: str, version_type: str = "branch"):
492
+ def modify_wiki_page(self, wiki_identified: str, page_name: str, page_content: str, version_identifier: str, version_type: str = "branch", expanded: Optional[bool] = False):
230
493
  """Create or Update ADO wiki page content."""
231
494
  try:
232
495
  all_wikis = [wiki.name for wiki in self._client.get_all_wikis(project=self.project)]
@@ -257,24 +520,24 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
257
520
  return ToolException(f"Unable to extract page by path {page_name}: {str(get_page_e)}")
258
521
 
259
522
  try:
260
- return self._client.create_or_update_page(
523
+ return _format_wiki_page_response(self._client.create_or_update_page(
261
524
  project=self.project,
262
525
  wiki_identifier=wiki_identified,
263
526
  path=page_name,
264
527
  parameters=WikiPageCreateOrUpdateParameters(content=page_content),
265
528
  version=version,
266
529
  version_descriptor=GitVersionDescriptor(version=version_identifier, version_type=version_type)
267
- )
530
+ ), expanded=expanded)
268
531
  except AzureDevOpsServiceError as e:
269
532
  if "The version '{0}' either is invalid or does not exist." in str(e):
270
533
  # Retry the request without version_descriptor
271
- return self._client.create_or_update_page(
534
+ return _format_wiki_page_response(wiki_page_response=self._client.create_or_update_page(
272
535
  project=self.project,
273
536
  wiki_identifier=wiki_identified,
274
537
  path=page_name,
275
538
  parameters=WikiPageCreateOrUpdateParameters(content=page_content),
276
539
  version=version
277
- )
540
+ ), expanded=expanded)
278
541
  else:
279
542
  raise
280
543
  except Exception as e:
@@ -323,6 +586,12 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
323
586
  "args_schema": GetWikiInput,
324
587
  "ref": self.get_wiki,
325
588
  },
589
+ {
590
+ "name": "get_wiki_page",
591
+ "description": self.get_wiki_page.__doc__,
592
+ "args_schema": GetPageInput,
593
+ "ref": self.get_wiki_page,
594
+ },
326
595
  {
327
596
  "name": "get_wiki_page_by_path",
328
597
  "description": self.get_wiki_page_by_path.__doc__,
@@ -9,22 +9,36 @@ from ...elitea_base import filter_missconfigured_index_tools
9
9
  from ....configurations.ado import AdoConfiguration
10
10
  from ....configurations.pgvector import PgVectorConfiguration
11
11
  from ...base.tool import BaseAction
12
- from ...utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
12
+ from ...utils import clean_string, get_max_toolkit_length, check_connection_response
13
+ from ....runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
13
14
 
14
15
  name = "ado_boards"
15
16
 
17
+ def get_toolkit(tool):
18
+ return AzureDevOpsWorkItemsToolkit().get_toolkit(
19
+ selected_tools=tool['settings'].get('selected_tools', []),
20
+ ado_configuration=tool['settings']['ado_configuration'],
21
+ limit=tool['settings'].get('limit', 5),
22
+ toolkit_name=tool.get('toolkit_name', ''),
23
+ alita=tool['settings'].get('alita', None),
24
+ llm=tool['settings'].get('llm', None),
25
+ pgvector_configuration=tool['settings'].get('pgvector_configuration', {}),
26
+ collection_name=tool['toolkit_name'],
27
+ doctype='doc',
28
+ embedding_model=tool['settings'].get('embedding_model'),
29
+ vectorstore_type="PGVector"
30
+ )
31
+
16
32
  class AzureDevOpsWorkItemsToolkit(BaseToolkit):
17
33
  tools: List[BaseTool] = []
18
- toolkit_max_length: int = 0
19
34
 
20
35
  @staticmethod
21
36
  def toolkit_config_schema() -> BaseModel:
22
37
  selected_tools = {x['name']: x['args_schema'].schema() for x in AzureDevOpsApiWrapper.model_construct().get_available_tools()}
23
- AzureDevOpsWorkItemsToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
24
38
  m = create_model(
25
39
  name,
26
40
  ado_configuration=(AdoConfiguration, Field(description="Ado Work Item configuration", json_schema_extra={'configuration_types': ['ado']})),
27
- limit=(Optional[int], Field(description="ADO plans limit used for limitation of the list with results", default=5)),
41
+ limit=(Optional[int], Field(description="Default ADO boards result limit (can be overridden by agent instructions)", default=5, gt=0)),
28
42
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
29
43
  # indexer settings
30
44
  pgvector_configuration=(Optional[PgVectorConfiguration], Field(default = None,
@@ -37,7 +51,6 @@ class AzureDevOpsWorkItemsToolkit(BaseToolkit):
37
51
  'metadata': {
38
52
  "label": "ADO boards",
39
53
  "icon_url": "ado-boards-icon.svg",
40
- "max_length": AzureDevOpsWorkItemsToolkit.toolkit_max_length,
41
54
  "categories": ["project management"],
42
55
  "extra_categories": ["work item management", "issue tracking", "agile boards"],
43
56
  "sections": {
@@ -92,16 +105,20 @@ class AzureDevOpsWorkItemsToolkit(BaseToolkit):
92
105
  azure_devops_api_wrapper = AzureDevOpsApiWrapper(**wrapper_payload)
93
106
  available_tools = azure_devops_api_wrapper.get_available_tools()
94
107
  tools = []
95
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
96
108
  for tool in available_tools:
97
109
  if selected_tools:
98
110
  if tool["name"] not in selected_tools:
99
111
  continue
112
+ description = tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}/{azure_devops_api_wrapper.project}"
113
+ if toolkit_name:
114
+ description = f"{description}\nToolkit: {toolkit_name}"
115
+ description = description[:1000]
100
116
  tools.append(BaseAction(
101
117
  api_wrapper=azure_devops_api_wrapper,
102
- name=prefix + tool["name"],
103
- description=tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}/{azure_devops_api_wrapper.project}",
104
- args_schema=tool["args_schema"]
118
+ name=tool["name"],
119
+ description=description,
120
+ args_schema=tool["args_schema"],
121
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
105
122
  ))
106
123
  return cls(tools=tools)
107
124
 
@@ -127,7 +127,29 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
127
127
  cls._core_client = connection.clients_v7_1.get_core_client()
128
128
 
129
129
  except Exception as e:
130
- return ImportError(f"Failed to connect to Azure DevOps: {e}")
130
+ error_msg = str(e).lower()
131
+ if "expired" in error_msg or "token" in error_msg and ("invalid" in error_msg or "unauthorized" in error_msg):
132
+ raise ValueError(
133
+ "Azure DevOps connection failed: Your access token has expired or is invalid. "
134
+ "Please refresh your token in the toolkit configuration."
135
+ )
136
+ elif "401" in error_msg or "unauthorized" in error_msg:
137
+ raise ValueError(
138
+ "Azure DevOps connection failed: Authentication failed. "
139
+ "Please check your credentials in the toolkit configuration."
140
+ )
141
+ elif "404" in error_msg or "not found" in error_msg:
142
+ raise ValueError(
143
+ "Azure DevOps connection failed: Organization or project not found. "
144
+ "Please verify your organization URL and project name."
145
+ )
146
+ elif "timeout" in error_msg or "timed out" in error_msg:
147
+ raise ValueError(
148
+ "Azure DevOps connection failed: Connection timed out. "
149
+ "Please check your network connection and try again."
150
+ )
151
+ else:
152
+ raise ValueError(f"Azure DevOps connection failed: {e}")
131
153
 
132
154
  return super().validate_toolkit(values)
133
155
 
@@ -576,9 +598,40 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
576
598
  return b"".join(content_generator)
577
599
 
578
600
  def _process_document(self, document: Document) -> Generator[Document, None, None]:
579
- for attachment_id, file_name in document.metadata.get('attachment_ids', {}).items():
601
+ raw_attachment_ids = document.metadata.get('attachment_ids', {})
602
+
603
+ # Normalize attachment_ids: accept dict or JSON string, raise otherwise
604
+ if isinstance(raw_attachment_ids, str):
605
+ try:
606
+ loaded = json.loads(raw_attachment_ids)
607
+ except json.JSONDecodeError:
608
+ raise TypeError(
609
+ f"Expected dict or JSON string for 'attachment_ids', got non-JSON string for id="
610
+ f"{document.metadata.get('id')}: {raw_attachment_ids!r}"
611
+ )
612
+ if not isinstance(loaded, dict):
613
+ raise TypeError(
614
+ f"'attachment_ids' JSON did not decode to dict for id={document.metadata.get('id')}: {loaded!r}"
615
+ )
616
+ attachment_ids = loaded
617
+ elif isinstance(raw_attachment_ids, dict):
618
+ attachment_ids = raw_attachment_ids
619
+ else:
620
+ raise TypeError(
621
+ f"Expected 'attachment_ids' to be dict or JSON string, got {type(raw_attachment_ids)} "
622
+ f"for id={document.metadata.get('id')}: {raw_attachment_ids!r}"
623
+ )
624
+
625
+ for attachment_id, file_name in attachment_ids.items():
580
626
  content = self.get_attachment_content(attachment_id=attachment_id)
581
- yield Document(page_content="", metadata={'id': attachment_id, IndexerKeywords.CONTENT_FILE_NAME.value: file_name, IndexerKeywords.CONTENT_IN_BYTES.value: content})
627
+ yield Document(
628
+ page_content="",
629
+ metadata={
630
+ 'id': attachment_id,
631
+ IndexerKeywords.CONTENT_FILE_NAME.value: file_name,
632
+ IndexerKeywords.CONTENT_IN_BYTES.value: content,
633
+ },
634
+ )
582
635
 
583
636
  def _index_tool_params(self):
584
637
  """Return the parameters for indexing data."""
@@ -6,7 +6,8 @@ from pydantic import create_model, BaseModel, Field, SecretStr
6
6
  from .data_mining_wrapper import AdvancedJiraMiningWrapper
7
7
  from ..base.tool import BaseAction
8
8
  from ..elitea_base import filter_missconfigured_index_tools
9
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
9
+ from ..utils import clean_string, get_max_toolkit_length
10
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
10
11
 
11
12
  name = "advanced_jira_mining"
12
13
 
@@ -28,15 +29,13 @@ def get_tools(tool):
28
29
 
29
30
  class AdvancedJiraMiningToolkit(BaseToolkit):
30
31
  tools: List[BaseTool] = []
31
- toolkit_max_length: int = 0
32
32
 
33
33
  @staticmethod
34
34
  def toolkit_config_schema() -> BaseModel:
35
35
  selected_tools = {x['name']: x['args_schema'].schema() for x in AdvancedJiraMiningWrapper.model_construct().get_available_tools()}
36
- AdvancedJiraMiningToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
37
36
  return create_model(
38
37
  name,
39
- jira_base_url=(str, Field(default="", title="Jira URL", description="Jira URL", json_schema_extra={'toolkit_name': True, 'max_toolkit_length': AdvancedJiraMiningToolkit.toolkit_max_length})),
38
+ jira_base_url=(str, Field(default="", title="Jira URL", description="Jira URL", json_schema_extra={'toolkit_name': True})),
40
39
  confluence_base_url=(str, Field(default="", title="Confluence URL", description="Confluence URL")),
41
40
  model_type=(str, Field(default="", title="Model type", description="Model type")),
42
41
  summarization_prompt=(Optional[str], Field(default=None, title="Summarization prompt", description="Summarization prompt")),
@@ -66,17 +65,21 @@ class AdvancedJiraMiningToolkit(BaseToolkit):
66
65
  selected_tools = []
67
66
  jira_mining_wrapper = AdvancedJiraMiningWrapper(**kwargs)
68
67
  available_tools = jira_mining_wrapper.get_available_tools()
69
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
70
68
  tools = []
71
69
  for tool in available_tools:
72
70
  if selected_tools:
73
71
  if tool["name"] not in selected_tools:
74
72
  continue
73
+ description = tool["description"]
74
+ if toolkit_name:
75
+ description = f"Toolkit: {toolkit_name}\n{description}"
76
+ description = description[:1000]
75
77
  tools.append(BaseAction(
76
78
  api_wrapper=jira_mining_wrapper,
77
- name=prefix + tool["name"],
78
- description=tool["description"],
79
- args_schema=tool["args_schema"]
79
+ name=tool["name"],
80
+ description=description,
81
+ args_schema=tool["args_schema"],
82
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
80
83
  ))
81
84
  return cls(tools=tools)
82
85