alita-sdk 0.3.497__py3-none-any.whl → 0.3.516__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.

Potentially problematic release.


This version of alita-sdk might be problematic. Click here for more details.

Files changed (109) hide show
  1. alita_sdk/cli/inventory.py +12 -195
  2. alita_sdk/community/inventory/__init__.py +12 -0
  3. alita_sdk/community/inventory/toolkit.py +9 -5
  4. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  5. alita_sdk/configurations/ado.py +144 -0
  6. alita_sdk/configurations/confluence.py +76 -42
  7. alita_sdk/configurations/figma.py +76 -0
  8. alita_sdk/configurations/gitlab.py +2 -0
  9. alita_sdk/configurations/qtest.py +72 -1
  10. alita_sdk/configurations/report_portal.py +96 -0
  11. alita_sdk/configurations/sharepoint.py +148 -0
  12. alita_sdk/configurations/testio.py +83 -0
  13. alita_sdk/runtime/clients/artifact.py +2 -2
  14. alita_sdk/runtime/clients/client.py +24 -19
  15. alita_sdk/runtime/clients/sandbox_client.py +14 -0
  16. alita_sdk/runtime/langchain/assistant.py +64 -23
  17. alita_sdk/runtime/langchain/constants.py +270 -1
  18. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  19. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +2 -1
  20. alita_sdk/runtime/langchain/document_loaders/constants.py +2 -1
  21. alita_sdk/runtime/langchain/langraph_agent.py +8 -9
  22. alita_sdk/runtime/langchain/utils.py +6 -1
  23. alita_sdk/runtime/toolkits/artifact.py +14 -5
  24. alita_sdk/runtime/toolkits/datasource.py +13 -6
  25. alita_sdk/runtime/toolkits/mcp.py +26 -157
  26. alita_sdk/runtime/toolkits/planning.py +10 -5
  27. alita_sdk/runtime/toolkits/tools.py +23 -7
  28. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  29. alita_sdk/runtime/tools/artifact.py +139 -6
  30. alita_sdk/runtime/tools/llm.py +20 -10
  31. alita_sdk/runtime/tools/mcp_remote_tool.py +2 -3
  32. alita_sdk/runtime/tools/mcp_server_tool.py +2 -4
  33. alita_sdk/runtime/utils/AlitaCallback.py +30 -1
  34. alita_sdk/runtime/utils/mcp_client.py +33 -6
  35. alita_sdk/runtime/utils/mcp_oauth.py +125 -8
  36. alita_sdk/runtime/utils/mcp_sse_client.py +35 -6
  37. alita_sdk/runtime/utils/utils.py +2 -0
  38. alita_sdk/tools/__init__.py +15 -0
  39. alita_sdk/tools/ado/repos/__init__.py +10 -12
  40. alita_sdk/tools/ado/test_plan/__init__.py +23 -8
  41. alita_sdk/tools/ado/wiki/__init__.py +24 -8
  42. alita_sdk/tools/ado/wiki/ado_wrapper.py +21 -7
  43. alita_sdk/tools/ado/work_item/__init__.py +24 -8
  44. alita_sdk/tools/advanced_jira_mining/__init__.py +10 -8
  45. alita_sdk/tools/aws/delta_lake/__init__.py +12 -9
  46. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  47. alita_sdk/tools/azure_ai/search/__init__.py +9 -7
  48. alita_sdk/tools/base/tool.py +5 -1
  49. alita_sdk/tools/base_indexer_toolkit.py +25 -0
  50. alita_sdk/tools/bitbucket/__init__.py +14 -10
  51. alita_sdk/tools/bitbucket/api_wrapper.py +50 -2
  52. alita_sdk/tools/browser/__init__.py +5 -4
  53. alita_sdk/tools/carrier/__init__.py +5 -6
  54. alita_sdk/tools/cloud/aws/__init__.py +9 -7
  55. alita_sdk/tools/cloud/azure/__init__.py +9 -7
  56. alita_sdk/tools/cloud/gcp/__init__.py +9 -7
  57. alita_sdk/tools/cloud/k8s/__init__.py +9 -7
  58. alita_sdk/tools/code/linter/__init__.py +9 -8
  59. alita_sdk/tools/code/sonar/__init__.py +9 -7
  60. alita_sdk/tools/confluence/__init__.py +15 -10
  61. alita_sdk/tools/custom_open_api/__init__.py +11 -5
  62. alita_sdk/tools/elastic/__init__.py +10 -8
  63. alita_sdk/tools/elitea_base.py +387 -9
  64. alita_sdk/tools/figma/__init__.py +8 -7
  65. alita_sdk/tools/github/__init__.py +12 -14
  66. alita_sdk/tools/github/github_client.py +68 -2
  67. alita_sdk/tools/github/tool.py +5 -1
  68. alita_sdk/tools/gitlab/__init__.py +14 -11
  69. alita_sdk/tools/gitlab/api_wrapper.py +81 -1
  70. alita_sdk/tools/gitlab_org/__init__.py +9 -8
  71. alita_sdk/tools/google/bigquery/__init__.py +12 -12
  72. alita_sdk/tools/google/bigquery/tool.py +5 -1
  73. alita_sdk/tools/google_places/__init__.py +9 -8
  74. alita_sdk/tools/jira/__init__.py +15 -10
  75. alita_sdk/tools/keycloak/__init__.py +10 -8
  76. alita_sdk/tools/localgit/__init__.py +8 -3
  77. alita_sdk/tools/localgit/local_git.py +62 -54
  78. alita_sdk/tools/localgit/tool.py +5 -1
  79. alita_sdk/tools/memory/__init__.py +11 -3
  80. alita_sdk/tools/ocr/__init__.py +10 -8
  81. alita_sdk/tools/openapi/__init__.py +6 -2
  82. alita_sdk/tools/pandas/__init__.py +9 -7
  83. alita_sdk/tools/postman/__init__.py +10 -11
  84. alita_sdk/tools/pptx/__init__.py +9 -9
  85. alita_sdk/tools/qtest/__init__.py +9 -8
  86. alita_sdk/tools/rally/__init__.py +9 -8
  87. alita_sdk/tools/report_portal/__init__.py +11 -9
  88. alita_sdk/tools/salesforce/__init__.py +9 -9
  89. alita_sdk/tools/servicenow/__init__.py +10 -8
  90. alita_sdk/tools/sharepoint/__init__.py +9 -8
  91. alita_sdk/tools/slack/__init__.py +8 -7
  92. alita_sdk/tools/sql/__init__.py +9 -8
  93. alita_sdk/tools/testio/__init__.py +9 -8
  94. alita_sdk/tools/testrail/__init__.py +10 -8
  95. alita_sdk/tools/utils/__init__.py +9 -4
  96. alita_sdk/tools/utils/text_operations.py +254 -0
  97. alita_sdk/tools/xray/__init__.py +10 -8
  98. alita_sdk/tools/yagmail/__init__.py +8 -3
  99. alita_sdk/tools/zephyr/__init__.py +8 -7
  100. alita_sdk/tools/zephyr_enterprise/__init__.py +10 -8
  101. alita_sdk/tools/zephyr_essential/__init__.py +9 -8
  102. alita_sdk/tools/zephyr_scale/__init__.py +9 -8
  103. alita_sdk/tools/zephyr_squad/__init__.py +9 -8
  104. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/METADATA +1 -1
  105. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/RECORD +109 -106
  106. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/WHEEL +0 -0
  107. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/entry_points.txt +0 -0
  108. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/licenses/LICENSE +0 -0
  109. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import requests
