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
@@ -13,6 +13,7 @@ from langchain_core.messages import (
13
13
  from langchain_core.tools import ToolException
14
14
  from langgraph.store.base import BaseStore
15
15
  from langchain_openai import OpenAIEmbeddings, ChatOpenAI
16
+ from langchain_anthropic import ChatAnthropic
16
17
 
17
18
  from ..langchain.assistant import Assistant as LangChainAssistant
18
19
  # from ..llamaindex.assistant import Assistant as LLamaAssistant
@@ -20,8 +21,9 @@ from .prompt import AlitaPrompt
20
21
  from .datasource import AlitaDataSource
21
22
  from .artifact import Artifact
22
23
  from ..langchain.chat_message_template import Jinja2TemplatedChatMessagesTemplate
23
- from ..utils.utils import TOOLKIT_SPLITTER
24
- from ...tools import get_available_toolkit_models
24
+ from ..utils.mcp_oauth import McpAuthorizationRequired
25
+ from ...tools import get_available_toolkit_models, instantiate_toolkit
26
+ from ...tools.base_indexer_toolkit import IndexTools
25
27
 
26
28
  logger = logging.getLogger(__name__)
27
29
 
@@ -42,6 +44,7 @@ class AlitaClient:
42
44
  self.base_url = base_url.rstrip('/')
43
45
  self.api_path = '/api/v1'
44
46
  self.llm_path = '/llm/v1'
47
+ self.allm_path = '/llm'
45
48
  self.project_id = project_id
46
49
  self.auth_token = auth_token
47
50
  self.headers = {
@@ -68,10 +71,15 @@ class AlitaClient:
68
71
  self.bucket_url = f"{self.base_url}{self.api_path}/artifacts/buckets/{self.project_id}"
69
72
  self.configurations_url = f'{self.base_url}{self.api_path}/integrations/integrations/default/{self.project_id}?section=configurations&unsecret=true'
70
73
  self.ai_section_url = f'{self.base_url}{self.api_path}/integrations/integrations/default/{self.project_id}?section=ai'
74
+ self.models_url = f'{self.base_url}{self.api_path}/configurations/models/{self.project_id}?include_shared=true'
71
75
  self.image_generation_url = f"{self.base_url}{self.llm_path}/images/generations"
72
76
  self.configurations: list = configurations or []
73
77
  self.model_timeout = kwargs.get('model_timeout', 120)
74
78
  self.model_image_generation = kwargs.get('model_image_generation')
79
+
80
+ # Cache for generated images to avoid token consumption
81
+ # This is used by image_generation and artifact toolkits to pass data via reference
82
+ self._generated_images_cache: Dict[str, Dict[str, Any]] = {}
75
83
 
76
84
  def get_mcp_toolkits(self):
77
85
  if user_id := self._get_real_user_id():
@@ -143,6 +151,19 @@ class AlitaClient:
143
151
  data = requests.get(url, headers=self.headers, verify=False).json()
144
152
  return data
145
153
 
154
+ def toolkit(self, toolkit_id: int):
155
+ url = f"{self.base_url}{self.api_path}/tool/prompt_lib/{self.project_id}/{toolkit_id}"
156
+ response = requests.get(url, headers=self.headers, verify=False)
157
+ if not response.ok:
158
+ raise ValueError(f"Failed to fetch toolkit {toolkit_id}: {response.text}")
159
+
160
+ tool_data = response.json()
161
+ if 'settings' not in tool_data:
162
+ tool_data['settings'] = {}
163
+ tool_data['settings']['alita'] = self
164
+
165
+ return instantiate_toolkit(tool_data)
166
+
146
167
  def get_list_of_apps(self):
147
168
  apps = []
148
169
  limit = 10
@@ -175,6 +196,20 @@ class AlitaClient:
175
196
  return resp.json()
176
197
  return []
177
198
 
199
+ def get_available_models(self):
200
+ """Get list of available models from the configurations API.
201
+
202
+ Returns:
203
+ List of model dictionaries with 'name' and other properties,
204
+ or empty list if request fails.
205
+ """
206
+ resp = requests.get(self.models_url, headers=self.headers, verify=False)
207
+ if resp.ok:
208
+ data = resp.json()
209
+ # API returns {"items": [...], ...}
210
+ return data.get('items', [])
211
+ return []
212
+
178
213
  def get_embeddings(self, embedding_model: str) -> OpenAIEmbeddings:
179
214
  """
180
215
  Get an instance of OpenAIEmbeddings configured with the project ID and auth token.
@@ -190,35 +225,104 @@ class AlitaClient:
190
225
  request_timeout=self.model_timeout
191
226
  )
192
227
 
193
- def get_llm(self, model_name: str, model_config: dict) -> ChatOpenAI:
228
+ def get_llm(self, model_name: str, model_config: dict):
194
229
  """
195
- Get a ChatOpenAI model instance based on the model name and configuration.
230
+ Get a ChatOpenAI or ChatAnthropic model instance based on the model name and configuration.
196
231
 
197
232
  Args:
198
233
  model_name: Name of the model to retrieve
199
234
  model_config: Configuration parameters for the model
200
235
 
201
236
  Returns:
202
- An instance of ChatOpenAI configured with the provided parameters.
237
+ An instance of ChatOpenAI or ChatAnthropic configured with the provided parameters.
203
238
  """
204
239
  if not model_name:
205
240
  raise ValueError("Model name must be provided")
206
241
 
207
- logger.info(f"Creating ChatOpenAI model: {model_name} with config: {model_config}")
242
+ # Determine if this is an Anthropic model
243
+ model_name_lower = model_name.lower()
244
+ is_anthropic = "anthropic" in model_name_lower or "claude" in model_name_lower
208
245
 
209
- return ChatOpenAI(
210
- base_url=f"{self.base_url}{self.llm_path}",
211
- model=model_name,
212
- api_key=self.auth_token,
213
- streaming=model_config.get("streaming", True),
214
- stream_usage=model_config.get("stream_usage", True),
215
- max_tokens=model_config.get("max_tokens", None),
216
- temperature=model_config.get("temperature"),
217
- max_retries=model_config.get("max_retries", 3),
218
- seed=model_config.get("seed", None),
219
- openai_organization=str(self.project_id),
220
- )
246
+ logger.info(f"Creating {'ChatAnthropic' if is_anthropic else 'ChatOpenAI'} model: {model_name} with config: {model_config}")
221
247
 
248
+ try:
249
+ from tools import this # pylint: disable=E0401,C0415
250
+ worker_config = this.for_module("indexer_worker").descriptor.config
251
+ except: # pylint: disable=W0702
252
+ worker_config = {}
253
+
254
+ use_responses_api = False
255
+
256
+ if worker_config and isinstance(worker_config, dict):
257
+ for target_name_tag in worker_config.get("use_responses_api_for", []):
258
+ if target_name_tag in model_name:
259
+ use_responses_api = True
260
+ break
261
+
262
+ # handle case when max_tokens are auto-configurable == -1 or None
263
+ llm_max_tokens = model_config.get("max_tokens", None)
264
+ if llm_max_tokens is None or llm_max_tokens == -1:
265
+ logger.warning(f'User selected `MAX COMPLETION TOKENS` as `auto` or value is None/missing')
266
+ # default number for a case when auto is selected for an agent
267
+ llm_max_tokens = 4000
268
+
269
+ if is_anthropic:
270
+ # ChatAnthropic configuration
271
+ # Anthropic requires max_tokens to be an integer, never None
272
+ target_kwargs = {
273
+ "base_url": f"{self.base_url}{self.allm_path}",
274
+ "model": model_name,
275
+ "api_key": self.auth_token,
276
+ "streaming": model_config.get("streaming", True),
277
+ "max_tokens": llm_max_tokens, # Always an integer now
278
+ "temperature": model_config.get("temperature"),
279
+ "max_retries": model_config.get("max_retries", 3),
280
+ "default_headers": {"openai-organization": str(self.project_id),
281
+ "Authorization": f"Bearer {self.auth_token}"},
282
+ }
283
+
284
+ # TODO": Check on ChatAnthropic client when they get "effort" support back
285
+ if model_config.get("reasoning_effort"):
286
+ if model_config["reasoning_effort"].lower() == "low":
287
+ target_kwargs['thinking'] = {"type": "enabled", "budget_tokens": 2048}
288
+ target_kwargs['temperature'] = 1
289
+ target_kwargs["max_tokens"] = 2048 + target_kwargs["max_tokens"]
290
+ elif model_config["reasoning_effort"].lower() == "medium":
291
+ target_kwargs['thinking'] = {"type": "enabled", "budget_tokens": 4096}
292
+ target_kwargs['temperature'] = 1
293
+ target_kwargs["max_tokens"] = 4096 + target_kwargs["max_tokens"]
294
+ elif model_config["reasoning_effort"].lower() == "high":
295
+ target_kwargs['thinking'] = {"type": "enabled", "budget_tokens": 9092}
296
+ target_kwargs['temperature'] = 1
297
+ target_kwargs["max_tokens"] = 9092 + target_kwargs["max_tokens"]
298
+
299
+ # Add http_client if provided
300
+ if "http_client" in model_config:
301
+ target_kwargs["http_client"] = model_config["http_client"]
302
+
303
+ llm = ChatAnthropic(**target_kwargs)
304
+ else:
305
+ # ChatOpenAI configuration
306
+ target_kwargs = {
307
+ "base_url": f"{self.base_url}{self.llm_path}",
308
+ "model": model_name,
309
+ "api_key": self.auth_token,
310
+ "streaming": model_config.get("streaming", True),
311
+ "stream_usage": model_config.get("stream_usage", True),
312
+ "max_tokens": llm_max_tokens,
313
+ "temperature": model_config.get("temperature"),
314
+ "reasoning_effort": model_config.get("reasoning_effort"),
315
+ "max_retries": model_config.get("max_retries", 3),
316
+ "seed": model_config.get("seed", None),
317
+ "openai_organization": str(self.project_id),
318
+ }
319
+
320
+ if use_responses_api:
321
+ target_kwargs["use_responses_api"] = True
322
+
323
+ llm = ChatOpenAI(**target_kwargs)
324
+ return llm
325
+
222
326
  def generate_image(self,
223
327
  prompt: str,
224
328
  n: int = 1,
@@ -303,7 +407,9 @@ class AlitaClient:
303
407
  app_type=None, memory=None, runtime='langchain',
304
408
  application_variables: Optional[dict] = None,
305
409
  version_details: Optional[dict] = None, store: Optional[BaseStore] = None,
306
- llm: Optional[ChatOpenAI] = None, mcp_tokens: Optional[dict] = None):
410
+ llm: Optional[ChatOpenAI] = None, mcp_tokens: Optional[dict] = None,
411
+ conversation_id: Optional[str] = None, ignored_mcp_servers: Optional[list] = None,
412
+ is_subgraph: bool = False):
307
413
  if tools is None:
308
414
  tools = []
309
415
  if chat_history is None:
@@ -323,11 +429,15 @@ class AlitaClient:
323
429
  if var['name'] in application_variables:
324
430
  var.update(application_variables[var['name']])
325
431
  if llm is None:
432
+ max_tokens = data['llm_settings'].get('max_tokens', 4000)
433
+ if max_tokens == -1:
434
+ # default nuber for case when auto is selected for agent
435
+ max_tokens = 4000
326
436
  llm = self.get_llm(
327
437
  model_name=data['llm_settings']['model_name'],
328
438
  model_config={
329
- "max_tokens": data['llm_settings']['max_tokens'],
330
- "top_p": data['llm_settings']['top_p'],
439
+ "max_tokens": max_tokens,
440
+ "reasoning_effort": data['llm_settings'].get('reasoning_effort'),
331
441
  "temperature": data['llm_settings']['temperature'],
332
442
  "model_project_id": data['llm_settings'].get('model_project_id'),
333
443
  }
@@ -342,16 +452,20 @@ class AlitaClient:
342
452
  app_type = "react"
343
453
  elif app_type == 'autogen':
344
454
  app_type = "react"
345
-
455
+
346
456
  # LangChainAssistant constructor calls get_tools() which may raise McpAuthorizationRequired
347
457
  # The exception will propagate naturally to the indexer worker's outer handler
348
458
  if runtime == 'nonrunnable':
349
459
  return LangChainAssistant(self, data, llm, chat_history, app_type,
350
- tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens)
460
+ tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens,
461
+ conversation_id=conversation_id, ignored_mcp_servers=ignored_mcp_servers,
462
+ is_subgraph=is_subgraph)
351
463
  if runtime == 'langchain':
352
464
  return LangChainAssistant(self, data, llm,
353
465
  chat_history, app_type,
354
- tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens).runnable()
466
+ tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens,
467
+ conversation_id=conversation_id, ignored_mcp_servers=ignored_mcp_servers,
468
+ is_subgraph=is_subgraph).runnable()
355
469
  elif runtime == 'llama':
356
470
  raise NotImplementedError("LLama runtime is not supported")
357
471
 
@@ -419,11 +533,44 @@ class AlitaClient:
419
533
  return self._process_requst(data)
420
534
 
421
535
  def create_artifact(self, bucket_name, artifact_name, artifact_data):
536
+ # Sanitize filename to prevent regex errors during indexing
537
+ sanitized_name, was_modified = self._sanitize_artifact_name(artifact_name)
538
+ if was_modified:
539
+ logger.warning(f"Artifact filename sanitized: '{artifact_name}' -> '{sanitized_name}'")
540
+
422
541
  url = f'{self.artifacts_url}/{bucket_name.lower()}'
423
542
  data = requests.post(url, headers=self.headers, files={
424
- 'file': (artifact_name, artifact_data)
543
+ 'file': (sanitized_name, artifact_data)
425
544
  }, verify=False)
426
545
  return self._process_requst(data)
546
+
547
+ @staticmethod
548
+ def _sanitize_artifact_name(filename: str) -> tuple:
549
+ """Sanitize filename for safe storage and regex pattern matching."""
550
+ import re
551
+ from pathlib import Path
552
+
553
+ if not filename or not filename.strip():
554
+ return "unnamed_file", True
555
+
556
+ original = filename
557
+ path_obj = Path(filename)
558
+ name = path_obj.stem
559
+ extension = path_obj.suffix
560
+
561
+ # Whitelist: alphanumeric, underscore, hyphen, space, Unicode letters/digits
562
+ sanitized_name = re.sub(r'[^\w\s-]', '', name, flags=re.UNICODE)
563
+ sanitized_name = re.sub(r'[-\s]+', '-', sanitized_name)
564
+ sanitized_name = sanitized_name.strip('-').strip()
565
+
566
+ if not sanitized_name:
567
+ sanitized_name = "file"
568
+
569
+ if extension:
570
+ extension = re.sub(r'[^\w.-]', '', extension, flags=re.UNICODE)
571
+
572
+ sanitized = sanitized_name + extension
573
+ return sanitized, (sanitized != original)
427
574
 
428
575
  def download_artifact(self, bucket_name, artifact_name):
429
576
  url = f'{self.artifact_url}/{bucket_name.lower()}/{artifact_name}'
@@ -572,7 +719,8 @@ class AlitaClient:
572
719
  tools: Optional[list] = None, chat_history: Optional[List[Any]] = None,
573
720
  memory=None, runtime='langchain', variables: Optional[list] = None,
574
721
  store: Optional[BaseStore] = None, debug_mode: Optional[bool] = False,
575
- mcp_tokens: Optional[dict] = None):
722
+ mcp_tokens: Optional[dict] = None, conversation_id: Optional[str] = None,
723
+ ignored_mcp_servers: Optional[list] = None, persona: Optional[str] = "generic"):
576
724
  """
577
725
  Create a predict-type agent with minimal configuration.
578
726
 
@@ -588,6 +736,8 @@ class AlitaClient:
588
736
  variables: Optional list of variables for the agent
589
737
  store: Optional store for memory
590
738
  debug_mode: Enable debug mode for cases when assistant can be initialized without tools
739
+ ignored_mcp_servers: Optional list of MCP server URLs to ignore (user chose to continue without auth)
740
+ persona: Default persona for chat: 'generic' or 'qa' (default: 'generic')
591
741
 
592
742
  Returns:
593
743
  Runnable agent ready for execution
@@ -608,7 +758,7 @@ class AlitaClient:
608
758
  'tools': tools, # Tool configs that will be processed by get_tools()
609
759
  'variables': variables
610
760
  }
611
-
761
+
612
762
  # LangChainAssistant constructor calls get_tools() which may raise McpAuthorizationRequired
613
763
  # The exception will propagate naturally to the indexer worker's outer handler
614
764
  return LangChainAssistant(
@@ -620,12 +770,15 @@ class AlitaClient:
620
770
  memory=memory,
621
771
  store=store,
622
772
  debug_mode=debug_mode,
623
- mcp_tokens=mcp_tokens
773
+ mcp_tokens=mcp_tokens,
774
+ conversation_id=conversation_id,
775
+ ignored_mcp_servers=ignored_mcp_servers,
776
+ persona=persona
624
777
  ).runnable()
625
778
 
626
779
  def test_toolkit_tool(self, toolkit_config: dict, tool_name: str, tool_params: dict = None,
627
780
  runtime_config: dict = None, llm_model: str = None,
628
- llm_config: dict = None) -> dict:
781
+ llm_config: dict = None, mcp_tokens: dict = None) -> dict:
629
782
  """
630
783
  Test a single tool from a toolkit with given parameters and runtime callbacks.
631
784
 
@@ -644,6 +797,7 @@ class AlitaClient:
644
797
  - configurable: Additional configuration parameters
645
798
  - tags: Tags for the execution
646
799
  llm_model: Name of the LLM model to use (default: 'gpt-4o-mini')
800
+ mcp_tokens: Optional dictionary of MCP OAuth tokens by server URL
647
801
  llm_config: Configuration for the LLM containing:
648
802
  - max_tokens: Maximum tokens for response (default: 1000)
649
803
  - temperature: Temperature for response generation (default: 0.1)
@@ -691,7 +845,6 @@ class AlitaClient:
691
845
  llm_config = {
692
846
  'max_tokens': 1024,
693
847
  'temperature': 0.1,
694
- 'top_p': 1.0
695
848
  }
696
849
  import logging
697
850
  logger = logging.getLogger(__name__)
@@ -763,12 +916,12 @@ class AlitaClient:
763
916
 
764
917
  # Instantiate the toolkit with client and LLM support
765
918
  try:
766
- tools = instantiate_toolkit_with_client(toolkit_config, llm, self)
767
- except Exception as toolkit_error:
919
+ tools = instantiate_toolkit_with_client(toolkit_config, llm, self, mcp_tokens=mcp_tokens, use_prefix=False)
920
+ except McpAuthorizationRequired:
768
921
  # Re-raise McpAuthorizationRequired to allow proper handling upstream
769
- from ..utils.mcp_oauth import McpAuthorizationRequired
770
- if isinstance(toolkit_error, McpAuthorizationRequired):
771
- raise
922
+ logger.info(f"McpAuthorizationRequired detected, re-raising")
923
+ raise
924
+ except Exception as toolkit_error:
772
925
  # For other errors, return error response
773
926
  return {
774
927
  "success": False,
@@ -854,7 +1007,6 @@ class AlitaClient:
854
1007
  if target_tool is None:
855
1008
  available_tools = []
856
1009
  base_available_tools = []
857
- full_available_tools = []
858
1010
 
859
1011
  for tool in tools:
860
1012
  tool_name_attr = None
@@ -871,16 +1023,14 @@ class AlitaClient:
871
1023
  if base_name not in base_available_tools:
872
1024
  base_available_tools.append(base_name)
873
1025
 
874
- # Track full names separately
875
- if TOOLKIT_SPLITTER in tool_name_attr:
876
- full_available_tools.append(tool_name_attr)
877
-
878
1026
  # Create comprehensive error message
879
- error_msg = f"Tool '{tool_name}' not found in toolkit '{toolkit_config.get('toolkit_name')}'."
1027
+ error_msg = f"Tool '{tool_name}' not found in toolkit '{toolkit_config.get('toolkit_name')}'.\n"
880
1028
 
881
- if base_available_tools and full_available_tools:
882
- error_msg += f" Available tools: {base_available_tools} (base names) or {full_available_tools} (full names)"
883
- elif base_available_tools:
1029
+ # Custom error for index tools
1030
+ if toolkit_name in [tool.value for tool in IndexTools]:
1031
+ error_msg += f" Please make sure proper PGVector configuration and embedding model are set in the platform.\n"
1032
+
1033
+ if base_available_tools:
884
1034
  error_msg += f" Available tools: {base_available_tools}"
885
1035
  elif available_tools:
886
1036
  error_msg += f" Available tools: {available_tools}"
@@ -889,10 +1039,7 @@ class AlitaClient:
889
1039
 
890
1040
  # Add helpful hint about naming conventions
891
1041
  if '___' in tool_name:
892
- error_msg += f" Note: You provided a full name '{tool_name}'. Try using just the base name '{extract_base_tool_name(tool_name)}'."
893
- elif full_available_tools:
894
- possible_full_name = create_full_tool_name(tool_name, toolkit_name)
895
- error_msg += f" Note: You provided a base name '{tool_name}'. The full name might be '{possible_full_name}'."
1042
+ error_msg += f" Note: Tool names no longer use '___' prefixes. Try using just the base name '{extract_base_tool_name(tool_name)}'."
896
1043
 
897
1044
  return {
898
1045
  "success": False,
@@ -998,6 +1145,9 @@ class AlitaClient:
998
1145
  }
999
1146
 
1000
1147
  except Exception as e:
1148
+ # Re-raise McpAuthorizationRequired to allow proper handling upstream
1149
+ if isinstance(e, McpAuthorizationRequired):
1150
+ raise
1001
1151
  logger = logging.getLogger(__name__)
1002
1152
  logger.error(f"Error in test_toolkit_tool: {str(e)}")
1003
1153
  return {
@@ -1009,3 +1159,158 @@ class AlitaClient:
1009
1159
  "events_dispatched": [],
1010
1160
  "execution_time_seconds": 0.0
1011
1161
  }
1162
+
1163
+ def test_mcp_connection(self, toolkit_config: dict, mcp_tokens: dict = None) -> dict:
1164
+ """
1165
+ Test MCP server connection using protocol-level list_tools.
1166
+
1167
+ This method verifies MCP server connectivity and authentication by calling
1168
+ the protocol-level tools/list JSON-RPC method (NOT executing a tool).
1169
+ This is ideal for auth checks as it validates the connection without
1170
+ requiring any tool execution.
1171
+
1172
+ Args:
1173
+ toolkit_config: Configuration dictionary for the MCP toolkit containing:
1174
+ - toolkit_name: Name of the toolkit
1175
+ - settings: Dictionary with 'url', optional 'headers', 'session_id'
1176
+ mcp_tokens: Optional dictionary of MCP OAuth tokens by server URL
1177
+ Format: {canonical_url: {access_token: str, session_id: str}}
1178
+
1179
+ Returns:
1180
+ Dictionary containing:
1181
+ - success: Boolean indicating if the connection was successful
1182
+ - tools: List of tool names available on the MCP server (if successful)
1183
+ - tools_count: Number of tools discovered
1184
+ - server_session_id: Session ID provided by the server (if any)
1185
+ - error: Error message (if unsuccessful)
1186
+ - toolkit_config: Original toolkit configuration
1187
+
1188
+ Raises:
1189
+ McpAuthorizationRequired: If MCP server requires OAuth authorization
1190
+
1191
+ Example:
1192
+ >>> config = {
1193
+ ... 'toolkit_name': 'my-mcp-server',
1194
+ ... 'type': 'mcp',
1195
+ ... 'settings': {
1196
+ ... 'url': 'https://mcp-server.example.com/mcp',
1197
+ ... 'headers': {'X-Custom': 'value'}
1198
+ ... }
1199
+ ... }
1200
+ >>> result = client.test_mcp_connection(config)
1201
+ >>> if result['success']:
1202
+ ... print(f"Connected! Found {result['tools_count']} tools")
1203
+ """
1204
+ import asyncio
1205
+ import time
1206
+ from ..utils.mcp_client import McpClient
1207
+ from ..utils.mcp_oauth import canonical_resource
1208
+
1209
+ toolkit_name = toolkit_config.get('toolkit_name', 'unknown')
1210
+ settings = toolkit_config.get('settings', {})
1211
+
1212
+ # Extract connection parameters
1213
+ url = settings.get('url')
1214
+ if not url:
1215
+ return {
1216
+ "success": False,
1217
+ "error": "MCP toolkit configuration missing 'url' in settings",
1218
+ "toolkit_config": toolkit_config,
1219
+ "tools": [],
1220
+ "tools_count": 0
1221
+ }
1222
+
1223
+ headers = settings.get('headers') or {}
1224
+ session_id = settings.get('session_id')
1225
+
1226
+ # Apply OAuth token if available
1227
+ if mcp_tokens and url:
1228
+ canonical_url = canonical_resource(url)
1229
+ token_data = mcp_tokens.get(canonical_url)
1230
+ if token_data:
1231
+ if isinstance(token_data, dict):
1232
+ access_token = token_data.get('access_token')
1233
+ if not session_id:
1234
+ session_id = token_data.get('session_id')
1235
+ else:
1236
+ # Backward compatibility: plain token string
1237
+ access_token = token_data
1238
+
1239
+ if access_token:
1240
+ headers = dict(headers) # Copy to avoid mutating original
1241
+ headers.setdefault('Authorization', f'Bearer {access_token}')
1242
+ logger.info(f"[MCP Auth Check] Applied OAuth token for {canonical_url}")
1243
+
1244
+ logger.info(f"Testing MCP connection to '{toolkit_name}' at {url}")
1245
+
1246
+ start_time = time.time()
1247
+
1248
+ async def _test_connection():
1249
+ client = McpClient(
1250
+ url=url,
1251
+ session_id=session_id,
1252
+ headers=headers,
1253
+ timeout=60 # Reasonable timeout for connection test
1254
+ )
1255
+
1256
+ async with client:
1257
+ # Initialize MCP protocol session
1258
+ await client.initialize()
1259
+ logger.info(f"[MCP Auth Check] Session initialized (transport={client.detected_transport})")
1260
+
1261
+ # Call protocol-level list_tools (tools/list JSON-RPC method)
1262
+ tools = await client.list_tools()
1263
+
1264
+ return {
1265
+ "tools": tools,
1266
+ "server_session_id": client.server_session_id,
1267
+ "transport": client.detected_transport
1268
+ }
1269
+
1270
+ try:
1271
+ # Run async operation
1272
+ try:
1273
+ loop = asyncio.get_event_loop()
1274
+ if loop.is_running():
1275
+ # If we're already in an async context, create a new task
1276
+ import concurrent.futures
1277
+ with concurrent.futures.ThreadPoolExecutor() as executor:
1278
+ future = executor.submit(asyncio.run, _test_connection())
1279
+ result = future.result(timeout=120)
1280
+ else:
1281
+ result = loop.run_until_complete(_test_connection())
1282
+ except RuntimeError:
1283
+ # No event loop, create one
1284
+ result = asyncio.run(_test_connection())
1285
+
1286
+ execution_time = time.time() - start_time
1287
+
1288
+ # Extract tool names for the response
1289
+ tool_names = [tool.get('name', 'unknown') for tool in result.get('tools', [])]
1290
+
1291
+ logger.info(f"[MCP Auth Check] Connection successful to '{toolkit_name}': {len(tool_names)} tools in {execution_time:.3f}s")
1292
+
1293
+ return {
1294
+ "success": True,
1295
+ "tools": tool_names,
1296
+ "tools_count": len(tool_names),
1297
+ "server_session_id": result.get('server_session_id'),
1298
+ "transport": result.get('transport'),
1299
+ "toolkit_config": toolkit_config,
1300
+ "execution_time_seconds": execution_time
1301
+ }
1302
+
1303
+ except McpAuthorizationRequired:
1304
+ # Re-raise to allow proper handling upstream
1305
+ raise
1306
+ except Exception as e:
1307
+ execution_time = time.time() - start_time
1308
+ logger.error(f"[MCP Auth Check] Connection failed to '{toolkit_name}': {str(e)}")
1309
+ return {
1310
+ "success": False,
1311
+ "error": f"MCP connection failed: {str(e)}",
1312
+ "toolkit_config": toolkit_config,
1313
+ "tools": [],
1314
+ "tools_count": 0,
1315
+ "execution_time_seconds": execution_time
1316
+ }
@@ -48,27 +48,6 @@ class SandboxArtifact:
48
48
  return f'{data['error']}. {data['content'] if data['content'] else ''}'
49
49
  detected = chardet.detect(data)
50
50
  return data
51
- # TODO: add proper handling for binary files (images, pdf, etc.) for sandbox
52
- # if detected['encoding'] is not None:
53
- # try:
54
- # return data.decode(detected['encoding'])
55
- # except Exception:
56
- # logger.error('Error while default encoding')
57
- # return parse_file_content(file_name=artifact_name,
58
- # file_content=data,
59
- # is_capture_image=is_capture_image,
60
- # page_number=page_number,
61
- # sheet_name=sheet_name,
62
- # excel_by_sheets=excel_by_sheets,
63
- # llm=llm)
64
- # else:
65
- # return parse_file_content(file_name=artifact_name,
66
- # file_content=data,
67
- # is_capture_image=is_capture_image,
68
- # page_number=page_number,
69
- # sheet_name=sheet_name,
70
- # excel_by_sheets=excel_by_sheets,
71
- # llm=llm)
72
51
 
73
52
  def delete(self, artifact_name: str, bucket_name=None):
74
53
  if not bucket_name: