solace-agent-mesh 0.0.1__py3-none-any.whl → 0.1.0__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 solace-agent-mesh might be problematic. Click here for more details.

Files changed (172) hide show
  1. solace_agent_mesh/__init__.py +0 -3
  2. solace_agent_mesh/agents/__init__.py +0 -0
  3. solace_agent_mesh/agents/base_agent_component.py +224 -0
  4. solace_agent_mesh/agents/global/__init__.py +0 -0
  5. solace_agent_mesh/agents/global/actions/__init__.py +0 -0
  6. solace_agent_mesh/agents/global/actions/agent_state_change.py +54 -0
  7. solace_agent_mesh/agents/global/actions/clear_history.py +32 -0
  8. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +160 -0
  9. solace_agent_mesh/agents/global/actions/create_file.py +70 -0
  10. solace_agent_mesh/agents/global/actions/error_action.py +45 -0
  11. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +93 -0
  12. solace_agent_mesh/agents/global/actions/plotly_graph.py +117 -0
  13. solace_agent_mesh/agents/global/actions/retrieve_file.py +51 -0
  14. solace_agent_mesh/agents/global/global_agent_component.py +38 -0
  15. solace_agent_mesh/agents/image_processing/__init__.py +0 -0
  16. solace_agent_mesh/agents/image_processing/actions/__init__.py +0 -0
  17. solace_agent_mesh/agents/image_processing/actions/create_image.py +75 -0
  18. solace_agent_mesh/agents/image_processing/actions/describe_image.py +115 -0
  19. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +23 -0
  20. solace_agent_mesh/agents/slack/__init__.py +1 -0
  21. solace_agent_mesh/agents/slack/actions/__init__.py +1 -0
  22. solace_agent_mesh/agents/slack/actions/post_message.py +177 -0
  23. solace_agent_mesh/agents/slack/slack_agent_component.py +59 -0
  24. solace_agent_mesh/agents/web_request/__init__.py +0 -0
  25. solace_agent_mesh/agents/web_request/actions/__init__.py +0 -0
  26. solace_agent_mesh/agents/web_request/actions/do_image_search.py +84 -0
  27. solace_agent_mesh/agents/web_request/actions/do_news_search.py +47 -0
  28. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +34 -0
  29. solace_agent_mesh/agents/web_request/actions/do_web_request.py +134 -0
  30. solace_agent_mesh/agents/web_request/actions/download_file.py +69 -0
  31. solace_agent_mesh/agents/web_request/web_request_agent_component.py +33 -0
  32. solace_agent_mesh/cli/__init__.py +1 -0
  33. solace_agent_mesh/cli/commands/__init__.py +0 -0
  34. solace_agent_mesh/cli/commands/add/__init__.py +3 -0
  35. solace_agent_mesh/cli/commands/add/add.py +88 -0
  36. solace_agent_mesh/cli/commands/add/agent.py +110 -0
  37. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +90 -0
  38. solace_agent_mesh/cli/commands/add/gateway.py +221 -0
  39. solace_agent_mesh/cli/commands/build.py +631 -0
  40. solace_agent_mesh/cli/commands/chat/__init__.py +3 -0
  41. solace_agent_mesh/cli/commands/chat/chat.py +361 -0
  42. solace_agent_mesh/cli/commands/config.py +29 -0
  43. solace_agent_mesh/cli/commands/init/__init__.py +3 -0
  44. solace_agent_mesh/cli/commands/init/ai_provider_step.py +76 -0
  45. solace_agent_mesh/cli/commands/init/broker_step.py +102 -0
  46. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +88 -0
  47. solace_agent_mesh/cli/commands/init/check_if_already_done.py +13 -0
  48. solace_agent_mesh/cli/commands/init/create_config_file_step.py +52 -0
  49. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +96 -0
  50. solace_agent_mesh/cli/commands/init/file_service_step.py +73 -0
  51. solace_agent_mesh/cli/commands/init/init.py +114 -0
  52. solace_agent_mesh/cli/commands/init/project_structure_step.py +45 -0
  53. solace_agent_mesh/cli/commands/init/rest_api_step.py +50 -0
  54. solace_agent_mesh/cli/commands/init/web_ui_step.py +40 -0
  55. solace_agent_mesh/cli/commands/plugin/__init__.py +3 -0
  56. solace_agent_mesh/cli/commands/plugin/add.py +98 -0
  57. solace_agent_mesh/cli/commands/plugin/build.py +217 -0
  58. solace_agent_mesh/cli/commands/plugin/create.py +117 -0
  59. solace_agent_mesh/cli/commands/plugin/plugin.py +109 -0
  60. solace_agent_mesh/cli/commands/plugin/remove.py +71 -0
  61. solace_agent_mesh/cli/commands/run.py +68 -0
  62. solace_agent_mesh/cli/commands/visualizer.py +138 -0
  63. solace_agent_mesh/cli/config.py +81 -0
  64. solace_agent_mesh/cli/main.py +306 -0
  65. solace_agent_mesh/cli/utils.py +246 -0
  66. solace_agent_mesh/common/__init__.py +0 -0
  67. solace_agent_mesh/common/action.py +91 -0
  68. solace_agent_mesh/common/action_list.py +37 -0
  69. solace_agent_mesh/common/action_response.py +327 -0
  70. solace_agent_mesh/common/constants.py +3 -0
  71. solace_agent_mesh/common/mysql_database.py +40 -0
  72. solace_agent_mesh/common/postgres_database.py +79 -0
  73. solace_agent_mesh/common/prompt_templates.py +30 -0
  74. solace_agent_mesh/common/prompt_templates_unused_delete.py +161 -0
  75. solace_agent_mesh/common/stimulus_utils.py +152 -0
  76. solace_agent_mesh/common/time.py +24 -0
  77. solace_agent_mesh/common/utils.py +638 -0
  78. solace_agent_mesh/configs/agent_global.yaml +74 -0
  79. solace_agent_mesh/configs/agent_image_processing.yaml +82 -0
  80. solace_agent_mesh/configs/agent_slack.yaml +64 -0
  81. solace_agent_mesh/configs/agent_web_request.yaml +75 -0
  82. solace_agent_mesh/configs/conversation_to_file.yaml +56 -0
  83. solace_agent_mesh/configs/error_catcher.yaml +56 -0
  84. solace_agent_mesh/configs/monitor.yaml +0 -0
  85. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +106 -0
  86. solace_agent_mesh/configs/monitor_user_feedback.yaml +58 -0
  87. solace_agent_mesh/configs/orchestrator.yaml +241 -0
  88. solace_agent_mesh/configs/service_embedding.yaml +81 -0
  89. solace_agent_mesh/configs/service_llm.yaml +265 -0
  90. solace_agent_mesh/configs/visualize_websocket.yaml +55 -0
  91. solace_agent_mesh/gateway/__init__.py +0 -0
  92. solace_agent_mesh/gateway/components/__init__.py +0 -0
  93. solace_agent_mesh/gateway/components/gateway_base.py +41 -0
  94. solace_agent_mesh/gateway/components/gateway_input.py +265 -0
  95. solace_agent_mesh/gateway/components/gateway_output.py +289 -0
  96. solace_agent_mesh/gateway/identity/bamboohr_identity.py +18 -0
  97. solace_agent_mesh/gateway/identity/identity_base.py +10 -0
  98. solace_agent_mesh/gateway/identity/identity_provider.py +60 -0
  99. solace_agent_mesh/gateway/identity/no_identity.py +9 -0
  100. solace_agent_mesh/gateway/identity/passthru_identity.py +9 -0
  101. solace_agent_mesh/monitors/base_monitor_component.py +26 -0
  102. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +75 -0
  103. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +560 -0
  104. solace_agent_mesh/orchestrator/__init__.py +0 -0
  105. solace_agent_mesh/orchestrator/action_manager.py +225 -0
  106. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  107. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +54 -0
  108. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +179 -0
  109. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +107 -0
  110. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +477 -0
  111. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +246 -0
  112. solace_agent_mesh/orchestrator/orchestrator_main.py +166 -0
  113. solace_agent_mesh/orchestrator/orchestrator_prompt.py +410 -0
  114. solace_agent_mesh/services/__init__.py +0 -0
  115. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +56 -0
  116. solace_agent_mesh/services/bamboo_hr_service/__init__.py +3 -0
  117. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +182 -0
  118. solace_agent_mesh/services/common/__init__.py +4 -0
  119. solace_agent_mesh/services/common/auto_expiry.py +45 -0
  120. solace_agent_mesh/services/common/singleton.py +18 -0
  121. solace_agent_mesh/services/file_service/__init__.py +14 -0
  122. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  123. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +149 -0
  124. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +162 -0
  125. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +64 -0
  126. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +106 -0
  127. solace_agent_mesh/services/file_service/file_service.py +432 -0
  128. solace_agent_mesh/services/file_service/file_service_constants.py +54 -0
  129. solace_agent_mesh/services/file_service/file_transformations.py +131 -0
  130. solace_agent_mesh/services/file_service/file_utils.py +322 -0
  131. solace_agent_mesh/services/file_service/transformers/__init__.py +5 -0
  132. solace_agent_mesh/services/history_service/__init__.py +3 -0
  133. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  134. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +78 -0
  135. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +167 -0
  136. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +163 -0
  137. solace_agent_mesh/services/history_service/history_service.py +139 -0
  138. solace_agent_mesh/services/llm_service/components/llm_request_component.py +293 -0
  139. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +152 -0
  140. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  141. solace_agent_mesh/services/middleware_service/middleware_service.py +20 -0
  142. solace_agent_mesh/templates/action.py +38 -0
  143. solace_agent_mesh/templates/agent.py +29 -0
  144. solace_agent_mesh/templates/agent.yaml +70 -0
  145. solace_agent_mesh/templates/gateway-config-template.yaml +6 -0
  146. solace_agent_mesh/templates/gateway-default-config.yaml +28 -0
  147. solace_agent_mesh/templates/gateway-flows.yaml +81 -0
  148. solace_agent_mesh/templates/gateway-header.yaml +16 -0
  149. solace_agent_mesh/templates/gateway_base.py +15 -0
  150. solace_agent_mesh/templates/gateway_input.py +98 -0
  151. solace_agent_mesh/templates/gateway_output.py +71 -0
  152. solace_agent_mesh/templates/plugin-pyproject.toml +30 -0
  153. solace_agent_mesh/templates/rest-api-default-config.yaml +23 -0
  154. solace_agent_mesh/templates/rest-api-flows.yaml +80 -0
  155. solace_agent_mesh/templates/slack-default-config.yaml +9 -0
  156. solace_agent_mesh/templates/slack-flows.yaml +90 -0
  157. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +77 -0
  158. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +8 -0
  159. solace_agent_mesh/templates/web-default-config.yaml +5 -0
  160. solace_agent_mesh/templates/web-flows.yaml +86 -0
  161. solace_agent_mesh/tools/__init__.py +0 -0
  162. solace_agent_mesh/tools/components/__init__.py +0 -0
  163. solace_agent_mesh/tools/components/conversation_formatter.py +111 -0
  164. solace_agent_mesh/tools/components/file_resolver_component.py +58 -0
  165. solace_agent_mesh/tools/config/runtime_config.py +26 -0
  166. solace_agent_mesh-0.1.0.dist-info/METADATA +179 -0
  167. solace_agent_mesh-0.1.0.dist-info/RECORD +170 -0
  168. solace_agent_mesh-0.1.0.dist-info/entry_points.txt +3 -0
  169. solace_agent_mesh-0.0.1.dist-info/licenses/LICENSE.txt → solace_agent_mesh-0.1.0.dist-info/licenses/LICENSE +1 -2
  170. solace_agent_mesh-0.0.1.dist-info/METADATA +0 -51
  171. solace_agent_mesh-0.0.1.dist-info/RECORD +0 -5
  172. {solace_agent_mesh-0.0.1.dist-info → solace_agent_mesh-0.1.0.dist-info}/WHEEL +0 -0