1
2
  from pydantic import BaseModel, ConfigDict, Field, SecretStr
2
3
 
3
4
 
@@ -16,3 +17,85 @@ class TestIOConfiguration(BaseModel):
16
17
  )
17
18
  endpoint: str = Field(description="TestIO endpoint")
18
19
  api_key: SecretStr = Field(description="TestIO API Key")
20
+
21
+ @staticmethod
22
+ def check_connection(settings: dict) -> str | None:
23
+ """
24
+ Test the connection to TestIO API.
25
+
26
+ Args:
27
+ settings: Dictionary containing 'endpoint' and 'api_key' (both required)
28
+
29
+ Returns:
30
+ None if connection is successful, error message string otherwise
31
+ """
32
+ endpoint = settings.get("endpoint")
33
+ if endpoint is None or endpoint == "":
34
+ if endpoint == "":
35
+ return "Endpoint cannot be empty"
36
+ return "Endpoint is required"
37
+
38
+ # Validate endpoint format
39
+ if not isinstance(endpoint, str):
40
+ return "Endpoint must be a string"
41
+
42
+ endpoint = endpoint.strip()
43
+ if not endpoint:
44
+ return "Endpoint cannot be empty"
45
+ if not endpoint.startswith(("http://", "https://")):
46
+ return "Endpoint must start with http:// or https://"
47
+
48
+ # Remove trailing slash for consistency
49
+ endpoint = endpoint.rstrip("/")
50
+
51
+ api_key = settings.get("api_key")
52
+ if api_key is None:
53
+ return "API key is required"
54
+
55
+ # Extract secret value if it's a SecretStr
56
+ if hasattr(api_key, "get_secret_value"):
57
+ api_key = api_key.get_secret_value()
58
+
59
+ # Validate API key is not empty
60
+ if not api_key or not api_key.strip():
61
+ return "API key cannot be empty"
62
+
63
+ # Verification strategy:
64
+ # Use an auth-required endpoint and a single, explicit auth scheme:
65
+ # Authorization: Token <token>
66
+ url = f"{endpoint}/customer/v2/products"
67
+
68
+ try:
69
+ resp = TestIOConfiguration._get_with_token(url, api_key)
70
+
71
+ if resp.status_code == 200:
72
+ return None # Connection successful
73
+ if resp.status_code == 401:
74
+ return "Invalid token"
75
+ if resp.status_code == 403:
76
+ return "Access forbidden - token has no access to /customer/v2/products"
77
+ if resp.status_code == 404:
78
+ return "Invalid endpoint. Verify TestIO base endpoint."
79
+ if resp.status_code == 429:
80
+ return "Rate limited - please try again later"
81
+ if 500 <= resp.status_code <= 599:
82
+ return f"TestIO service error (HTTP {resp.status_code})"
83
+ return f"Connection failed (HTTP {resp.status_code})"
84
+
85
+ except requests.exceptions.Timeout:
86
+ return "Connection timeout - TestIO did not respond within 10 seconds"
87
+ except requests.exceptions.ConnectionError:
88
+ return "Connection error - unable to reach TestIO. Check endpoint URL and network."
89
+ except requests.exceptions.RequestException as e:
90
+ return f"Request failed: {str(e)}"
91
+ except Exception:
92
+ return "Unexpected error during TestIO connection check"
93
+
94
+ @staticmethod
95
+ def _get_with_token(url: str, token: str) -> requests.Response:
96
+ """Perform an authenticated GET using `Authorization: Token <token>`."""
97
+ return requests.get(
98
+ url,
99
+ headers={"Authorization": f"Token {token}"},
100
+ timeout=10,
101
+ )
@@ -38,8 +38,8 @@ class Artifact:
38
38
  if len(data) == 0:
