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
@@ -0,0 +1,265 @@
1
+ from typing import Dict, Any
2
+ import base64
3
+ import json
4
+ from uuid import uuid4
5
+
6
+ from solace_ai_connector.common.message import Message
7
+ from solace_ai_connector.common.log import log
8
+ from ...services.file_service import FileService
9
+ from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
10
+ from .gateway_base import GatewayBase
11
+
12
+ info = {
13
+ "class_name": "GatewayInput",
14
+ "description": (
15
+ "This component handles requests from users and forms the "
16
+ "appropriate prompt for the next component in the flow."
17
+ ),
18
+ "config_parameters": [
19
+ {
20
+ "name": "gateway_config",
21
+ "type": "object",
22
+ "properties": {
23
+ "gateway_id": {"type": "string"},
24
+ "system_purpose": {"type": "string"},
25
+ "interaction_type": {"type": "string"},
26
+ "history_class": {"type": "string"},
27
+ "history_config": {"type": "object"},
28
+ "identity_key_field": {"type": "string", "default": DEFAULT_IDENTITY_KEY_FIELD},
29
+ "identity": {
30
+ "type": "object",
31
+ "properties": {
32
+ "type": {"type": "string"},
33
+ "module": {"type": "string"},
34
+ "configuration": {"type": "object"},
35
+ },
36
+ "description": "Identity component configuration including module and class names.",
37
+ },
38
+ },
39
+ "description": "Gateway configuration including originators and their configurations.",
40
+ },
41
+ ],
42
+ "input_schema": {
43
+ "type": "object",
44
+ "properties": {
45
+ "event": {
46
+ "type": "object",
47
+ "properties": {
48
+ "text": {"type": "string"},
49
+ "files": {
50
+ "type": "array",
51
+ "items": {
52
+ "type": "object",
53
+ "properties": {
54
+ "name": {
55
+ "type": "string",
56
+ },
57
+ "content": {
58
+ "type": "string",
59
+ },
60
+ "mime_type": {"type": "string"},
61
+ "url": {
62
+ "type": "string",
63
+ },
64
+ "size": {
65
+ "type": "number",
66
+ },
67
+ },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ "required": ["event"],
74
+ },
75
+ "output_schema": {
76
+ "type": "object",
77
+ "properties": {
78
+ "files": {
79
+ "type": "array",
80
+ "items": {
81
+ "type": "object",
82
+ "properties": {
83
+ "name": {
84
+ "type": "string",
85
+ },
86
+ "content": {
87
+ "type": "string",
88
+ },
89
+ "mime_type": {
90
+ "type": "string",
91
+ },
92
+ "url": {
93
+ "type": "string",
94
+ },
95
+ "file_size": {
96
+ "type": "number",
97
+ },
98
+ },
99
+ },
100
+ },
101
+ "text": {"type": "string"},
102
+ "interface_properties": {"type": "object"},
103
+ "history": {
104
+ "type": "array",
105
+ "items": {
106
+ "type": "object",
107
+ "properties": {
108
+ "role": {"type": "string"},
109
+ "content": {"type": "string"},
110
+ },
111
+ },
112
+ },
113
+ },
114
+ "required": ["session_id", "clear_history", "messages"],
115
+ },
116
+ }
117
+
118
+ DEFAULT_SYSTEM_PURPOSE = "You are participating in the chat bot framework."
119
+ DEFAULT_INTERACTION_TYPE = "interactive" # vs "autonomous"
120
+
121
+
122
+ class GatewayInput(GatewayBase):
123
+ """This component handles incoming stimuli and prepares the context for processing."""
124
+
125
+ def __init__(self, **kwargs):
126
+ super().__init__(info, **kwargs)
127
+ self.system_purpose = self.get_config("system_purpose", DEFAULT_SYSTEM_PURPOSE)
128
+ self.interaction_type = self.get_config(
129
+ "interaction_type", DEFAULT_INTERACTION_TYPE
130
+ )
131
+ self.identity_component = self._initialize_identity_component()
132
+
133
+ def _authenticate_user(self, _user_properties: Dict[str, Any]) -> bool:
134
+ # Implement actual authentication logic here
135
+ return True
136
+
137
+ def invoke(self, message: Message, data: Dict[str, Any]) -> Dict[str, Any]:
138
+ user_properties = message.get_user_properties() or {}
139
+ copied_data = data.copy()
140
+
141
+ errors = []
142
+ available_files = []
143
+
144
+ try:
145
+ if not self._authenticate_user(user_properties):
146
+ log.error("User authentication failed")
147
+ raise PermissionError("User authentication failed")
148
+
149
+ # Get identity info
150
+ identity_field = self.identity_component.get_identity_field()
151
+ identity_value = user_properties.get(identity_field)
152
+ if not identity_value:
153
+ log.error("Identity field '%s' not found", identity_field)
154
+ raise ValueError(f"Identity field '{identity_field}' not found")
155
+
156
+ user_info = self.identity_component.get_user_info(identity_value)
157
+
158
+ session_id = user_properties.get("session_id")
159
+
160
+ # De-clutter the data and user_properties by moving some properties to a nested sub-object
161
+ top_level_data_properties = {"text", "files"}
162
+ self.demote_interface_properties(copied_data, top_level_data_properties)
163
+
164
+ top_level_user_properties = {
165
+ "input_type",
166
+ "session_id",
167
+ "response_format_prompt",
168
+ }
169
+ self.demote_interface_properties(user_properties, top_level_user_properties)
170
+
171
+ prompt = data.get("text", "")
172
+ files = data.get("files", [])
173
+
174
+ attached_files = []
175
+ if len(files) > 0:
176
+ file_service = FileService()
177
+ for file in files:
178
+ content = file["content"]
179
+ if type(content) == str:
180
+ try:
181
+ byte_buffer = base64.b64decode(content)
182
+ except Exception as e:
183
+ byte_buffer = content.encode("utf-8")
184
+ elif type(content) == bytes:
185
+ byte_buffer = content
186
+ else:
187
+ log.error(
188
+ "Invalid content type for file %s: %s",
189
+ file["name"],
190
+ type(content),
191
+ )
192
+ continue
193
+
194
+ file_metadata = file_service.upload_from_buffer(
195
+ byte_buffer,
196
+ file["name"],
197
+ session_id,
198
+ file_size=file["size"],
199
+ data_source=f"User provided file - {self.gateway_id} Gateway",
200
+ )
201
+ attached_files.append(file_metadata)
202
+ copied_data["files"] = attached_files
203
+
204
+ copied_data["history"] = []
205
+ if self.use_history:
206
+ prompt = data.get("text", "")
207
+ self.history_instance.store_history(session_id, "user", prompt)
208
+
209
+ for file in attached_files:
210
+ self.history_instance.store_file(session_id, file)
211
+
212
+ # retrieve all files for the session
213
+ available_files = self.history_instance.get_files(session_id)
214
+
215
+ # Add history to the data
216
+ copied_data["history"] = self.history_instance.get_history(session_id)
217
+
218
+ available_files = json.dumps(available_files)
219
+ except Exception as e:
220
+ log.error("Error processing input: %s", e)
221
+ errors.append(str(e))
222
+
223
+ stimulus_uuid = self.gateway_id + str(uuid4())
224
+
225
+ user_properties.update(
226
+ {
227
+ "gateway_id": self.gateway_id,
228
+ "system_purpose": self.system_purpose,
229
+ "interaction_type": self.interaction_type,
230
+ "available_files": available_files,
231
+ "stimulus_uuid": stimulus_uuid,
232
+ "user_info": user_info,
233
+ "identity": identity_value,
234
+ }
235
+ )
236
+ copied_data["user_info"] = user_info
237
+ copied_data["errors"] = errors
238
+ message.set_user_properties(user_properties)
239
+ message.set_payload(copied_data)
240
+
241
+ return copied_data
242
+
243
+ def demote_interface_properties(
244
+ self, dict_properties: Dict[str, Any], top_level_properties: set
245
+ ):
246
+ """
247
+ Demotes specified properties from the top level of a dictionary to a nested dictionary under the key 'interface_properties'.
248
+
249
+ Args:
250
+ dict_properties (Dict[str, Any]): The dictionary containing the properties.
251
+ top_level_properties (set): A set of property names that should remain at the top level.
252
+
253
+ Modifies:
254
+ dict_properties: Adds a new key 'interface_properties' containing the properties that are not in top_level_properties.
255
+ Removes the demoted properties from the top level of dict_properties.
256
+ """
257
+
258
+ interface_properties = {
259
+ k: v for k, v in dict_properties.items() if k not in top_level_properties
260
+ }
261
+ dict_properties["interface_properties"] = interface_properties
262
+
263
+ # Remove keys from user_properties that are in interface_user_properties
264
+ for key in interface_properties:
265
+ dict_properties.pop(key, None)
@@ -0,0 +1,289 @@
1
+ import base64
2
+ from solace_ai_connector.common.message import Message
3
+ from solace_ai_connector.common.log import log
4
+
5
+ from .gateway_base import GatewayBase
6
+ from ...services.file_service import FileService
7
+ from ...common.utils import files_to_block_text
8
+
9
+
10
+ info = {
11
+ "class_name": "GatewayOutput",
12
+ "description": (
13
+ "This component handles stimuli from users and forms the "
14
+ "appropriate prompt for the next component in the flow."
15
+ ),
16
+ "config_parameters": [
17
+ {
18
+ "name": "gateway_config",
19
+ "type": "object",
20
+ "properties": {
21
+ "gateway_id": {"type": "string"},
22
+ "system_purpose": {"type": "string"},
23
+ "interaction_type": {"type": "string"},
24
+ "default_originator_scopes": {
25
+ "type": "array",
26
+ "items": {"type": "string"},
27
+ },
28
+ },
29
+ "description": "Gateway configuration including originators and their configurations.",
30
+ },
31
+ ],
32
+ "input_schema": {
33
+ "type": "object",
34
+ "properties": {
35
+ "event": {
36
+ "type": "object",
37
+ "properties": {
38
+ "text": {
39
+ "type": "string",
40
+ },
41
+ "files": {
42
+ "type": "array",
43
+ "items": {
44
+ "type": "object",
45
+ "properties": {
46
+ "name": {
47
+ "type": "string",
48
+ },
49
+ "data": {
50
+ "type": "string",
51
+ },
52
+ "mime_type": {
53
+ "type": "string",
54
+ },
55
+ "url": {
56
+ "type": "string",
57
+ },
58
+ "file_size": {
59
+ "type": "number",
60
+ },
61
+ },
62
+ },
63
+ },
64
+ "first_chunk": {
65
+ "type": "boolean",
66
+ },
67
+ "last_chunk": {
68
+ "type": "boolean",
69
+ },
70
+ "uuid": {
71
+ "type": "string",
72
+ },
73
+ "chunk": {
74
+ "type": "text",
75
+ },
76
+ "status_update": {
77
+ "type": "boolean",
78
+ },
79
+ },
80
+ },
81
+ },
82
+ },
83
+ "output_schema": {
84
+ "type": "object",
85
+ "properties": {
86
+ "event": {
87
+ "type": "object",
88
+ "properties": {
89
+ "text": {
90
+ "type": "string",
91
+ },
92
+ "files": {
93
+ "type": "array",
94
+ "items": {
95
+ "type": "object",
96
+ "properties": {
97
+ "name": {
98
+ "type": "string",
99
+ },
100
+ "data": {
101
+ "type": "string",
102
+ },
103
+ "mime_type": {
104
+ "type": "string",
105
+ },
106
+ "url": {
107
+ "type": "string",
108
+ },
109
+ "file_size": {
110
+ "type": "number",
111
+ },
112
+ },
113
+ },
114
+ },
115
+ "first_chunk": {
116
+ "type": "boolean",
117
+ },
118
+ "last_chunk": {
119
+ "type": "boolean",
120
+ },
121
+ "uuid": {
122
+ "type": "string",
123
+ },
124
+ "chunk": {
125
+ "type": "text",
126
+ },
127
+ "status_update": {
128
+ "type": "boolean",
129
+ },
130
+ },
131
+ },
132
+ },
133
+ },
134
+ }
135
+
136
+
137
+ class GatewayOutput(GatewayBase):
138
+ """This is the component that handles incoming stimuli"""
139
+
140
+ def __init__(self, **kwargs):
141
+ super().__init__(info, **kwargs)
142
+ self.default_agent_scopes = self.get_config("default_agent_scopes", [])
143
+ self.originators = self.get_config("originators", [])
144
+
145
+ def _resolve_text_content(self, data: dict, session_id: str) -> None:
146
+ """
147
+ Resolve all AMFS URLs in text and chunk fields of the data dictionary.
148
+
149
+ Args:
150
+ data (dict): The data dictionary containing text and chunk fields
151
+ session_id (str): The session ID for file service operations
152
+
153
+ Returns:
154
+ None: Modifies the data dictionary in place
155
+ """
156
+ try:
157
+ file_service = FileService()
158
+ text_output_response = data.get("text", "") or ""
159
+ data["text"] = file_service.resolve_all_resolvable_urls(
160
+ text_output_response, session_id
161
+ )
162
+ chunk_output_response = data.get("chunk", "") or ""
163
+ data["chunk"] = file_service.resolve_all_resolvable_urls(
164
+ chunk_output_response, session_id
165
+ )
166
+ except Exception as e:
167
+ log.error(f"Failed to resolve URLs in text: {e}")
168
+
169
+ def invoke(self, message: Message, data) -> Message:
170
+ file_service = FileService()
171
+ user_properties = message.get_user_properties()
172
+ session_id = user_properties.get("session_id")
173
+ files = data.get("files", [])
174
+
175
+ # Extract the interface queue ID
176
+ interface_properties = user_properties.get("interface_properties", [])
177
+ server_input_id = (
178
+ interface_properties and interface_properties.get("server_input_id", "")
179
+ if interface_properties
180
+ else ""
181
+ )
182
+
183
+ if self.use_history and session_id:
184
+ topic = message.get_topic()
185
+ content = data.get("text") or ""
186
+
187
+ file_text_blocks = files_to_block_text(files)
188
+ if file_text_blocks:
189
+ content += file_text_blocks
190
+
191
+ if (
192
+ "/streamingResponse/" in topic
193
+ and data.get("last_chunk")
194
+ and "text" in data
195
+ ):
196
+ if content:
197
+ self.history_instance.store_history(
198
+ session_id, "assistant", content
199
+ )
200
+
201
+ for file in files:
202
+ self.history_instance.store_history(
203
+ session_id, "assistant", f'\n[Returned file: {{name: {file.get("name")}, url: {file.get("url")}}}]\n'
204
+ )
205
+ self.history_instance.store_file(session_id, file)
206
+
207
+ clear_history_tuple = user_properties.get("clear_gateway_history", [])
208
+ if clear_history_tuple and clear_history_tuple[0]:
209
+ keep_depth = clear_history_tuple[1]
210
+ self.history_instance.clear_history(session_id, keep_depth)
211
+
212
+ if files:
213
+ downloaded_files = []
214
+ for file in files:
215
+ output_file = {
216
+ "name": file.get("name"),
217
+ }
218
+ # inline file
219
+ if file.get("data"):
220
+ data_content = file_service.resolve_all_resolvable_urls(
221
+ file.get("data"), session_id
222
+ )
223
+ inline_data = base64.b64encode(data_content.encode()).decode()
224
+ output_file["content"] = inline_data
225
+ output_file["mime_type"] = file.get("mime_type", "text/plain")
226
+ elif file.get("url"):
227
+ url = file.get("url")
228
+ try:
229
+ resolved_content = file_service.resolve_url(url, session_id)
230
+ buffer_content = (
231
+ resolved_content
232
+ if type(resolved_content) == bytes
233
+ else resolved_content.encode()
234
+ )
235
+ output_file["content"] = base64.b64encode(
236
+ buffer_content
237
+ ).decode("utf-8")
238
+
239
+ # If the file name or mime type is not provided, try to get it from the resolved URL
240
+ if not output_file.get("name") or not output_file.get(
241
+ "mime_type"
242
+ ):
243
+ metadata = file_service.get_metadata(url)
244
+ output_file["name"] = output_file.get(
245
+ "name"
246
+ ) or metadata.get("name")
247
+ output_file["mime_type"] = output_file.get(
248
+ "mime_type"
249
+ ) or metadata.get("mime_type")
250
+ except Exception as e:
251
+ log.error(f"Failed to download file {file.get('name')}: {e}")
252
+ continue
253
+ else:
254
+ log.error(f"No file content found for {file.get('name')}")
255
+ continue
256
+ downloaded_files.append(output_file)
257
+
258
+ data["files"] = downloaded_files
259
+ data["server_input_id"] = server_input_id
260
+
261
+ # Promote the interface properties back to the top level of the user_properties
262
+ self.promote_interface_properties(user_properties)
263
+ message.set_user_properties(user_properties)
264
+
265
+ # Resolve inline URLs in text and chunk fields
266
+ self._resolve_text_content(data, session_id)
267
+
268
+ message.set_payload(data)
269
+ return {"payload": data}
270
+
271
+ def promote_interface_properties(self, user_properties):
272
+ """
273
+ Updates the user_properties with the values from the
274
+ "interface_properties" sub-object, if it exists.
275
+
276
+ Args:
277
+ user_properties (dict): A dictionary that may contain an
278
+ "interface_properties" key with a sub-dictionary
279
+ of properties to be merged into the main dictionary.
280
+
281
+ Returns:
282
+ None: The function modifies the user_properties dictionary in place.
283
+ """
284
+ interface_properties = user_properties.get("interface_properties", {})
285
+ if isinstance(interface_properties, dict):
286
+ for key, value in interface_properties.items():
287
+ user_properties[key] = value
288
+
289
+ user_properties.pop("interface_properties", None)
@@ -0,0 +1,18 @@
1
+ import re
2
+ from .identity_base import IdentityBase
3
+ from ...services.bamboo_hr_service.bamboo_hr import BambooHR
4
+
5
+ class BambooHRIdentity(IdentityBase):
6
+ def __init__(self, config: dict):
7
+ super().__init__(config)
8
+ self.bamboo_hr_service = BambooHR(config)
9
+
10
+ def get_user_info(self, identity: str) -> dict:
11
+ if not re.match(r"[^@]+@[^@]+\.[^@]+", identity):
12
+ raise ValueError("Invalid email address")
13
+
14
+ employee_summary = self.bamboo_hr_service.get_employee_summary(identity)
15
+ if employee_summary:
16
+ return employee_summary
17
+
18
+ return {"identity": identity}
@@ -0,0 +1,10 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class IdentityBase(ABC):
4
+ def __init__(self, config: dict):
5
+ self.config = config
6
+
7
+ @abstractmethod
8
+ def get_user_info(self, identity: str) -> dict:
9
+ """Get user information for a given identity."""
10
+ pass
@@ -0,0 +1,60 @@
1
+ import importlib
2
+
3
+ from ...gateway.identity.passthru_identity import PassthruIdentity
4
+ from ...gateway.identity.bamboohr_identity import BambooHRIdentity
5
+ from ...gateway.identity.no_identity import NoIdentity
6
+ from ...gateway.identity.identity_base import IdentityBase
7
+ from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
8
+
9
+ IDENTITY_PROVIDERS = {
10
+ "passthru": PassthruIdentity,
11
+ "bamboohr": BambooHRIdentity,
12
+ "none": NoIdentity,
13
+ }
14
+
15
+ DEFAULT_PROVIDER = "none"
16
+
17
+ class IdentityProvider:
18
+ identity_provider: IdentityBase
19
+
20
+ def __init__(self, config=None):
21
+ self.config = config or {}
22
+ self.provider_type = self.config.get("type", DEFAULT_PROVIDER)
23
+ self.identity_field = self.config.get("key_field", DEFAULT_IDENTITY_KEY_FIELD)
24
+ if self.provider_type not in IDENTITY_PROVIDERS and not self.config.get("module_path"):
25
+ raise ValueError(
26
+ f"Unsupported identity provider type: {self.provider_type}. No module_path provided."
27
+ )
28
+
29
+ provider_configuration = self.config.get("configuration", {})
30
+
31
+ if self.provider_type in IDENTITY_PROVIDERS:
32
+ # Load built-in identity provider
33
+ self.identity_provider = IDENTITY_PROVIDERS[self.provider_type](provider_configuration)
34
+ else:
35
+ try:
36
+ # Load the provider from the module path
37
+ module_name = self.provider_type
38
+ module_path = self.config.get("module_path")
39
+ module = importlib.import_module(module_path, package=__package__)
40
+ identity_class = getattr(module, module_name)
41
+ if not issubclass(identity_class, IdentityBase):
42
+ raise ValueError(
43
+ f"Identity provider class {identity_class} does not inherit from IdentityBase"
44
+ )
45
+ self.identity_provider = identity_class(provider_configuration)
46
+ except Exception as e:
47
+ raise ImportError("Unable to load component: " + str(e)) from e
48
+
49
+ def get_identity_field(self) -> str:
50
+ """Returns the configured field name to use for identity lookup"""
51
+ return self.identity_field
52
+
53
+ def get_user_info(self, identity: str) -> dict:
54
+ """
55
+ Get user information using the configured identity provider.
56
+
57
+ :param identity: The user identity string (usually email)
58
+ :return: Dictionary containing user information
59
+ """
60
+ return self.identity_provider.get_user_info(identity)
@@ -0,0 +1,9 @@
1
+
2
+ from .identity_base import IdentityBase
3
+
4
+ class NoIdentity(IdentityBase):
5
+ def __init__(self, config: dict):
6
+ super().__init__(config)
7
+
8
+ def get_user_info(self, identity: str) -> dict:
9
+ return {}
@@ -0,0 +1,9 @@
1
+
2
+ from .identity_base import IdentityBase
3
+
4
+ class PassthruIdentity(IdentityBase):
5
+ def __init__(self, config: dict):
6
+ super().__init__(config)
7
+
8
+ def get_user_info(self, identity: str) -> dict:
9
+ return {"identity": identity}