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,246 @@
1
+ """This is the component that handles processing all streaming outputs from LLM"""
2
+
3
+ from datetime import datetime
4
+
5
+ from solace_ai_connector.components.component_base import ComponentBase
6
+ from solace_ai_connector.common.log import log
7
+ from solace_ai_connector.common.message import Message
8
+ from ...common.utils import parse_orchestrator_response
9
+ from ...services.history_service import HistoryService
10
+ from ...services.file_service import FileService
11
+ from ...orchestrator.orchestrator_main import (
12
+ ORCHESTRATOR_HISTORY_IDENTIFIER,
13
+ ORCHESTRATOR_HISTORY_CONFIG,
14
+ )
15
+
16
+ info = {
17
+ "class_name": "OrchestratorStreamingOutputComponent",
18
+ "description": ("This component handles all streaming outputs from LLM"),
19
+ "config_parameters": [],
20
+ "input_schema": {
21
+ # A streaming output object - it doesn't have a fixed schema
22
+ "type": "object",
23
+ "additionalProperties": True,
24
+ },
25
+ "output_schema": {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "object",
29
+ "properties": {
30
+ "text": {"type": "string"},
31
+ "streaming": {"type": "boolean"},
32
+ "first_chunk": {"type": "boolean"},
33
+ "last_chunk": {"type": "boolean"},
34
+ "uuid": {"type": "string"},
35
+ },
36
+ "required": [
37
+ "text",
38
+ "streaming",
39
+ "first_chunk",
40
+ "last_chunk",
41
+ "uuid",
42
+ ],
43
+ },
44
+ },
45
+ }
46
+
47
+
48
+ class OrchestratorStreamingOutputComponent(ComponentBase):
49
+ """This is the component that handles processing all streaming outputs from LLM"""
50
+
51
+ def __init__(self, **kwargs):
52
+ super().__init__(info, **kwargs)
53
+ self._response_state = {}
54
+ self.history = HistoryService(
55
+ ORCHESTRATOR_HISTORY_CONFIG, identifier=ORCHESTRATOR_HISTORY_IDENTIFIER
56
+ )
57
+ self.file_service = FileService()
58
+
59
+ def invoke(self, message: Message, data):
60
+ """Handle streaming outputs from LLM"""
61
+ if not data:
62
+ log.error("No data received from LLM")
63
+ self.discard_current_message()
64
+ return None
65
+
66
+ # response complete messages also go through this flow
67
+ # to maintain the order of messages with the streamed responses
68
+ if data.get("response_complete"):
69
+ return [data]
70
+
71
+ user_properties = message.get_user_properties()
72
+ stimulus_uuid = user_properties.get("stimulus_uuid")
73
+ session_id = user_properties.get("session_id")
74
+ text = data.get("content")
75
+ response_uuid = data.get("response_uuid")
76
+ first_chunk = data.get("first_chunk")
77
+ last_chunk = data.get("last_chunk")
78
+
79
+ if first_chunk:
80
+ response_state = self.add_response_state(response_uuid)
81
+ else:
82
+ response_state = self.get_response_state(response_uuid)
83
+ if not response_state:
84
+ # Just discard the data
85
+ self.discard_current_message()
86
+ return None
87
+ if last_chunk:
88
+ self.delete_response_state(response_uuid)
89
+ if stimulus_uuid:
90
+ self.history.store_history(stimulus_uuid, "assistant", text)
91
+
92
+ obj = parse_orchestrator_response(text, last_chunk=last_chunk)
93
+
94
+ if not obj or isinstance(obj, str) or not obj.get("content"):
95
+ log.debug("Error parsing LLM output: %s", obj)
96
+ self.discard_current_message()
97
+ return None
98
+
99
+ content = obj.get("content")
100
+ status_updates = obj.get("status_updates")
101
+ send_last_status_update = obj.get("send_last_status_update")
102
+
103
+ outputs = []
104
+ full_text = ""
105
+ last_text_output_idx = None
106
+ if content and isinstance(content, list):
107
+ for item_idx, item in enumerate(content):
108
+ output, item_text = self.process_content_item(
109
+ item_idx,
110
+ len(content),
111
+ item,
112
+ response_state,
113
+ response_uuid,
114
+ last_chunk,
115
+ session_id
116
+ )
117
+ if item_text:
118
+ full_text += item_text
119
+ if output:
120
+ outputs.append(output)
121
+ if output.get("text"):
122
+ last_text_output_idx = len(outputs) - 1
123
+
124
+ if status_updates and isinstance(status_updates, list):
125
+ last_status_update_idx = response_state.get("last_status_update_idx", -1)
126
+ if (
127
+ last_status_update_idx < len(status_updates) - 1
128
+ or send_last_status_update
129
+ ):
130
+ user_properties = message.get_user_properties()
131
+ stimulus_uuid = user_properties.get("stimulus_uuid")
132
+ if not stimulus_uuid:
133
+ log.error("No stimulus_uuid found in user_properties")
134
+ stimulus_uuid = response_uuid
135
+ output = {
136
+ "status_update": True,
137
+ "text": status_updates[-1],
138
+ "uuid": stimulus_uuid + "-status",
139
+ "streaming": True,
140
+ }
141
+ outputs.append(output)
142
+ response_state["last_status_update_idx"] = len(status_updates) - 1
143
+
144
+ if not outputs:
145
+ self.discard_current_message()
146
+ return None
147
+
148
+ chunk, next_chunk_index = self.get_current_chunk(
149
+ full_text, response_state.get("previous_chunk_index", 0)
150
+ )
151
+
152
+ # Add the chunk to the last output
153
+ if chunk:
154
+ if last_text_output_idx is not None:
155
+ outputs[last_text_output_idx]["chunk"] = chunk
156
+ response_state["previous_chunk_index"] = next_chunk_index
157
+
158
+ return outputs
159
+
160
+ def process_content_item(
161
+ self, item_idx, num_items, item, response_state, response_uuid, last_chunk, session_id
162
+ ):
163
+ """Process a content item"""
164
+ streaming_content_idx = response_state.get("streaming_content_idx", 0)
165
+
166
+ if item_idx < streaming_content_idx:
167
+ if (
168
+ item.get("type") == "text"
169
+ and not item.get("status_update")
170
+ and not item.get("response_complete")
171
+ ):
172
+ return None, item.get("body")
173
+ return None, ""
174
+
175
+ streaming_started = response_state.get("streaming_started", False)
176
+ last_chunk = last_chunk or item_idx < (num_items - 1)
177
+ first_chunk = not streaming_started or item_idx > streaming_content_idx
178
+
179
+ output = {
180
+ "streaming": True,
181
+ }
182
+
183
+ if item.get("type") == "text":
184
+ output["text"] = item.get("body")
185
+ elif item.get("type") == "file":
186
+ file_info = item.get("body", {})
187
+ if file_info.get("url") or file_info.get("data"):
188
+ # If there is no url but there is data, we need to store the data in the file service
189
+ if not file_info.get("url") and file_info.get("data"):
190
+ file_meta = self.file_service.upload_from_buffer(
191
+ file_info["data"],
192
+ file_info["name"],
193
+ session_id,
194
+ data_source="Orchestrator created data",
195
+ )
196
+ if not session_id:
197
+ log.error("No session_id found in user_properties")
198
+ file_info.update(file_meta)
199
+
200
+ output["files"] = [file_info]
201
+ else:
202
+ return None
203
+
204
+ output["first_chunk"] = first_chunk
205
+ output["last_chunk"] = last_chunk
206
+ output["uuid"] = response_uuid + "-" + str(item_idx)
207
+ response_state["streaming_content_idx"] = item_idx
208
+ response_state["streaming_started"] = True
209
+ return output, output.get("text") or ""
210
+
211
+ def get_current_chunk(self, full_text, previous_chunk_index):
212
+ """Use the previous_chunk_index to get the current chunk of text from the full_text"""
213
+
214
+ # The chunk is the text from the previous_chunk_index to the end of the full_text
215
+ chunk = full_text[previous_chunk_index:]
216
+ return chunk, len(full_text)
217
+
218
+ def age_out_response_state(self):
219
+ """Remove any responses that have been around for too long"""
220
+ # Loop through all states and remove any that are too old
221
+ current_time = datetime.now()
222
+ for response_uuid in list(self._response_state.keys()):
223
+ response_state = self._response_state[response_uuid]
224
+ delta = current_time - response_state["create_time"]
225
+ if delta.total_seconds() > 60:
226
+ del self._response_state[response_uuid]
227
+
228
+ def get_response_state(self, response_uuid):
229
+ """Get the state of a response"""
230
+ return self._response_state.get(response_uuid)
231
+
232
+ def add_response_state(self, response_uuid):
233
+ """Add a new response state"""
234
+ response_state = {
235
+ "create_time": datetime.now(),
236
+ "streaming_content_idx": 0,
237
+ "previous_chunk_index": 0,
238
+ }
239
+ self._response_state[response_uuid] = response_state
240
+ self.age_out_response_state()
241
+ return response_state
242
+
243
+ def delete_response_state(self, response_uuid):
244
+ """Delete a response state"""
245
+ if response_uuid in self._response_state:
246
+ del self._response_state[response_uuid]
@@ -0,0 +1,166 @@
1
+ from datetime import datetime, timedelta
2
+ from ..services.middleware_service.middleware_service import MiddlewareService
3
+ import threading
4
+ from solace_ai_connector.common.log import log
5
+ from ..common.action_response import ActionResponse
6
+ from ..common.time import TEN_MINUTES, THIRTY_MINUTES
7
+
8
+
9
+ ORCHESTRATOR_HISTORY_IDENTIFIER = "orchestrator"
10
+
11
+ ORCHESTRATOR_HISTORY_CONFIG = {
12
+ "type": "memory",
13
+ "time_to_live": THIRTY_MINUTES,
14
+ "expiration_check_interval": TEN_MINUTES,
15
+ "history_policy": {
16
+ "max_turns": 30,
17
+ "max_characters": 0,
18
+ "enforce_alternate_message_roles": False,
19
+ },
20
+ }
21
+
22
+
23
+ class OrchestratorState:
24
+ """Singleton object to store orchestrator state"""
25
+
26
+ _instance = None
27
+ _lock = threading.Lock()
28
+ _config = None
29
+ _session_state = {}
30
+
31
+ @classmethod
32
+ def set_config(cls, config):
33
+ cls._config = config
34
+
35
+ def __new__(cls):
36
+ with cls._lock:
37
+ if cls._instance is None:
38
+ cls._instance = super(OrchestratorState, cls).__new__(cls)
39
+ return cls._instance
40
+
41
+ def __init__(self):
42
+ if not hasattr(self, "registered_agents"):
43
+ self.registered_agents = {}
44
+
45
+ def register_agent(self, agent):
46
+ with self._lock:
47
+ agent_name = agent.get("agent_name")
48
+ agent["state"] = "closed"
49
+ if agent_name not in self.registered_agents:
50
+ self.registered_agents[agent_name] = agent
51
+
52
+ # Reset its TTL
53
+ self.registered_agents[agent_name][
54
+ "expire_time"
55
+ ] = datetime.now() + timedelta(
56
+ milliseconds=self._config.get("agent_ttl_ms")
57
+ )
58
+
59
+ def get_registered_agents(self):
60
+ with self._lock:
61
+ return self.registered_agents
62
+
63
+ def get_agent_action(self, agent_name, action_name):
64
+ with self._lock:
65
+ agent = self.registered_agents.get(agent_name)
66
+ if not agent:
67
+ return None
68
+ actions = agent.get("actions")
69
+ if not actions:
70
+ return None
71
+ for action in actions:
72
+ if action is None:
73
+ continue
74
+ for action_obj_name, action_obj in action.items():
75
+ if action_obj_name == action_name:
76
+ return action_obj
77
+ return None
78
+
79
+ def age_out_agents(self):
80
+ with self._lock:
81
+ now = datetime.now()
82
+ for agent_name, agent in self.registered_agents.items():
83
+ if agent.get("expire_time") < now:
84
+ log.warning("Agent %s has expired. Removing.", agent_name)
85
+ del self.registered_agents[agent_name]
86
+
87
+ def delete_agent(self, agent_name):
88
+ with self._lock:
89
+ if agent_name in self.registered_agents:
90
+ del self.registered_agents[agent_name]
91
+
92
+ def get_session_state(self, session_id):
93
+ if not session_id in self._session_state:
94
+ self._session_state[session_id] = {}
95
+ return self._session_state[session_id]
96
+
97
+ def get_agent_state(self, session_id):
98
+ session_state = self.get_session_state(session_id)
99
+ if "agent_state" not in session_state:
100
+ session_state["agent_state"] = {
101
+ "global": {"agent_name": "global", "state": "open"}
102
+ }
103
+ return session_state["agent_state"]
104
+
105
+ def set_agent_state(self, session_id, agent_state):
106
+ session_state = self.get_session_state(session_id)
107
+ session_state["agent_state"] = agent_state
108
+
109
+ def get_current_subject_starting_id(self, session_id):
110
+ session_state = self.get_session_state(session_id)
111
+ return session_state.get("current_subject_starting_id")
112
+
113
+ def set_current_subject_starting_id(self, session_id, current_subject_starting_id):
114
+ session_state = self.get_session_state(session_id)
115
+ session_state["current_subject_starting_id"] = current_subject_starting_id
116
+
117
+ def update_agent_state(
118
+ self, agent_name: str, new_state: str, session_id
119
+ ) -> ActionResponse:
120
+ """
121
+ Handle an app state change. Return whether or not
122
+ """
123
+ with self._lock:
124
+ if agent_name == "global":
125
+ return None
126
+ old_state = "closed"
127
+ conversation_agent_state = self.get_agent_state(session_id)
128
+ if agent_name in conversation_agent_state:
129
+ old_state = conversation_agent_state[agent_name].get("state", "closed")
130
+ conversation_agent_state[agent_name] = {
131
+ "agent_name": agent_name,
132
+ "state": new_state,
133
+ }
134
+ self.set_agent_state(session_id, conversation_agent_state)
135
+ if old_state == "closed" and new_state == "open":
136
+ return ActionResponse(
137
+ invoke_model_again=True,
138
+ )
139
+ return None
140
+
141
+ def get_agents_and_actions(self, user_properties: dict) -> dict:
142
+ session_id = user_properties.get("session_id", "")
143
+ result = {}
144
+ middleware_service = MiddlewareService()
145
+
146
+ for agent_name, agent in self.registered_agents.items():
147
+ actions = agent.get("actions", [])
148
+ filtered_actions = middleware_service.get("filter_action")(user_properties, actions)
149
+
150
+ if filtered_actions:
151
+ agent_state = self.get_agent_state(session_id).get(agent_name, {})
152
+ state = agent_state.get("state", "closed")
153
+
154
+ result[agent_name] = {
155
+ "description": agent.get("description"),
156
+ "state": state,
157
+ }
158
+
159
+ if state == "open":
160
+ if agent.get("detailed_description"):
161
+ result[agent_name]["description"] = agent.get(
162
+ "detailed_description"
163
+ )
164
+ result[agent_name]["actions"] = filtered_actions
165
+
166
+ return result