@@ -1,3 +0,0 @@
1
- # Internal components that are dynamically loaded by the AI Connector
2
- # Listing them here allows for them to use relative imports
3
- __version__ = "0.0.1"
File without changes
@@ -0,0 +1,224 @@
1
+ """This is the base class for all custom agent components"""
2
+
3
+ import json
4
+ import traceback
5
+
6
+ import os
7
+ from abc import ABC
8
+ from solace_ai_connector.common.log import log
9
+ from solace_ai_connector.common.message import Message
10
+ from solace_ai_connector.common.utils import ensure_slash_on_end
11
+
12
+ from ..services.llm_service.components.llm_service_component_base import LLMServiceComponentBase
13
+ from ..common.action_list import ActionList
14
+ from ..common.action_response import ActionResponse, ErrorInfo
15
+ from ..services.file_service import FileService
16
+ from ..services.file_service.file_utils import recursive_file_resolver
17
+ from ..services.middleware_service.middleware_service import MiddlewareService
18
+
19
+ agent_info = {
20
+ "class_name": "BaseAgentComponent",
21
+ "description": "This component handles action requests",
22
+ "config_parameters": [
23
+ {
24
+ "name": "llm_service_topic",
25
+ "required": False,
26
+ "description": "The topic to use for the LLM service",
27
+ },
28
+ {
29
+ "name": "embedding_service_topic",
30
+ "required": False,
31
+ "description": "The topic to use for the Embedding service",
32
+ },
33
+ {
34
+ "name": "registration_interval",
35
+ "required": False,
36
+ "description": "The interval in seconds for agent registration",
37
+ "default": 30,
38
+ },
39
+ ],
40
+ "input_schema": {
41
+ "type": "object",
42
+ "properties": {
43
+ "agent_name": {"type": "string"},
44
+ "action_name": {"type": "string"},
45
+ "params": {"type": "object", "additionalProperties": True},
46
+ },
47
+ "required": ["agent_name", "action_name", "params"],
48
+ },
49
+ }
50
+
51
+
52
+ class BaseAgentComponent(LLMServiceComponentBase, ABC):
53
+
54
+ @classmethod
55
+ def get_actions_list(cls, **kwargs):
56
+ return ActionList(cls.actions, **kwargs)
57
+
58
+
59
+ def __init__(self, module_info={}, **kwargs):
60
+ super().__init__(module_info, **kwargs)
61
+ self.kwargs = kwargs
62
+ self.action_config = kwargs.get("action_config", {})
63
+ self.registration_interval = int(self.get_config("registration_interval", 30))
64
+
65
+ self.llm_service_topic = self.get_config("llm_service_topic")
66
+ if self.llm_service_topic:
67
+ self.llm_service_topic = ensure_slash_on_end(self.llm_service_topic)
68
+ # Check that the component's broker request/response is enabled
69
+ if not self.is_broker_request_response_enabled():
70
+ raise ValueError(
71
+ "LLM service topic is set, but the component does not "
72
+ f"have its broker request/response enabled, {self.__class__.__name__}"
73
+ )
74
+
75
+ self.embedding_service_topic = self.get_config("embedding_service_topic")
76
+ if self.embedding_service_topic:
77
+ self.embedding_service_topic = ensure_slash_on_end(
78
+ self.embedding_service_topic
79
+ )
80
+ # Check that the component's broker request/response is enabled
81
+ if not self.is_broker_request_response_enabled():
82
+ raise ValueError(
83
+ "Embedding service topic is set, but the component does not "
84
+ f"have its broker request/response enabled, {self.__class__.__name__}"
85
+ )
86
+
87
+ self.action_list = self.get_actions_list(agent=self, config_fn=self.get_config)
88
+
89
+ def run(self):
90
+ # This is called when the component is started - we will use this to send the first registration message
91
+ # Only do this for the first of the agent components
92
+ if self.component_index == 0:
93
+ # Send the registration message immediately - this will also schedule the timer
94
+ self.handle_timer_event(None)
95
+
96
+ # Call the base class run method
97
+ super().run()
98
+
99
+ def get_actions_summary(self):
100
+ action_list = self.action_list
101
+ return action_list.get_prompt_summary(prefix=self.info.get("agent_name"))
102
+
103
+ def get_agent_summary(self):
104
+ return {
105
+ "agent_name": self.info["agent_name"],
106
+ "description": self.info["description"],
107
+ "always_open": self.info.get("always_open", False),
108
+ "actions": self.get_actions_summary(),
109
+ }
110
+
111
+ def invoke(self, message, data):
112
+ """Invoke the component"""
113
+ action_name = data.get("action_name")
114
+ action_response = None
115
+ file_service = FileService()
116
+ if not action_name:
117
+ log.error("Action name not provided. Data: %s", json.dumps(data))
118
+ action_response = ActionResponse(
119
+ message="Internal error: Action name not provided. Please try again",
120
+ )
121
+ else:
122
+ action = self.action_list.get_action(action_name)
123
+ if not action:
124
+ log.error(
125
+ "Action not found: %s. Data: %s", action_name, json.dumps(data)
126
+ )
127
+ action_response = ActionResponse(
128
+ message="Internal error: Action not found. Please try again",
129
+ )
130
+ else:
131
+ resolved_params = data.get("action_params", {}).copy()
132
+ try:
133
+ session_id = (message.get_user_properties() or {}).get("session_id")
134
+ resolved_params = recursive_file_resolver(
135
+ resolved_params,
136
+ resolver=file_service.resolve_all_resolvable_urls,
137
+ session_id=session_id,
138
+ )
139
+ except Exception as e:
140
+ log.error(
141
+ "Error resolving file service URLs: %s. Data: %s",
142
+ str(e),
143
+ json.dumps(data),
144
+ exc_info=True,
145
+ )
146
+ action_response = ActionResponse(
147
+ message=f"Error resolving file URLs. Details: {str(e)}",
148
+ )
149
+
150
+ middleware_service = MiddlewareService()
151
+ if middleware_service.get("base_agent_filter")(message.user_properties or {}, action):
152
+ try:
153
+ meta = {
154
+ "session_id": session_id,
155
+ }
156
+ action_response = action.invoke(resolved_params, meta)
157
+ except Exception as e:
158
+
159
+ error_message = (
160
+ f"Error invoking action {action_name} "
161
+ f"in agent {self.info.get('agent_name', 'Unknown')}: \n\n"
162
+ f"Exception name: {type(e).__name__}\n"
163
+ f"Exception info: {str(e)}\n"
164
+ f"Stack trace: {traceback.format_exc()}\n\n"
165
+ f"Data: {json.dumps(data)}"
166
+ )
167
+ log.error(error_message)
168
+ action_response = ActionResponse(
169
+ message=f"Internal error: {type(e).__name__} - Error invoking action. Details: {str(e)}",
170
+ error_info=ErrorInfo(
171
+ error_message=error_message,
172
+ ),
173
+ )
174
+ else:
175
+ log.warning(
176
+ "Unauthorized access attempt for action %s. Data: %s",
177
+ action_name,
178
+ json.dumps(data),
179
+ )
180
+ action_response = ActionResponse(
181
+ message="Unauthorized: You don't have permission to perform this action.",
182
+ )
183
+
184
+ action_response.action_list_id = data.get("action_list_id")
185
+ action_response.action_idx = data.get("action_idx")
186
+ action_response.action_name = action_name
187
+ action_response.action_params = data.get("action_params", {})
188
+ try:
189
+ action_response_dict = action_response.to_dict()
190
+ except Exception as e:
191
+ log.error(
192
+ "Error after action %s in converting action response to dict: %s. Data: %s",
193
+ action_name,
194
+ str(e),
195
+ json.dumps(data),
196
+ exc_info=True,
197
+ )
198
+ action_response_dict = {
199
+ "message": "Internal error: Error converting action response to dict",
200
+ }
201
+
202
+ # Construct the response topic
203
+ response_topic = f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/actionResponse/agent/{self.info['agent_name']}/{action_name}"
204
+
205
+ return {"payload": action_response_dict, "topic": response_topic}
206
+
207
+ def handle_timer_event(self, timer_data):
208
+ """Handle the timer event for agent registration."""
209
+ registration_message = self.get_agent_summary()
210
+ registration_topic = f"{os.getenv('SOLACE_AGENT_MESH_NAMESPACE')}solace-agent-mesh/v1/register/agent/{self.info['agent_name']}"
211
+
212
+ message = Message(
213
+ topic=registration_topic,
214
+ payload=registration_message,
215
+ )
216
+
217
+ message.set_previous(
218
+ {"topic": registration_topic, "payload": registration_message}
219
+ )
220
+
221
+ self.send_message(message)
222
+
223
+ # Re-schedule the timer
224
+ self.add_timer(self.registration_interval * 1000, "agent_registration")
File without changes
File without changes
@@ -0,0 +1,54 @@
1
+ """Agent state change action"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ from ....common.action import Action
5
+ from ....common.action_response import (
6
+ ActionResponse,
7
+ AgentStateChange,
8
+ ErrorInfo,
9
+ )
10
+
11
+
12
+ class AgentStateChangeAction(Action):
13
+
14
+ def __init__(self, **kwargs):
15
+ super().__init__(
16
+ {
17
+ "name": "change_agent_status",
18
+ "prompt_directive": "Handle agent state change",
19
+ "params": [
20
+ {
21
+ "name": "agent_name",
22
+ "desc": "Name of the agent",
23
+ "type": "string",
24
+ },
25
+ {
26
+ "name": "new_state",
27
+ "desc": "New state of the agent: open or closed",
28
+ "type": "string",
29
+ },
30
+ ],
31
+ "required_scopes": ["*:*:*"],
32
+ },
33
+ **kwargs,
34
+ )
35
+
36
+ def invoke(self, params, meta={}) -> ActionResponse:
37
+ agent_name = params["agent_name"]
38
+ new_state = params["new_state"]
39
+ log.debug("Handling agent state change: %s -> %s", agent_name, new_state)
40
+
41
+ if new_state == "open":
42
+ return ActionResponse(
43
+ message=f"_Opening agent {agent_name}..._",
44
+ agent_state_change=AgentStateChange(agent_name, "open"),
45
+ )
46
+ elif new_state == "closed":
47
+ return ActionResponse(
48
+ agent_state_change=AgentStateChange(agent_name, "closed"),
49
+ )
50
+ else:
51
+ log.error("Invalid agent state: %s", new_state)
52
+ return ActionResponse(
53
+ error_info=ErrorInfo(error_message=f"Invalid agent state for {agent_name}: {new_state}"),
54
+ )
@@ -0,0 +1,32 @@
1
+ """Clear history action"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ from ....common.action import Action
5
+ from ....common.action_response import ActionResponse
6
+
7
+
8
+ class ClearHistory(Action):
9
+ def __init__(self, **kwargs):
10
+ super().__init__(
11
+ {
12
+ "name": "clear_history",
13
+ "prompt_directive": "Clear the chat history",
14
+ "params": [
15
+ {
16
+ "name": "depth_to_keep",
17
+ "desc": "The number of messages to keep in the chat history. If 0, clear all history. ",
18
+ "type": "integer",
19
+ }
20
+ ],
21
+ "required_scopes": ["*:*:*"],
22
+ },
23
+ **kwargs,
24
+ )
25
+
26
+ def invoke(self, params, meta={}) -> ActionResponse:
27
+ log.debug("Clearing chat history")
28
+ depth_to_keep = params.get("depth_to_keep", 0)
29
+ return ActionResponse(
30
+ clear_history=True,
31
+ history_depth_to_keep=depth_to_keep,
32
+ )
@@ -0,0 +1,160 @@
1
+ """Action description"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ from markitdown import MarkItDown
5
+ from markitdown import UnsupportedFormatException
6
+ import json
7
+ import os
8
+ import mimetypes
9
+ import tempfile
10
+ from ....common.action import Action
11
+ from ....common.action_response import ActionResponse
12
+ from ....services.file_service import FS_PROTOCOL, FileService
13
+
14
+ STRIP_LENGTH = len(FS_PROTOCOL) + len("://") + 36 + 1 # 36 is uuid, 1 is underscore
15
+
16
+
17
+ class ConvertFileToMarkdown(Action):
18
+
19
+ def __init__(self, **kwargs):
20
+ super().__init__(
21
+ {
22
+ "name": "convert_file_to_markdown",
23
+ "prompt_directive": (
24
+ "Convert various file formats to Markdown using the MarkItDown utility. "
25
+ "Supported file types include PDF, Word, Excel, HTML, CSV and PPTX "
26
+ "and ZIP files. Files are provided in "
27
+ + f"{FS_PROTOCOL}://"
28
+ + " format. "
29
+ "JSON and XML files are not supported. "
30
+ "This action will return Markdown files."
31
+ ),
32
+ "params": [
33
+ {
34
+ "name": "files",
35
+ "desc": (
36
+ f"List of {FS_PROTOCOL}:// URLs to the files to convert to Markdown. "
37
+ f"For example: ['{FS_PROTOCOL}://file1.pdf', '{FS_PROTOCOL}://file2.docx']"
38
+ ),
39
+ "type": "array",
40
+ }
41
+ ],
42
+ "required_scopes": ["global:markitdown:execute"],
43
+ },
44
+ **kwargs,
45
+ )
46
+
47
+ def invoke(self, params, meta={}) -> ActionResponse:
48
+ files = params.get("files", [])
49
+ session_id = meta.get("session_id")
50
+ log.debug("Doing markitdown convert action: %s", params["files"])
51
+
52
+ if isinstance(files, str):
53
+ try:
54
+ files = json.loads(files)
55
+ except json.JSONDecodeError:
56
+ log.error("Failed to decode files string as JSON")
57
+ return ActionResponse(message="Error: File URL list is not valid.")
58
+
59
+ return self.do_action(files, session_id)
60
+
61
+ def extract_file_format(self, file_path: str) -> str:
62
+ """
63
+ Extracts the file format from a file path or guesses it using mimetypes if not present.
64
+ """
65
+ file_extension = os.path.splitext(file_path)[1]
66
+ if file_extension:
67
+ return file_extension.lower()
68
+
69
+ mime_type, _ = mimetypes.guess_type(file_path)
70
+ if mime_type:
71
+ guessed_extension = mimetypes.guess_extension(mime_type)
72
+ if guessed_extension:
73
+ return guessed_extension.lower()
74
+
75
+ raise ValueError(f"Unable to determine file format for path: {file_path}")
76
+
77
+ def do_action(self, files, session_id) -> ActionResponse:
78
+ markitdown_client = MarkItDown()
79
+ file_service = FileService()
80
+
81
+ response_messages = []
82
+ uploaded_files = []
83
+
84
+ # Iterate over each file URL
85
+ for file_url in files:
86
+ try:
87
+ file_buffer = file_service.download_to_buffer(file_url, session_id)
88
+ except Exception as e:
89
+ log.error(
90
+ "Failed to download file from the url %s: %s", file_url, str(e)
91
+ )
92
+ response_messages.append(
93
+ f"Failed to download file from the url {file_url}"
94
+ )
95
+ continue
96
+
97
+ # Find the file extension
98
+ try:
99
+ file_extension = self.extract_file_format(file_url)
100
+ except ValueError:
101
+ response_messages.append(
102
+ f"Could not determine file format for: {file_url}"
103
+ )
104
+ continue
105
+
106
+ # strip first {STRIP LENGTH} characters of the url to get the original name
107
+ original_name = file_url[STRIP_LENGTH:]
108
+
109
+ with tempfile.NamedTemporaryFile(
110
+ delete=True, suffix=file_extension
111
+ ) as temporary_file:
112
+ try:
113
+ # Write to temp file
114
+ temporary_file.write(file_buffer)
115
+ temporary_file.flush()
116
+
117
+ # Convert the file to Markdown
118
+ try:
119
+ conversion_result = markitdown_client.convert(
120
+ temporary_file.name
121
+ )
122
+ # Empty content
123
+ if not conversion_result.text_content:
124
+ log.error("Error converting file %s: Conversion resulted in empty content", file_url)
125
+ response_messages.append(f"Could not convert file: {file_url} - conversion resulted in empty content")
126
+ continue
127
+ except UnsupportedFormatException as e:
128
+ log.error("Error converting file %s: %s", file_url, str(e))
129
+ response_messages.append(
130
+ f"Could not convert file: {file_url} as this format is not supported: {str(e)}"
131
+ )
132
+ continue
133
+
134
+ # Use the original file name + "_converted" affix + .md extension
135
+ converted_filename = f"{original_name}_converted.md"
136
+
137
+ # Upload the converted content
138
+ file_obj = file_service.upload_from_buffer(
139
+ conversion_result.text_content.encode("utf-8"),
140
+ converted_filename,
141
+ session_id,
142
+ data_source="Global Agent - MarkItDown Action",
143
+ )
144
+
145
+ filename = file_obj["name"]
146
+ uploaded_files.append(file_obj)
147
+ response_messages.append(f"File converted successfully: {filename}")
148
+
149
+ except Exception as e:
150
+ log.error(
151
+ "Failed to upload file %s: %s", converted_filename, str(e)
152
+ )
153
+ response_messages.append(
154
+ f"Failed to upload file {converted_filename}"
155
+ )
156
+ continue
157
+
158
+ return ActionResponse(
159
+ message="\n".join(response_messages), files=uploaded_files
160
+ )
@@ -0,0 +1,70 @@
1
+ """Create a new file in file service"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+ from ....common.action import Action
5
+ from ....common.action_response import ActionResponse
6
+ from ....services.file_service import FS_PROTOCOL, FileService
7
+ from ....services.file_service.file_utils import starts_with_fs_url
8
+
9
+
10
+ class CreateFile(Action):
11
+ """Action to create a new file in file service"""
12
+
13
+ def __init__(self, **kwargs):
14
+
15
+ super().__init__(
16
+ {
17
+ "name": "create_file",
18
+ "prompt_directive": f"Creates a new persisted file and returns the file block with {FS_PROTOCOL} URL",
19
+ "params": [
20
+ {
21
+ "name": "name",
22
+ "desc": "File name (required) - must have an extension. Eg: file.csv",
23
+ "type": "string",
24
+ },
25
+ {
26
+ "name": "content",
27
+ "desc": "Content of the file (required)",
28
+ "type": "string",
29
+ },
30
+ ],
31
+ "required_scopes": ["global:create_file:write"],
32
+ },
33
+ **kwargs,
34
+ )
35
+
36
+ def invoke(self, params, meta={}) -> ActionResponse:
37
+ name: str = params.get("name")
38
+ content: str = params.get("content")
39
+ session_id: str = meta.get("session_id")
40
+
41
+ if not name or not content:
42
+ return ActionResponse(message="Error: name and content are required.")
43
+
44
+ try:
45
+ # Upload the file
46
+ file_service = FileService()
47
+
48
+ updated_content = content
49
+ if starts_with_fs_url(updated_content):
50
+ updated_content = file_service.resolve_url(updated_content, session_id)
51
+ else:
52
+ updated_content = file_service.resolve_all_resolvable_urls(updated_content, session_id)
53
+
54
+ if isinstance(updated_content, str):
55
+ updated_content = updated_content.encode("utf-8")
56
+
57
+ file_meta = file_service.upload_from_buffer(
58
+ updated_content,
59
+ name,
60
+ session_id,
61
+ data_source="Global Agent - Create File Action",
62
+ )
63
+ except Exception as e:
64
+ log.error("Failed to upload file %s: %s", name, str(e))
65
+ return ActionResponse(message=f"Failed to upload the file: {name}")
66
+
67
+ file_block = file_service.get_file_block_by_metadata(file_meta)
68
+ return ActionResponse(
69
+ message=f"Following file has been created:\n\n{file_block}",
70
+ )
@@ -0,0 +1,45 @@
1
+ """Action to handle errors from the system"""
2
+
3
+ from solace_ai_connector.common.log import log
4
+
5
+ from ....common.action import Action
6
+ from ....common.action_response import (
7
+ ActionResponse,
8
+ ErrorInfo,
9
+ WithContextQuery,
10
+ )
11
+
12
+
13
+ class ErrorAction(Action):
14
+ def __init__(self, **kwargs):
15
+ super().__init__(
16
+ {
17
+ "name": "error_action",
18
+ "prompt_directive": "Raise an error message",
19
+ "params": [
20
+ {
21
+ "name": "error_message",
22
+ "desc": "The error to send to the user",
23
+ "type": "string",
24
+ }
25
+ ],
26
+ "required_scopes": ["*:*:*"],
27
+ },
28
+ **kwargs,
29
+ )
30
+
31
+ def invoke(self, params, meta={}) -> ActionResponse:
32
+ error_message = params.get("error_message")
33
+ if error_message:
34
+ log.error("Raising error: %s", error_message)
35
+ # If the error_message contains "Failed to parse response",
36
+ # then return a response requesting a redo from the LLM
37
+ if "Failed to parse response" in error_message:
38
+ return ActionResponse(
39
+ context_query=WithContextQuery(
40
+ context_type="statement",
41
+ context="Your response was not correctly formated and parsing failed. Please try again but don't comment on the parsing problem in your response.",
42
+ query="",
43
+ )
44
+ )
45
+ return ActionResponse(error_info=ErrorInfo(error_message=error_message))