39
39
  # empty file might be created
40
40
  return ""
41
- if isinstance(data, dict) and data['error']:
42
- return f"{data['error']}. {data['content'] if data['content'] else ''}"
41
+ if isinstance(data, dict) and data.get('error'):
42
+ return f"{data['error']}. {data.get('content', '')}"
43
43
  detected = chardet.detect(data)
44
44
  if detected['encoding'] is not None:
45
45
  try:
@@ -20,9 +20,8 @@ from .prompt import AlitaPrompt
20
20
  from .datasource import AlitaDataSource
21
21
  from .artifact import Artifact
22
22
  from ..langchain.chat_message_template import Jinja2TemplatedChatMessagesTemplate
23
- from ..utils.utils import TOOLKIT_SPLITTER
24
23
  from ..utils.mcp_oauth import McpAuthorizationRequired
25
- from ...tools import get_available_toolkit_models
24
+ from ...tools import get_available_toolkit_models, instantiate_toolkit
26
25
  from ...tools.base_indexer_toolkit import IndexTools
27
26
 
28
27
  logger = logging.getLogger(__name__)
@@ -146,6 +145,19 @@ class AlitaClient:
146
145
  data = requests.get(url, headers=self.headers, verify=False).json()
147
146
  return data
148
147
 
148
+ def toolkit(self, toolkit_id: int):
149
+ url = f"{self.base_url}{self.api_path}/tool/prompt_lib/{self.project_id}/{toolkit_id}"
150
+ response = requests.get(url, headers=self.headers, verify=False)
151
+ if not response.ok:
152
+ raise ValueError(f"Failed to fetch toolkit {toolkit_id}: {response.text}")
153
+
154
+ tool_data = response.json()
155
+ if 'settings' not in tool_data:
156
+ tool_data['settings'] = {}
157
+ tool_data['settings']['alita'] = self
158
+
159
+ return instantiate_toolkit(tool_data)
160
+
149
161
  def get_list_of_apps(self):
150
162
  apps = []
151
163
  limit = 10
@@ -348,7 +360,7 @@ class AlitaClient:
348
360
  application_variables: Optional[dict] = None,
349
361
  version_details: Optional[dict] = None, store: Optional[BaseStore] = None,
350
362
  llm: Optional[ChatOpenAI] = None, mcp_tokens: Optional[dict] = None,
351
- conversation_id: Optional[str] = None):
363
+ conversation_id: Optional[str] = None, ignored_mcp_servers: Optional[list] = None):
352
364
  if tools is None:
353
365
  tools = []
354
366
  if chat_history is None:
@@ -397,12 +409,12 @@ class AlitaClient:
397
409
  if runtime == 'nonrunnable':
398
410
  return LangChainAssistant(self, data, llm, chat_history, app_type,
399
411
  tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens,
400
- conversation_id=conversation_id)
412
+ conversation_id=conversation_id, ignored_mcp_servers=ignored_mcp_servers)
401
413
  if runtime == 'langchain':
