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
@@ -4,7 +4,7 @@ import re
4
4
  import traceback
5
5
  from json import JSONDecodeError
6
6
  from traceback import format_exc
7
- from typing import List, Optional, Any, Dict, Generator
7
+ from typing import List, Optional, Any, Dict, Generator, Literal
8
8
  import os
9
9
 
10
10
  from atlassian import Jira
@@ -13,10 +13,11 @@ from langchain_core.tools import ToolException
13
13
  from pydantic import Field, PrivateAttr, model_validator, create_model, SecretStr
14
14
  import requests
15
15
 
16
- from ..elitea_base import BaseVectorStoreToolApiWrapper, extend_with_vector_tools
17
16
  from ..llm.img_utils import ImageDescriptionCache
17
+ from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
18
18
  from ..utils import is_cookie_token, parse_cookie_string
19
- from ..utils.content_parser import parse_file_content, load_content_from_bytes
19
+ from ..utils.available_tools_decorator import extend_with_parent_available_tools
20
+ from ..utils.content_parser import file_extension_by_chunker, process_content_by_type
20
21
  from ...runtime.utils.utils import IndexerKeywords
21
22
 
22
23
  logger = logging.getLogger(__name__)
@@ -131,6 +132,13 @@ GetRemoteLinks = create_model(
131
132
  jira_issue_key=(str, Field(description="Jira issue key from which remote links will be extracted, e.g. TEST-1234"))
132
133
  )
133
134
 
135
+ GetIssueAttachments = create_model(
136
+ "GetIssueAttachments",
137
+ jira_issue_key=(str, Field(description="Jira issue key from which remote links will be extracted, e.g. TEST-1234")),
138
+ attachment_pattern=(Optional[str], Field(description="Regex pattern to filter attachment filenames. If not provided,"
139
+ " all attachments will be processed", default=None))
140
+ )
141
+
134
142
  ListCommentsInput = create_model(
135
143
  "ListCommentsInputModel",
136
144
  issue_key=(str, Field(description="The issue key of the Jira issue from which comments will be extracted, e.g. 'TEST-123'."))
@@ -391,7 +399,7 @@ def process_search_response(jira_url, response, payload_params: Dict[str, Any] =
391
399
 
392
400
  return str(processed_issues)
393
401
 
394
- class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
402
+ class JiraApiWrapper(NonCodeIndexerToolkit):
395
403
  base_url: str
396
404
  api_version: Optional[str] = "2",
397
405
  api_key: Optional[SecretStr] = None,
@@ -436,50 +444,72 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
436
444
  cls._client = Jira(url=url, token=token, cloud=cloud, verify_ssl=values['verify_ssl'], api_version=api_version)
437
445
  else:
438
446
  cls._client = Jira(url=url, username=username, password=api_key, cloud=cloud, verify_ssl=values['verify_ssl'], api_version=api_version)
439
- custom_headers = values.get('custom_headers', {})
447
+ custom_headers = values.get('custom_headers') or {}
440
448
  logger.info(f"Jira tool: custom headers length: {len(custom_headers)}")
441
449
  for header, value in custom_headers.items():
442
450
  cls._client._update_header(header, value)
443
451
 
444
452
  cls.llm=values.get('llm')
445
- return values
453
+ return super().validate_toolkit(values)
446
454
 
447
455
  def _parse_issues(self, issues: Dict) -> List[dict]:
448
- parsed = []
449
- for issue in issues["issues"]:
450
- if len(parsed) >= self.limit:
456
+ parsed: List[dict] = []
457
+ issues_list = issues.get("issues") if isinstance(issues, dict) else None
458
+ if not isinstance(issues_list, list):
459
+ return parsed
460
+
461
+ for issue in issues_list:
462
+ if self.limit and len(parsed) >= self.limit:
451
463
  break
452
- issue_fields = issue["fields"]
453
- key = issue["key"]
454
- id = issue["id"]
455
- summary = issue_fields["summary"]
456
- description = issue_fields["description"]
457
- created = issue_fields["created"][0:10]
458
- updated = issue_fields["updated"]
459
- duedate = issue_fields["duedate"]
460
- priority = issue_fields["priority"]["name"]
461
- status = issue_fields["status"]["name"]
462
- project_id = issue_fields["project"]["id"]
463
- issue_url = f"{self._client.url}browse/{key}"
464
- try:
465
- assignee = issue_fields["assignee"]["displayName"]
466
- except Exception:
467
- assignee = "None"
464
+
465
+ issue_fields = issue.get("fields") or {}
466
+ key = issue.get("key", "")
467
+ issue_id = issue.get("id", "")
468
+
469
+ summary = issue_fields.get("summary") or ""
470
+ description = issue_fields.get("description") or ""
471
+ created_raw = issue_fields.get("created") or ""
472
+ created = created_raw[:10] if created_raw else ""
473
+ updated = issue_fields.get("updated") or ""
474
+ duedate = issue_fields.get("duedate")
475
+
476
+ priority_info = issue_fields.get("priority") or {}
477
+ priority = priority_info.get("name") or "None"
478
+
479
+ status_info = issue_fields.get("status") or {}
480
+ status = status_info.get("name") or "Unknown"
481
+
482
+ project_info = issue_fields.get("project") or {}
483
+ project_id = project_info.get("id") or ""
484
+
485
+ issue_url = f"{self._client.url}browse/{key}" if key else self._client.url
486
+
487
+ assignee_info = issue_fields.get("assignee") or {}
488
+ assignee = assignee_info.get("displayName") or "None"
489
+
468
490
  rel_issues = {}
469
- for related_issue in issue_fields["issuelinks"]:
470
- if "inwardIssue" in related_issue.keys():
471
- rel_type = related_issue["type"]["inward"]
472
- rel_key = related_issue["inwardIssue"]["key"]
491
+ for related_issue in issue_fields.get("issuelinks") or []:
492
+ rel_type = None
493
+ rel_key = None
494
+ if related_issue.get("inwardIssue"):
495
+ rel_type = related_issue.get("type", {}).get("inward")
496
+ rel_key = related_issue["inwardIssue"].get("key")
473
497
  # rel_summary = related_issue["inwardIssue"]["fields"]["summary"]
474
- if "outwardIssue" in related_issue.keys():
475
- rel_type = related_issue["type"]["outward"]
476
- rel_key = related_issue["outwardIssue"]["key"]
498
+ elif related_issue.get("outwardIssue"):
499
+ rel_type = related_issue.get("type", {}).get("outward")
500
+ rel_key = related_issue["outwardIssue"].get("key")
477
501
  # rel_summary = related_issue["outwardIssue"]["fields"]["summary"]
478
- rel_issues = {"type": rel_type, "key": rel_key, "url": f"{self._client.url}browse/{rel_key}"}
502
+
503
+ if rel_type and rel_key:
504
+ rel_issues = {
505
+ "type": rel_type,
506
+ "key": rel_key,
507
+ "url": f"{self._client.url}browse/{rel_key}",
508
+ }
479
509
 
480
510
  parsed_issue = {
481
511
  "key": key,
482
- "id": id,
512
+ "id": issue_id,
483
513
  "projectId": project_id,
484
514
  "summary": summary,
485
515
  "description": description,
@@ -492,10 +522,13 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
492
522
  "url": issue_url,
493
523
  "related_issues": rel_issues,
494
524
  }
495
- for field in self.additional_fields:
496
- field_value = issue_fields.get(field, None)
525
+
526
+ for field in (self.additional_fields or []):
527
+ field_value = issue_fields.get(field)
497
528
  parsed_issue[field] = field_value
529
+
498
530
  parsed.append(parsed_issue)
531
+
499
532
  return parsed
500
533
 
501
534
  @staticmethod
@@ -555,12 +588,14 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
555
588
  Use the appropriate issue link type (e.g., "Test", "Relates", "Blocks").
556
589
  If we use "Test" linktype, the test is inward issue, the story/other issue is outward issue.."""
557
590
 
591
+ comment = f"Issue {inward_issue_key} was linked to {outward_issue_key}."
592
+ comment_body = {"content": [{"content": [{"text": comment,"type": "text"}],"type": "paragraph"}],"type": "doc","version": 1} if self.api_version == "3" else comment
558
593
  link_data = {
559
594
  "type": {"name": f"{linktype}"},
560
595
  "inwardIssue": {"key": f"{inward_issue_key}"},
561
596
  "outwardIssue": {"key": f"{outward_issue_key}"},
562
597
  "comment": {
563
- "body": "This test is linked to the story."
598
+ "body": comment_body
564
599
  }
565
600
  }
566
601
  self._client.create_issue_link(link_data)
@@ -698,6 +733,8 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
698
733
  def add_comments(self, issue_key: str, comment: str):
699
734
  """ Add a comment to a Jira issue."""
700
735
  try:
736
+ if self.api_version == '3':
737
+ comment = {"content": [{"content": [{"text": comment,"type": "text"}],"type": "paragraph"}],"type": "doc","version": 1}
701
738
  self._client.issue_add_comment(issue_key, comment)
702
739
  issue_url = f"{self._client.url}browse/{issue_key}"
703
740
  output = f"Done. Comment is added for issue {issue_key}. You can view it at {issue_url}"
@@ -721,22 +758,48 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
721
758
  return parsed_projects_str
722
759
  except Exception:
723
760
  stacktrace = format_exc()
724
- logger.error(f"Error creating Jira issue: {stacktrace}")
725
- return ToolException(f"Error creating Jira issue: {stacktrace}")
726
-
727
- def get_attachments_content(self, jira_issue_key: str):
728
- """ Extract content of all attachments related to specified Jira issue key.
729
- NOTE: only parsable attachments will be considered """
761
+ logger.error(f"Error listing Jira projects: {stacktrace}")
762
+ return ToolException(f"Error listing Jira projects: {stacktrace}")
763
+
764
+ def get_attachments_content(self, jira_issue_key: str, attachment_pattern: Optional[str] = None):
765
+ """ Extract the content of all attachments related to a specified Jira issue key.
766
+ NOTE: only parsable attachments will be considered
767
+ Args:
768
+ jira_issue_key: The key of the Jira issue, e.g. "TEST-123
769
+ attachment_pattern: Optional regex pattern to filter attachments by filename.
770
+ If provided, only attachments with filenames matching this pattern will be processed.
771
+ Returns:
772
+ A string containing the content of all relevant attachments, separated by double newlines.
773
+ """
730
774
 
731
775
  attachment_data = []
732
776
  attachments = self._client.get_attachments_ids_from_issue(issue=jira_issue_key)
777
+ api_version = str(getattr(self._client, "api_version", "2"))
733
778
  for attachment in attachments:
734
- if self.api_version == "3":
735
- attachment_data.append(self._client.get_attachment_content(attachment['attachment_id']))
736
- else:
737
- extracted_attachment = self._client.get_attachment(attachment_id=attachment['attachment_id'])
738
- if extracted_attachment['mimeType'] in SUPPORTED_ATTACHMENT_MIME_TYPES:
739
- attachment_data.append(self._extract_attachment_content(extracted_attachment))
779
+ if attachment_pattern and not re.search(attachment_pattern, attachment['filename']):
780
+ logger.info(f"Skipping attachment {attachment['filename']} as it does not match pattern {attachment_pattern}")
781
+ continue
782
+ logger.info(f"Processing attachment {attachment['filename']} with ID {attachment['attachment_id']}")
783
+ try:
784
+ attachment_content = None
785
+
786
+ # Cloud (REST v3) attachments require signed URLs returned from metadata
787
+ if api_version in {"3", "latest"} or self.cloud:
788
+ attachment_content = self._download_attachment_v3(
789
+ attachment['attachment_id'],
790
+ attachment['filename']
791
+ )
792
+
793
+ if attachment_content is None:
794
+ attachment_content = self._client.get_attachment_content(attachment['attachment_id'])
795
+ except Exception as e:
796
+ logger.error(
797
+ f"Failed to download attachment {attachment['filename']} for issue {jira_issue_key}: {str(e)}")
798
+ attachment_content = self._client.get(
799
+ path=f"secure/attachment/{attachment['attachment_id']}/{attachment['filename']}", not_json_response=True)
800
+ content_docs = process_content_by_type(attachment_content, attachment['filename'], llm=self.llm, fallback_extensions=[".txt", ".png"])
801
+ attachment_data.append("filename: " + attachment['filename'] + "\ncontent: " + str([doc.page_content for doc in content_docs]))
802
+
740
803
  return "\n\n".join(attachment_data)
741
804
 
742
805
  def execute_generic_rq(self, method: str, relative_url: str, params: Optional[str] = "", *args):
@@ -770,15 +833,6 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
770
833
  logger.debug(response_string)
771
834
  return response_string
772
835
 
773
- def _extract_attachment_content(self, attachment):
774
- """Extract attachment's content if possible (used for api v.2)"""
775
-
776
- try:
777
- content = self._client.get(attachment['content'].replace(self.base_url, ''))
778
- except Exception as e:
779
- content = f"Unable to parse content of '{attachment['filename']}' due to: {str(e)}"
780
- return f"filename: {attachment['filename']}\ncontent: {content}"
781
-
782
836
  # Helper functions for image processing
783
837
  @staticmethod
784
838
  def _collect_context_for_image(content: str, image_marker: str, context_radius: int = 500) -> str:
@@ -1011,13 +1065,65 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1011
1065
  logger.error(f"Error downloading attachment: {str(e)}")
1012
1066
  return None
1013
1067
 
1068
+ def _download_attachment_v3(self, attachment_id: str, filename: str | None = None) -> Optional[bytes]:
1069
+ """Download Jira attachment using metadata content URL (required for REST v3 / Cloud)."""
1070
+ try:
1071
+ metadata = self._client.get_attachment(attachment_id)
1072
+ except Exception as e:
1073
+ logger.error(f"Failed to retrieve metadata for attachment {attachment_id}: {str(e)}")
1074
+ return None
1075
+
1076
+ download_url = metadata.get('content') or metadata.get('_links', {}).get('content')
1077
+
1078
+ if not download_url:
1079
+ logger.warning(
1080
+ f"Attachment {attachment_id} ({filename}) metadata does not include a content URL; falling back.")
1081
+ return None
1082
+
1083
+ logger.info(f"Downloading attachment {attachment_id} via metadata content URL (v3).")
1084
+ content = self._download_attachment(download_url)
1085
+
1086
+ if content is None:
1087
+ logger.error(
1088
+ f"Failed to download attachment {attachment_id} ({filename}) from v3 content URL: {download_url}")
1089
+
1090
+ return content
1091
+
1014
1092
  def _extract_image_data(self, field_data):
1015
1093
  """
1016
- Extracts image data from general JSON response
1094
+ Extracts image data from general JSON response.
1095
+ Handles lists, dicts with image info, and plain strings.
1017
1096
  """
1018
- if isinstance(field_data, dict) and 'filename' in field_data and 'content' in field_data:
1019
- return f"!{field_data['filename']}|alt={field_data['filename']}!"
1020
- return str(field_data)
1097
+ if isinstance(field_data, list):
1098
+ return ' '.join(self._extract_image_data(item) for item in field_data)
1099
+ if isinstance(field_data, dict):
1100
+ if 'filename' in field_data and 'content' in field_data:
1101
+ return f"!{field_data['filename']}|alt={field_data['filename']}!"
1102
+ if 'content' in field_data and isinstance(field_data['content'], list):
1103
+ result = []
1104
+ for content_item in field_data['content']:
1105
+ if (
1106
+ isinstance(content_item, dict)
1107
+ and 'content' in content_item
1108
+ and isinstance(content_item['content'], list)
1109
+ and content_item['content']
1110
+ ):
1111
+ if content_item.get('type') == 'mediaSingle':
1112
+ media = content_item['content'][0]
1113
+ attrs = media.get('attrs', {})
1114
+ if attrs.get('type') == 'file':
1115
+ alt = attrs.get('alt', '')
1116
+ image_str = f'!{alt}|alt="{alt}"!'
1117
+ result.append(image_str)
1118
+ elif content_item.get('type') == 'paragraph':
1119
+ result.append(content_item['content'][0].get('text', ''))
1120
+ else:
1121
+ result.append(self._extract_image_data(content_item))
1122
+ return '\n'.join(result)
1123
+ return f"Unsupported format of field content."
1124
+ if isinstance(field_data, str):
1125
+ return field_data
1126
+ return f"Unsupported field content type: {type(field_data)}. Expected a string, list, or dict."
1021
1127
 
1022
1128
  def get_field_with_image_descriptions(self, jira_issue_key: str, field_name: str, prompt: Optional[str] = None,
1023
1129
  context_radius: int = 500):
@@ -1052,12 +1158,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1052
1158
  return f"Unable to find field '{field_name}' or it's empty. Available fields are: {existing_fields_str}"
1053
1159
 
1054
1160
  # Handle multiple images or non-string content
1055
- if isinstance(field_content, list):
1056
- field_content = ' '.join(map(self._extract_image_data, field_content))
1057
- elif isinstance(field_content, dict):
1058
- field_content = self._extract_image_data(field_content)
1059
- elif not isinstance(field_content, str):
1060
- return f"Unsupported field content type: {type(field_content)}. Expected a string, list, or dict."
1161
+ field_content = self._extract_image_data(field_content)
1061
1162
 
1062
1163
  # Regular expression to find image references in Jira markup
1063
1164
  image_pattern = r'!([^!|]+)(?:\|[^!]*)?!'
@@ -1118,6 +1219,97 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1118
1219
  logger.error(f"Error processing field with images: {stacktrace}")
1119
1220
  return f"Error processing field with images: {str(e)}"
1120
1221
 
1222
+ def process_image_match(self, match, body, attachment_resolver, context_radius=500, prompt=None):
1223
+ """Process each image reference and get its contextual description"""
1224
+ image_ref = match.group(1)
1225
+ full_match = match.group(0) # The complete image reference with markers
1226
+
1227
+ logger.info(f"Processing image reference: {image_ref} (full match: {full_match})")
1228
+
1229
+ try:
1230
+ # Use the AttachmentResolver to find the attachment
1231
+ attachment = attachment_resolver.find_attachment(image_ref)
1232
+
1233
+ if not attachment:
1234
+ logger.warning(f"Could not find attachment for reference: {image_ref}")
1235
+ if image_ref.startswith("http://") or image_ref.startswith("https://"):
1236
+ content_url = image_ref
1237
+ image_name = image_ref.split("/")[-1] # Extract the name from the URL
1238
+ response = requests.get(content_url, timeout=10)
1239
+ response.raise_for_status()
1240
+ image_data = response.content
1241
+ else:
1242
+ logger.error(f"Invalid image reference: {image_ref}")
1243
+ return f"[Image: {image_ref} - attachment not found]"
1244
+ else:
1245
+ # Get the content URL and download the image
1246
+ content_url = attachment.get('content')
1247
+ if not content_url:
1248
+ logger.error(f"No content URL found in attachment: {attachment}")
1249
+ return f"[Image: {image_ref} - no content URL]"
1250
+
1251
+ image_name = attachment.get('filename', image_ref)
1252
+
1253
+ # Download the image data
1254
+ logger.info(f"Downloading image from URL: {content_url}")
1255
+ image_data = self._download_attachment(content_url)
1256
+
1257
+ if not image_data:
1258
+ logger.error(f"Failed to download image from URL: {content_url}")
1259
+ return f"[Image: {image_ref} - download failed]"
1260
+
1261
+ # Collect surrounding content
1262
+ context_text = self._collect_context_for_image(body, full_match, context_radius)
1263
+
1264
+ # Process with LLM (will use cache if available)
1265
+ description = self._process_image_with_llm(image_data, image_name, context_text, prompt)
1266
+ return f"[Image {image_name} Description: {description}]"
1267
+
1268
+ except Exception as e:
1269
+ logger.error(f"Error retrieving attachment {image_ref}: {str(e)}")
1270
+ return f"[Image: {image_ref} - Error: {str(e)}]"
1271
+
1272
+ def get_processed_comments_list_with_image_description(self, jira_issue_key: str, prompt: Optional[str] = None, context_radius: int = 500):
1273
+ # Retrieve all comments for the issue
1274
+ comments = self._client.issue_get_comments(jira_issue_key)
1275
+
1276
+ if not comments or not comments.get('comments'):
1277
+ return []
1278
+
1279
+ processed_comments = []
1280
+
1281
+ # Create an AttachmentResolver to efficiently handle attachment lookups
1282
+ attachment_resolver = AttachmentResolver(self._client, jira_issue_key)
1283
+
1284
+ # Regular expression to find image references in Jira markup
1285
+ image_pattern = r'!([^!|]+)(?:\|[^!]*)?!'
1286
+
1287
+ # Process each comment
1288
+ for comment in comments['comments']:
1289
+ comment_body = comment.get('body', '')
1290
+ if not comment_body:
1291
+ continue
1292
+
1293
+ comment_author = comment.get('author', {}).get('displayName', 'Unknown')
1294
+ comment_created = comment.get('created', 'Unknown date')
1295
+ comment_body = self._extract_image_data(comment_body)
1296
+
1297
+ # Process the comment body by replacing image references with descriptions
1298
+ processed_body = re.sub(image_pattern,
1299
+ lambda match: self.process_image_match(match, comment_body, attachment_resolver, context_radius, prompt),
1300
+ comment_body)
1301
+
1302
+ # Add the processed comment to our results
1303
+ processed_comments.append({
1304
+ "author": comment_author,
1305
+ "created": comment_created,
1306
+ "id": comment.get('id'),
1307
+ "original_content": comment_body,
1308
+ "processed_content": processed_body
1309
+ })
1310
+
1311
+ return processed_comments
1312
+
1121
1313
  def get_comments_with_image_descriptions(self, jira_issue_key: str, prompt: Optional[str] = None, context_radius: int = 500):
1122
1314
  """
1123
1315
  Get all comments from Jira issue and augment any images in them with textual descriptions.
@@ -1137,84 +1329,11 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1137
1329
  The comments with image references replaced with contextual descriptions
1138
1330
  """
1139
1331
  try:
1140
- # Retrieve all comments for the issue
1141
- comments = self._client.issue_get_comments(jira_issue_key)
1142
-
1143
- if not comments or not comments.get('comments'):
1332
+ processed_comments = self.get_processed_comments_list_with_image_description(jira_issue_key=jira_issue_key,
1333
+ prompt=prompt,
1334
+ context_radius=context_radius)
1335
+ if not processed_comments:
1144
1336
  return f"No comments found for issue '{jira_issue_key}'"
1145
-
1146
- processed_comments = []
1147
-
1148
- # Create an AttachmentResolver to efficiently handle attachment lookups
1149
- attachment_resolver = AttachmentResolver(self._client, jira_issue_key)
1150
-
1151
- # Regular expression to find image references in Jira markup
1152
- image_pattern = r'!([^!|]+)(?:\|[^!]*)?!'
1153
-
1154
- # Process each comment
1155
- for comment in comments['comments']:
1156
- comment_body = comment.get('body', '')
1157
- if not comment_body:
1158
- continue
1159
-
1160
- comment_author = comment.get('author', {}).get('displayName', 'Unknown')
1161
- comment_created = comment.get('created', 'Unknown date')
1162
-
1163
- # Function to process images in comment text
1164
- def process_image_match(match):
1165
- """Process each image reference and get its contextual description"""
1166
- image_ref = match.group(1)
1167
- full_match = match.group(0) # The complete image reference with markers
1168
-
1169
- logger.info(f"Processing image reference: {image_ref} (full match: {full_match})")
1170
-
1171
- try:
1172
- # Use the AttachmentResolver to find the attachment
1173
- attachment = attachment_resolver.find_attachment(image_ref)
1174
-
1175
- if not attachment:
1176
- logger.warning(f"Could not find attachment for reference: {image_ref}")
1177
- return f"[Image: {image_ref} - attachment not found]"
1178
-
1179
- # Get the content URL and download the image
1180
- content_url = attachment.get('content')
1181
- if not content_url:
1182
- logger.error(f"No content URL found in attachment: {attachment}")
1183
- return f"[Image: {image_ref} - no content URL]"
1184
-
1185
- image_name = attachment.get('filename', image_ref)
1186
-
1187
- # Collect surrounding content
1188
- context_text = self._collect_context_for_image(comment_body, full_match, context_radius)
1189
-
1190
- # Download the image data
1191
- logger.info(f"Downloading image from URL: {content_url}")
1192
- image_data = self._download_attachment(content_url)
1193
-
1194
- if not image_data:
1195
- logger.error(f"Failed to download image from URL: {content_url}")
1196
- return f"[Image: {image_ref} - download failed]"
1197
-
1198
- # Process with LLM (will use cache if available)
1199
- description = self._process_image_with_llm(image_data, image_name, context_text, prompt)
1200
- return f"[Image {image_name} Description: {description}]"
1201
-
1202
- except Exception as e:
1203
- logger.error(f"Error retrieving attachment {image_ref}: {str(e)}")
1204
- return f"[Image: {image_ref} - Error: {str(e)}]"
1205
-
1206
- # Process the comment body by replacing image references with descriptions
1207
- processed_body = re.sub(image_pattern, process_image_match, comment_body)
1208
-
1209
- # Add the processed comment to our results
1210
- processed_comments.append({
1211
- "author": comment_author,
1212
- "created": comment_created,
1213
- "id": comment.get('id'),
1214
- "original_content": comment_body,
1215
- "processed_content": processed_body
1216
- })
1217
-
1218
1337
  # Format the output
1219
1338
  result = f"Comments from issue '{jira_issue_key}' with image descriptions:\n\n"
1220
1339
  for idx, comment in enumerate(processed_comments, 1):
@@ -1243,6 +1362,8 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1243
1362
  self._skipped_attachment_extensions = kwargs.get('skip_attachment_extensions', [])
1244
1363
  self._include_attachments = kwargs.get('include_attachments', False)
1245
1364
  self._included_fields = fields_to_extract.copy() if fields_to_extract else []
1365
+ self._include_comments = kwargs.get('include_comments', True)
1366
+ self._chunking_tool = kwargs.get('chunking_tool', None)
1246
1367
 
1247
1368
  try:
1248
1369
  # Prepare fields to extract
@@ -1257,7 +1378,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1257
1378
 
1258
1379
  # Use provided JQL query or default to all issues
1259
1380
  if not jql:
1260
- jql_query = "ORDER BY updated DESC" # Default to get all issues ordered by update time
1381
+ jql_query = "created >= \"1970-01-01\" ORDER BY updated DESC" # Default to get all issues ordered by update time
1261
1382
  else:
1262
1383
  jql_query = jql
1263
1384
 
@@ -1285,6 +1406,19 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1285
1406
  logger.error(f"Error loading Jira issues: {str(e)}")
1286
1407
  raise ToolException(f"Unable to load Jira issues: {str(e)}")
1287
1408
 
1409
+ def _extend_data(self, documents: Generator[Document, None, None]):
1410
+ image_pattern = r'!([^!|]+)(?:\|[^!]*)?!'
1411
+ for doc in documents:
1412
+ attachment_resolver = AttachmentResolver(self._client, doc.metadata['issue_key'])
1413
+ processed_content = re.sub(image_pattern,
1414
+ lambda match: self.process_image_match(match,
1415
+ doc.page_content,
1416
+ attachment_resolver),
1417
+ doc.page_content)
1418
+ doc.metadata[IndexerKeywords.CONTENT_IN_BYTES.value] = processed_content.encode('utf-8')
1419
+ doc.metadata[IndexerKeywords.CONTENT_FILE_NAME.value] = f"base_doc{file_extension_by_chunker(self._chunking_tool)}"
1420
+ yield doc
1421
+
1288
1422
  def _process_document(self, base_document: Document) -> Generator[Document, None, None]:
1289
1423
  """
1290
1424
  Process a base document to extract and index Jira issues extra fields: comments, attachments, etc..
@@ -1306,21 +1440,36 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1306
1440
  except Exception as e:
1307
1441
  logger.error(f"Failed to download attachment {attachment['filename']} for issue {issue_key}: {str(e)}")
1308
1442
  attachment_content = self._client.get(path=f"secure/attachment/{attachment['id']}/{attachment['filename']}", not_json_response=True)
1309
- content = load_content_from_bytes(attachment_content, ext, llm=self.llm) if ext not in '.pdf' \
1310
- else parse_file_content(file_content=attachment_content, file_name=attachment['filename'], llm=self.llm, is_capture_image=True)
1311
- if not content:
1312
- continue
1313
- yield Document(page_content=content,
1443
+
1444
+ yield Document(page_content='',
1445
+ metadata={
1446
+ IndexerKeywords.CONTENT_IN_BYTES.value: attachment_content,
1447
+ IndexerKeywords.CONTENT_FILE_NAME.value: attachment['filename'],
1448
+ 'id': attachment_id,
1449
+ 'issue_key': issue_key,
1450
+ 'source': f"{self.base_url}/browse/{issue_key}",
1451
+ 'filename': attachment['filename'],
1452
+ 'created': attachment['created'],
1453
+ 'mimeType': attachment['mimeType'],
1454
+ 'author': attachment.get('author', {}).get('name'),
1455
+ IndexerKeywords.PARENT.value: base_document.metadata.get('id', None),
1456
+ 'type': 'attachment',
1457
+ })
1458
+ if self._include_comments:
1459
+ comments = self.get_processed_comments_list_with_image_description(issue_key)
1460
+ if comments:
1461
+ for comment in comments:
1462
+ yield Document(page_content='',
1314
1463
  metadata={
1315
- 'id': attachment_id,
1464
+ IndexerKeywords.CONTENT_IN_BYTES.value: comment.get('processed_content').encode('utf-8'),
1465
+ IndexerKeywords.CONTENT_FILE_NAME.value: "comment.md",
1466
+ 'id': comment.get('id'),
1316
1467
  'issue_key': issue_key,
1317
1468
  'source': f"{self.base_url}/browse/{issue_key}",
1318
- 'filename': attachment['filename'],
1319
- 'created': attachment['created'],
1320
- 'mimeType': attachment['mimeType'],
1321
- 'author': attachment.get('author', {}).get('name'),
1469
+ 'created': comment.get('created'),
1470
+ 'author': comment.get('author'),
1322
1471
  IndexerKeywords.PARENT.value: base_document.metadata.get('id', None),
1323
- 'type': 'attachment',
1472
+ 'type': 'comment',
1324
1473
  })
1325
1474
 
1326
1475
  def _jql_get_tickets(self, jql, fields="*all", start=0, limit=None, expand=None, validate_query=None):
@@ -1343,7 +1492,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1343
1492
  if validate_query is not None:
1344
1493
  params["validateQuery"] = validate_query
1345
1494
 
1346
- url = self._client.resource_url("search")
1495
+ url = self._client.resource_url("search/jql" if self.api_version == '3' else "search")
1347
1496
 
1348
1497
  while True:
1349
1498
  params["startAt"] = int(start)
@@ -1361,6 +1510,8 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1361
1510
  break
1362
1511
  if not response["issues"]:
1363
1512
  break
1513
+ if len(issues) < limit:
1514
+ break
1364
1515
  start += len(issues)
1365
1516
 
1366
1517
  def _process_issue_for_indexing(self, issue: dict, fields_to_index=None) -> Document:
@@ -1370,21 +1521,16 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1370
1521
  """
1371
1522
  try:
1372
1523
  # Build content starting with summary
1373
- content = f"{issue['fields']['summary']}\n"
1524
+ content = f"# Summary\n{issue['fields']['summary']}\n\n"
1374
1525
 
1375
1526
  # Add description if present
1376
1527
  description = issue['fields'].get('description', '')
1377
1528
  if description:
1378
- content += f"{description}\n"
1529
+ content += f"# Description\n{description}\n\n"
1379
1530
  else:
1380
1531
  # If no description, still create document but with minimal content
1381
1532
  logger.debug(f"Issue {issue.get('key', 'unknown')} has no description")
1382
1533
 
1383
- # Add comments if present
1384
- if 'comment' in issue['fields'] and issue['fields']['comment'].get('comments'):
1385
- for comment in issue['fields']['comment']['comments']:
1386
- content += f"{comment['body']}\n"
1387
-
1388
1534
  # Add additional fields to index
1389
1535
  if fields_to_index:
1390
1536
  for field in fields_to_index:
@@ -1395,7 +1541,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1395
1541
  field_value = str(field_value)
1396
1542
  elif isinstance(field_value, list):
1397
1543
  field_value = ', '.join(str(item) for item in field_value)
1398
- content += f"{field_value}\n"
1544
+ content += f"# {field}\n{field_value}\n\n"
1399
1545
 
1400
1546
  # Create metadata
1401
1547
  metadata = {
@@ -1433,6 +1579,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1433
1579
  'skip_attachment_extensions': (Optional[List[str]], Field(
1434
1580
  description="List of file extensions to skip when processing attachments: i.e. ['.png', '.jpg']",
1435
1581
  default=[])),
1582
+ 'chunking_tool': (Literal['markdown', ''], Field(description="Name of chunking tool for base document", default='markdown')),
1436
1583
  }
1437
1584
 
1438
1585
  # def index_data(self,
@@ -1505,7 +1652,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1505
1652
  # logger.error(f"Error indexing Jira issues: {str(e)}")
1506
1653
  # raise ToolException(f"Error indexing Jira issues: {str(e)}")
1507
1654
 
1508
- @extend_with_vector_tools
1655
+ @extend_with_parent_available_tools
1509
1656
  def get_available_tools(self):
1510
1657
  return [
1511
1658
  {
@@ -1589,7 +1736,7 @@ class JiraApiWrapper(BaseVectorStoreToolApiWrapper):
1589
1736
  {
1590
1737
  "name": "get_attachments_content",
1591
1738
  "description": self.get_attachments_content.__doc__,
1592
- "args_schema": GetRemoteLinks,
1739
+ "args_schema": GetIssueAttachments,
1593
1740
  "ref": self.get_attachments_content,
1594
1741
  },
1595
1742
  {