402
414
  return LangChainAssistant(self, data, llm,
403
415
  chat_history, app_type,
404
416
  tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens,
405
- conversation_id=conversation_id).runnable()
417
+ conversation_id=conversation_id, ignored_mcp_servers=ignored_mcp_servers).runnable()
406
418
  elif runtime == 'llama':
407
419
  raise NotImplementedError("LLama runtime is not supported")
408
420
 
@@ -656,7 +668,8 @@ class AlitaClient:
656
668
  tools: Optional[list] = None, chat_history: Optional[List[Any]] = None,
657
669
  memory=None, runtime='langchain', variables: Optional[list] = None,
658
670
  store: Optional[BaseStore] = None, debug_mode: Optional[bool] = False,
659
- mcp_tokens: Optional[dict] = None, conversation_id: Optional[str] = None):
671
+ mcp_tokens: Optional[dict] = None, conversation_id: Optional[str] = None,
672
+ ignored_mcp_servers: Optional[list] = None):
660
673
  """
661
674
  Create a predict-type agent with minimal configuration.
662
675
 
@@ -672,6 +685,7 @@ class AlitaClient:
672
685
  variables: Optional list of variables for the agent
673
686
  store: Optional store for memory
674
687
  debug_mode: Enable debug mode for cases when assistant can be initialized without tools
688
+ ignored_mcp_servers: Optional list of MCP server URLs to ignore (user chose to continue without auth)
675
689
 
676
690
  Returns:
677
691
  Runnable agent ready for execution
@@ -705,7 +719,8 @@ class AlitaClient:
705
719
  store=store,
706
720
  debug_mode=debug_mode,
707
721
  mcp_tokens=mcp_tokens,
708
- conversation_id=conversation_id
722
+ conversation_id=conversation_id,
723
+ ignored_mcp_servers=ignored_mcp_servers
709
724
  ).runnable()
710
725
 
711
726
  def test_toolkit_tool(self, toolkit_config: dict, tool_name: str, tool_params: dict = None,
@@ -939,7 +954,6 @@ class AlitaClient:
939
954
  if target_tool is None:
940
955
  available_tools = []
941
956
  base_available_tools = []
942
- full_available_tools = []
943
957
 
944
958
  for tool in tools:
945
959
  tool_name_attr = None
@@ -956,10 +970,6 @@ class AlitaClient:
956
970
  if base_name not in base_available_tools:
957
971
  base_available_tools.append(base_name)
958
972
 
959
- # Track full names separately
960
- if TOOLKIT_SPLITTER in tool_name_attr:
961
- full_available_tools.append(tool_name_attr)
962
-
963
973
  # Create comprehensive error message
964
974
  error_msg = f"Tool '{tool_name}' not found in toolkit '{toolkit_config.get('toolkit_name')}'.\n"
965
975
 
@@ -967,9 +977,7 @@ class AlitaClient:
967
977
  if toolkit_name in [tool.value for tool in IndexTools]:
968
978
  error_msg += f" Please make sure proper PGVector configuration and embedding model are set in the platform.\n"
969
979
 
970
- if base_available_tools and full_available_tools:
971
- error_msg += f" Available tools: {base_available_tools} (base names) or {full_available_tools} (full names)"
972
- elif base_available_tools:
980
+ if base_available_tools:
973
981
  error_msg += f" Available tools: {base_available_tools}"
974
982
  elif available_tools:
975
983
  error_msg += f" Available tools: {available_tools}"
@@ -978,10 +986,7 @@ class AlitaClient:
978
986
 
979
987
  # Add helpful hint about naming conventions
980
988
  if '___' in tool_name:
981
- error_msg += f" Note: You provided a full name '{tool_name}'. Try using just the base name '{extract_base_tool_name(tool_name)}'."
982
- elif full_available_tools:
983
- possible_full_name = create_full_tool_name(tool_name, toolkit_name)
984
- error_msg += f" Note: You provided a base name '{tool_name}'. The full name might be '{possible_full_name}'."
989
+ error_msg += f" Note: Tool names no longer use '___' prefixes. Try using just the base name '{extract_base_tool_name(tool_name)}'."
985
990
 
986
991
  return {
987
992
  "success": False,
@@ -6,6 +6,7 @@ import requests
6
6
  from typing import Any
7
7
  from json import dumps
8
8
  import chardet
9
+ from ...tools import instantiate_toolkit
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
@@ -184,6 +185,19 @@ class SandboxClient:
184
185
  data = requests.get(url, headers=self.headers, verify=False).json()
185
186
  return data
186
187
 
188
+ def toolkit(self, toolkit_id: int):
189
+ url = f"{self.base_url}{self.api_path}/tool/prompt_lib/{self.project_id}/{toolkit_id}"
190
+ response = requests.get(url, headers=self.headers, verify=False)
191
+ if not response.ok:
192
+ raise ValueError(f"Failed to fetch toolkit {toolkit_id}: {response.text}")
193
+
194
+ tool_data = response.json()
195
+ if 'settings' not in tool_data:
196
+ tool_data['settings'] = {}
197
+ tool_data['settings']['alita'] = self
198
+
199
+ return instantiate_toolkit(tool_data)
200
+
187
201
  def get_list_of_apps(self):
188
202
  apps = []
189
203
  limit = 10
@@ -13,7 +13,7 @@ from langchain_core.messages import (
13
13
  BaseMessage, SystemMessage, HumanMessage
14
14
  )
15
15
  from langchain_core.prompts import MessagesPlaceholder
16
- from .constants import REACT_ADDON, REACT_VARS, XML_ADDON
16
+ from .constants import REACT_ADDON, REACT_VARS, XML_ADDON, USER_ADDON, DEFAULT_ASSISTANT, PLAN_ADDON, PYODITE_ADDON
17
17
  from .chat_message_template import Jinja2TemplatedChatMessagesTemplate
18
18
  from ..tools.echo import EchoTool
19
19
  from langchain_core.tools import BaseTool, ToolException
@@ -33,7 +33,8 @@ class Assistant:
33
33
  store: Optional[BaseStore] = None,
34
34
  debug_mode: Optional[bool] = False,
35
35
  mcp_tokens: Optional[dict] = None,
36
- conversation_id: Optional[str] = None):
36
+ conversation_id: Optional[str] = None,
37
+ ignored_mcp_servers: Optional[list] = None):
37
38
 
38
39
  self.app_type = app_type
39
40
  self.memory = memory
@@ -98,10 +99,55 @@ class Assistant:
98
99
  memory_store=self.store,
99
100
  debug_mode=debug_mode,
100
101
  mcp_tokens=mcp_tokens,
101
- conversation_id=conversation_id
102
+ conversation_id=conversation_id,
103
+ ignored_mcp_servers=ignored_mcp_servers
102
104
  )
103
105
  if tools:
104
106
  self.tools += tools
107
+
108
+ # Create ToolRegistry to track tool metadata and handle name collisions
109
+ self.tool_registry = {}
110
+ tool_name_counts = {} # Track how many times each base name appears
111
+
112
+ for tool in self.tools:
113
+ if hasattr(tool, 'name'):
114
+ original_name = tool.name
115
+ base_name = original_name
116
+
117
+ # Extract toolkit metadata from tool configuration
118
+ toolkit_name = ""
119
+ toolkit_type = ""
120
+
121
+ # Find matching tool config to extract metadata
122
+ for tool_config in version_tools:
123
+ # Try to match by toolkit_name or name field
124
+ config_toolkit_name = tool_config.get('toolkit_name', tool_config.get('name', ''))
125
+ # Simple heuristic: toolkit info should be accessible from tool config
126
+ # For now, use toolkit_name and type from config
127
+ toolkit_name = config_toolkit_name
128
+ toolkit_type = tool_config.get('type', '')
129
+ break # Use first match for now; will refine with better matching
130
+
131
+ # Handle duplicate tool names by appending numeric suffix
132
+ if base_name in tool_name_counts:
133
+ tool_name_counts[base_name] += 1
134
+ # Append suffix to make unique
135
+ new_name = f"{base_name}_{tool_name_counts[base_name]}"
136
+ tool.name = new_name
137
+ logger.info(f"Tool name collision detected: '{base_name}' -> '{new_name}'")
138
+ else:
139
+ tool_name_counts[base_name] = 0
140
+ new_name = base_name
141
+
142
+ # Store in registry
143
+ self.tool_registry[tool.name] = {
144
+ 'toolkit_name': toolkit_name,
145
+ 'toolkit_type': toolkit_type,
146
+ 'original_tool_name': base_name
147
+ }
148
+
149
+ logger.info(f"ToolRegistry initialized with {len(self.tool_registry)} tools")
150
+
105
151
  # Handle prompt setup
106
152
  if app_type in ["pipeline", "predict", "react"]:
107
153
  self.prompt = data['instructions']
@@ -230,34 +276,29 @@ class Assistant:
230
276
  # Only use prompt_instructions if explicitly specified (for predict app_type)
231
277
  if self.app_type == "predict" and isinstance(self.prompt, str):
232
278
  prompt_instructions = self.prompt
233
-
234
- # take the system message from the openai prompt as a prompt instructions
235
- if self.app_type == "openai" and hasattr(self.prompt, 'messages'):
236
- prompt_instructions = self.__take_prompt_from_openai_messages()
237
-
238
- # Create a unified YAML schema with conditional tool binding
239
- # Build the base node configuration
240
- node_config = {
241
- 'id': 'agent',
242
- 'type': 'llm',
243
- 'prompt': {
244
- 'template': prompt_instructions or "You are a helpful assistant."
245
- },
246
- 'input': ['messages'],
247
- 'output': ['messages'],
248
- 'transition': 'END'
249
- }
250
279
 
251
280
  # Add tool binding only if tools are present
252
281
  if simple_tools:
253
282
  tool_names = [tool.name for tool in simple_tools]
254
- tool_names_yaml = str(tool_names).replace("'", '"') # Convert to YAML-compatible format
255
- node_config['tool_names'] = tool_names_yaml
256
283
  logger.info("Binding tools: %s", tool_names)
257
284
 
285
+ # take the system message from the openai prompt as a prompt instructions
286
+ if self.app_type == "openai" and hasattr(self.prompt, 'messages'):
287
+ prompt_instructions = self.__take_prompt_from_openai_messages()
288
+
289
+ user_addon = USER_ADDON.format(prompt=str(prompt_instructions)) if prompt_instructions else ""
290
+ plan_addon = PLAN_ADDON if 'update_plan' in tool_names else ""
291
+ pyodite_addon = PYODITE_ADDON if 'pyodide_sandbox' in tool_names else ""
292
+ escaped_prompt = DEFAULT_ASSISTANT.format(
293
+ user_addon=user_addon,
294
+ plan_addon=plan_addon,
295
+ pyodite_addon=pyodite_addon
296
+ )
297
+
298
+
258
299
  # Properly setup the prompt for YAML
259
300
  import yaml
260
- escaped_prompt = prompt_instructions or "You are a helpful assistant."
301
+
261
302
 
262
303
  # Create the schema as a dictionary first, then convert to YAML
263
304
  state_messages_config = {'type': 'list'}
@@ -86,4 +86,273 @@ PRINTER = "printer"
86
86
  PRINTER_NODE_RS = "printer_output"
87
87
  PRINTER_COMPLETED_STATE = "PRINTER_COMPLETED"
88
88
 
89
- LOADER_MAX_TOKENS_DEFAULT = 512
89
+ LOADER_MAX_TOKENS_DEFAULT = 512
90
+
91
+ DEFAULT_ASSISTANT = """You are **Alita**, a Testing Agent running in a web chat. You are expected to be precise, safe, technical, and helpful.
92
+
93
+ Your capabilities:
94
+
95
+ - Receive user prompts and other context provided by the harness, such as files, links, logs, test suites, reports, screenshots, API specs, and documentation.
96
+ - Communicate progress, decisions, and conclusions clearly, and by making & updating plans.
97
+ - Default to read-only analysis. Require explicit user approval before any mutating action (file edits, config changes, deployments, data changes) unless the session is already explicitly authorized.
98
+ - Use only the tools/functions explicitly provided by the harness in this session to best solve user request, analyze artifacts, and apply updates when required. Depending on configuration, you may request that these function calls be escalated for approval before executing.
99
+
100
+ Within this context, **Alita** refers to the open-source agentic testing interface (not any legacy language model).
101
+
102
+ ---
103
+
104
+ # How you work
105
+
106
+ ## Personality
107
+
108
+ You are concise, direct, and friendly. You communicate efficiently and always prioritize actionable insights.
109
+ You clearly state assumptions, environment prerequisites, and next steps.
110
+ When in doubt, prefer concise factual reporting over explanatory prose.
111
+
112
+ {users_instructions}
113
+
114
+ ## Responsiveness
115
+
116
+ ### Preamble messages
117
+
118
+ Before running tool calls (executing tests, launching commands, applying patches), send a brief preface describing what you’re about to do. It should:
119
+
120
+ - Be short (8–12 words)
121
+ - Group related actions together
122
+ - Refer to previous context when relevant
123
+ - Keep a light and collaborative tone
124
+
125
+ Example patterns:
126
+
127
+ - “Analyzing failing tests next to identify the root cause.”
128
+ - “Running backend API tests now to reproduce the reported issue.”
129
+ - “About to patch selectors and re-run UI regression tests.”
130
+ - “Finished scanning logs; now checking flaky test patterns.”
131
+ - “Next I’ll generate missing test data and rerun.”
132
+
133
+ ---
134
+
135
+ ## Task execution
136
+
137
+ You are a **testing agent**, not just a code-writing agent. Your responsibilities include:
138
+
139
+ - Executing tests across frameworks (API, UI, mobile, backend, contract, load, security)
140
+ - Analyzing logs, failures, screenshots, metrics, stack traces
141
+ - Investigating flakiness, nondeterminism, environmental issues
142
+ - Generating missing tests or aligning test coverage to requirements
143
+ - Proposing (and applying when asked) patches to fix the root cause of test failures
144
+ - Updating and creating test cases, fixtures, mocks, test data and configs
145
+ - Validating integrations (CI/CD, containers, runners, environments)
146
+ - Surfacing reliability and coverage gaps
147
+
148
+ When applying patches, follow repository style and `Custom instructions` rules.
149
+ Avoid modifying unrelated code and avoid adding technical debt.
150
+
151
+ Common use cases include:
152
+
153
+ - Test execution automation
154
+ - Manual exploratory testing documentation
155
+ - Test case generation from requirements
156
+ - Assertions improvements and selector stabilization
157
+ - Test coverage analysis
158
+ - Defect reproduction and debugging
159
+ - Root cause attribution (test vs product defect)
160
+
161
+ {planning_instructions}
162
+
163
+ ---
164
+
165
+ ## Handling files
166
+
167
+ ### CRITICAL: File creation and modification rules
168
+
169
+ **NEVER output entire file contents in your response.**
170
+
171
+ When creating or modifying files:
172
+
173
+ 1. **Use incremental writes for new files**: Create files in logical sections using multiple tool calls:
174
+ - First call: Create file with initial structure (imports, class definition header, TOC, etc.)
175
+ - Subsequent calls: Add methods, functions, or sections one at a time using edit/append
176
+ - This prevents context overflow and ensures each part is properly written
177
+
178
+ 2. **Use edit tools for modifications**: It allows precise text replacement instead of rewriting entire files
179
+
180
+ 3. **Never dump code in chat**: If you find yourself about to write a large code block in your response, STOP and use a file tool instead
181
+
182
+ Example - creating a test file correctly:
183
+ ```
184
+ # Call 1: Create file with structure
185
+ create_file("test_api.py", "import pytest\\nimport requests\\n\\n")
186
+
187
+ # Call 2: Append first test class/method
188
+ append_data("test_api.py", "class TestAPI:\\n def test_health(self):\\n assert requests.get(base_url + '/health').status_code == 200\\n")
189
+
190
+ # Call 3: Append second test method
191
+ append_data("test_api.py", "\\n def test_auth(self):\\n assert requests.get(base_url + '/protected').status_code == 401\\n")
192
+ ```
193
+
194
+ **Why this matters**: Large file outputs can exceed token limits, cause truncation, or fail silently. Incremental writes are reliable and verifiable.
195
+
196
+ ### Reading large files
197
+
198
+ When working with large files (logs, test reports, data files, source code):
199
+
200
+ - **Read in chunks**: Use offset and limit parameters to read files in manageable sections (e.g., 500-1000 lines at a time)
201
+ - **Start with structure**: First scan the file to understand its layout before diving into specific sections
202
+ - **Target relevant sections**: Once you identify the area of interest, read only that portion in detail
203
+ - **Avoid full loads**: Loading entire large files into context can cause models to return empty or incomplete responses due to context limitations
204
+
205
+ Example approach:
206
+ 1. Read first 100 lines to understand file structure
207
+ 2. Search for relevant patterns to locate target sections
208
+ 3. Read specific line ranges where issues or relevant code exist
209
+
210
+ ### Writing and updating files
211
+
212
+ When modifying files, especially large ones:
213
+
214
+ - **Update in pieces**: Make targeted edits to specific sections, paragraphs, or functions rather than rewriting entire files
215
+ - **Use precise replacements**: Replace exact strings with sufficient context (3-5 lines before/after) to ensure unique matches
216
+ - **Batch related changes**: Group logically related edits together, but keep each edit focused and minimal
217
+ - **Preserve structure**: Maintain existing formatting, indentation, and file organization
218
+ - **Avoid full rewrites**: Never regenerate an entire file when only a portion needs changes
219
+
220
+ ### Context limitations warning
221
+
222
+ **Important**: When context becomes too large (many files, long outputs, extensive history), some models may return empty or truncated responses. If you notice this:
223
+
224
+ - Summarize previous findings before continuing
225
+ - Focus on one file or task at a time
226
+ - Clear irrelevant context from consideration
227
+ - Break complex operations into smaller, sequential steps
228
+
229
+ {pyodite_addon}
230
+
231
+ ---
232
+
233
+ ## Validating your work
234
+
235
+ Validation is core to your role.
236
+
237
+ - Do not rely on assumptions or intuition alone.
238
+ - Cross-check conclusions against available evidence such as logs, configs, test results, metrics, traces, or code.
239
+ - When proposing a fix or recommendation, ensure it can be verified with concrete artifacts or reproducible steps.
240
+ - If evidence is missing or incomplete, explicitly state the gap and its impact on confidence.
241
+
242
+ ---
243
+
244
+ ## Presenting your work and final message
245
+
246
+ Your final message should read like a technical handoff from a senior engineer.
247
+
248
+ Good patterns include:
249
+
250
+ - What was analyzed or investigated
251
+ - What was observed and why it matters
252
+ - What failed or is misconfigured (root cause, not symptoms)
253
+ - What was changed, fixed, or recommended
254
+ - Where changes apply (files, services, environments)
255
+ - How to validate or reproduce locally or in a target environment
256
+
257
+ Do not dump full file contents unless explicitly requested.
258
+ Reference files, paths, services, or resources directly.
259
+
260
+ If relevant, offer optional next steps such as:
261
+
262
+ - Running broader validation (regression, load, smoke)
263
+ - Adding missing checks, tests, or monitoring
264
+ - Improving robustness, performance, or security
265
+ - Integrating the fix into CI/CD or automation
266
+
267
+ ---
268
+
269
+ ## Answer formatting rules
270
+
271
+ Keep results scannable and technical:
272
+
273
+ - Use section headers only where they improve clarity
274
+ - Use short bullet lists (4–6 key bullets per section)
275
+ - Use backticks for code, commands, identifiers, paths, and config keys
276
+ - Reference files and resources individually (e.g. `src/auth/token.ts:87`, `nginx.conf`, `service/payment-api`)
277
+ - Avoid nested bullet lists and long explanatory paragraphs
278
+
279
+ ---
280
+ Tone: pragmatic, precise, and focused on improving factual correctness, reliability and coverage.
281
+ """
282
+
283
+ USER_ADDON = """
284
+ ---
285
+
286
+ # Customization
287
+
288
+ User `Custom instructions` contains instructions for working in that specific session — including test conventions, folder structure, naming rules, frameworks in use, test data handling, or how to run validations.
289
+
290
+ Rules:
291
+ - Any action you do must follow instructions from applicable `Custom instructions`.
292
+ - For conflicting instructions, `Custom instructions` takes precedence.
293
+ - If `Custom instructions` conflict with earlier session notes, `Custom instructions` win; if they conflict with system/developer policy, system/developer wins.
294
+
295
+ ## Custom instructions:
296
+
297
+ ```
298
+ {prompt}
299
+ ```
300
+
301
+ ---
302
+ """
303
+
304
+ PLAN_ADDON = """
305
+ ---
306
+
307
+ ## Planning
308
+
309
+ Use `update_plan` when:
310
+
311
+ - Tasks involve multiple phases of testing
312
+ - The sequence of activities matters
313
+ - Ambiguity requires breaking down the approach
314
+ - The user requests step-wise execution
315
+
316
+ ### Resuming existing plans
317
+
318
+ **Important**: Before creating a new plan, check if there's already an existing plan in progress:
319
+
320
+ - If the user says "continue" or similar, look at the current plan state shown in tool results
321
+ - If steps are already marked as completed (☑), **do not create a new plan** — continue executing the remaining uncompleted steps
322
+ - Only use `update_plan` to create a **new** plan when starting a fresh task
323
+ - Use `complete_step` to mark steps done as you finish them
324
+
325
+ When resuming after interruption (e.g., tool limit reached):
326
+
327
+ 1. Review which steps are already completed (☑)
328
+ 2. Identify the next uncompleted step (☐)
329
+ 3. Continue execution from that step — do NOT recreate the plan
330
+ 4. Mark steps complete as you go
331
+
332
+ Example of a **high-quality test-oriented plan**:
333
+
334
+ 1. Reproduce failure locally
335
+ 2. Capture failing logs + stack traces
336
+ 3. Identify root cause in test or code
337
+ 4. Patch locator + stabilize assertions
338
+ 5. Run whole suite to confirm no regressions
339
+
340
+ Low-quality plans ("run tests → fix things → done") are not acceptable.
341
+ """
342
+
343
+ PYODITE_ADDON = """
344
+ ---
345
+
346
+ ## Using the Python (Pyodide) sandbox
347
+
348
+ Python sandbox is available, it runs in a **Pyodide (browser-based) environment** with limitations:
349
+
350
+ - Use it only for lightweight data analysis, parsing, transformation, or validation
351
+ - Do not assume access to the local filesystem, network, OS commands, or background processes
352
+ - Do not attempt `pip install` or rely on unavailable native extensions
353
+ - Treat all inputs as in-memory data provided by the harness or previous tool outputs
354
+ - For large datasets, long-running tasks, or environment-dependent execution, request an external tool or user-provided artifacts instead
355
+
356
+ If a task cannot be reliably executed in Pyodide, explicitly state the limitation and propose an alternative approach.
357
+
358
+ """