codetether 1.2.2__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.
- a2a_server/__init__.py +29 -0
- a2a_server/a2a_agent_card.py +365 -0
- a2a_server/a2a_errors.py +1133 -0
- a2a_server/a2a_executor.py +926 -0
- a2a_server/a2a_router.py +1033 -0
- a2a_server/a2a_types.py +344 -0
- a2a_server/agent_card.py +408 -0
- a2a_server/agents_server.py +271 -0
- a2a_server/auth_api.py +349 -0
- a2a_server/billing_api.py +638 -0
- a2a_server/billing_service.py +712 -0
- a2a_server/billing_webhooks.py +501 -0
- a2a_server/config.py +96 -0
- a2a_server/database.py +2165 -0
- a2a_server/email_inbound.py +398 -0
- a2a_server/email_notifications.py +486 -0
- a2a_server/enhanced_agents.py +919 -0
- a2a_server/enhanced_server.py +160 -0
- a2a_server/hosted_worker.py +1049 -0
- a2a_server/integrated_agents_server.py +347 -0
- a2a_server/keycloak_auth.py +750 -0
- a2a_server/livekit_bridge.py +439 -0
- a2a_server/marketing_tools.py +1364 -0
- a2a_server/mcp_client.py +196 -0
- a2a_server/mcp_http_server.py +2256 -0
- a2a_server/mcp_server.py +191 -0
- a2a_server/message_broker.py +725 -0
- a2a_server/mock_mcp.py +273 -0
- a2a_server/models.py +494 -0
- a2a_server/monitor_api.py +5904 -0
- a2a_server/opencode_bridge.py +1594 -0
- a2a_server/redis_task_manager.py +518 -0
- a2a_server/server.py +726 -0
- a2a_server/task_manager.py +668 -0
- a2a_server/task_queue.py +742 -0
- a2a_server/tenant_api.py +333 -0
- a2a_server/tenant_middleware.py +219 -0
- a2a_server/tenant_service.py +760 -0
- a2a_server/user_auth.py +721 -0
- a2a_server/vault_client.py +576 -0
- a2a_server/worker_sse.py +873 -0
- agent_worker/__init__.py +8 -0
- agent_worker/worker.py +4877 -0
- codetether/__init__.py +10 -0
- codetether/__main__.py +4 -0
- codetether/cli.py +112 -0
- codetether/worker_cli.py +57 -0
- codetether-1.2.2.dist-info/METADATA +570 -0
- codetether-1.2.2.dist-info/RECORD +66 -0
- codetether-1.2.2.dist-info/WHEEL +5 -0
- codetether-1.2.2.dist-info/entry_points.txt +4 -0
- codetether-1.2.2.dist-info/licenses/LICENSE +202 -0
- codetether-1.2.2.dist-info/top_level.txt +5 -0
- codetether_voice_agent/__init__.py +6 -0
- codetether_voice_agent/agent.py +445 -0
- codetether_voice_agent/codetether_mcp.py +345 -0
- codetether_voice_agent/config.py +16 -0
- codetether_voice_agent/functiongemma_caller.py +380 -0
- codetether_voice_agent/session_playback.py +247 -0
- codetether_voice_agent/tools/__init__.py +21 -0
- codetether_voice_agent/tools/definitions.py +135 -0
- codetether_voice_agent/tools/handlers.py +380 -0
- run_server.py +314 -0
- ui/monitor-tailwind.html +1790 -0
- ui/monitor.html +1775 -0
- ui/monitor.js +2662 -0
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced A2A agents that use MCP tools to perform complex tasks.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
|
|
13
|
+
from .mock_mcp import get_mock_mcp_client, MockMCPClient, cleanup_mock_mcp_client
|
|
14
|
+
from .models import Part, Message, Task, TaskStatus
|
|
15
|
+
from .livekit_bridge import create_livekit_bridge, LiveKitBridge
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EnhancedAgent:
|
|
21
|
+
"""Base class for agents that can use MCP tools and communicate with other agents."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, name: str, description: str, message_broker=None):
|
|
24
|
+
self.name = name
|
|
25
|
+
self.description = description
|
|
26
|
+
self.mcp_client: Optional[MockMCPClient] = None
|
|
27
|
+
self.message_broker = message_broker
|
|
28
|
+
self._message_handlers: Dict[str, List[callable]] = {}
|
|
29
|
+
|
|
30
|
+
async def initialize(self, message_broker=None):
|
|
31
|
+
"""Initialize the agent with MCP client and message broker."""
|
|
32
|
+
try:
|
|
33
|
+
self.mcp_client = await get_mock_mcp_client()
|
|
34
|
+
logger.info(f"Agent {self.name} initialized with mock MCP tools")
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logger.error(f"Failed to initialize mock MCP client for {self.name}: {e}")
|
|
37
|
+
|
|
38
|
+
# Set message broker if provided
|
|
39
|
+
if message_broker:
|
|
40
|
+
self.message_broker = message_broker
|
|
41
|
+
# Subscribe to messages for this agent
|
|
42
|
+
await self.subscribe_to_messages()
|
|
43
|
+
|
|
44
|
+
async def subscribe_to_messages(self):
|
|
45
|
+
"""Subscribe to messages addressed to this agent."""
|
|
46
|
+
if not self.message_broker:
|
|
47
|
+
logger.warning(f"Agent {self.name} has no message broker configured")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
# Subscribe to direct messages
|
|
51
|
+
await self.message_broker.subscribe_to_events(
|
|
52
|
+
f"message.to.{self.name}",
|
|
53
|
+
self._handle_incoming_message
|
|
54
|
+
)
|
|
55
|
+
logger.info(f"Agent {self.name} subscribed to incoming messages")
|
|
56
|
+
|
|
57
|
+
async def _handle_incoming_message(self, event_type: str, data: Dict[str, Any]):
|
|
58
|
+
"""Handle incoming messages from other agents."""
|
|
59
|
+
try:
|
|
60
|
+
from_agent = data.get("from_agent")
|
|
61
|
+
message_data = data.get("message", {})
|
|
62
|
+
|
|
63
|
+
# Convert message data to Message object
|
|
64
|
+
message = Message(**message_data)
|
|
65
|
+
|
|
66
|
+
logger.info(f"Agent {self.name} received message from {from_agent}")
|
|
67
|
+
|
|
68
|
+
# Process the message
|
|
69
|
+
response = await self.process_message(message)
|
|
70
|
+
|
|
71
|
+
# Send response back to sender
|
|
72
|
+
if from_agent:
|
|
73
|
+
await self.send_message_to_agent(from_agent, response)
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Error handling incoming message in {self.name}: {e}")
|
|
77
|
+
|
|
78
|
+
async def send_message_to_agent(self, target_agent: str, message: Message):
|
|
79
|
+
"""Send a message to another agent."""
|
|
80
|
+
if not self.message_broker:
|
|
81
|
+
logger.error(f"Agent {self.name} cannot send message: no message broker configured")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
# Publish message to specific agent's channel
|
|
86
|
+
await self.message_broker.publish_event(
|
|
87
|
+
f"message.to.{target_agent}",
|
|
88
|
+
{
|
|
89
|
+
"from_agent": self.name,
|
|
90
|
+
"to_agent": target_agent,
|
|
91
|
+
"message": message.model_dump(),
|
|
92
|
+
"timestamp": datetime.utcnow().isoformat()
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
logger.info(f"Agent {self.name} sent message to {target_agent}")
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.error(f"Error sending message from {self.name} to {target_agent}: {e}")
|
|
99
|
+
|
|
100
|
+
async def publish_event(self, event_type: str, data: Any):
|
|
101
|
+
"""Publish an event that other agents can subscribe to."""
|
|
102
|
+
if not self.message_broker:
|
|
103
|
+
logger.error(f"Agent {self.name} cannot publish event: no message broker configured")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
await self.message_broker.publish_event(
|
|
108
|
+
f"agent.{self.name}.{event_type}",
|
|
109
|
+
{
|
|
110
|
+
"agent": self.name,
|
|
111
|
+
"event_type": event_type,
|
|
112
|
+
"data": data,
|
|
113
|
+
"timestamp": datetime.utcnow().isoformat()
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
logger.info(f"Agent {self.name} published event: {event_type}")
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Error publishing event from {self.name}: {e}")
|
|
120
|
+
|
|
121
|
+
async def subscribe_to_agent_events(self, agent_name: str, event_type: str, handler: callable):
|
|
122
|
+
"""Subscribe to events from a specific agent."""
|
|
123
|
+
if not self.message_broker:
|
|
124
|
+
logger.error(f"Agent {self.name} cannot subscribe to events: no message broker configured")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
full_event_type = f"agent.{agent_name}.{event_type}"
|
|
129
|
+
await self.message_broker.subscribe_to_events(full_event_type, handler)
|
|
130
|
+
|
|
131
|
+
# Track handler for cleanup
|
|
132
|
+
if full_event_type not in self._message_handlers:
|
|
133
|
+
self._message_handlers[full_event_type] = []
|
|
134
|
+
self._message_handlers[full_event_type].append(handler)
|
|
135
|
+
|
|
136
|
+
logger.info(f"Agent {self.name} subscribed to {agent_name}'s {event_type} events")
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"Error subscribing to agent events: {e}")
|
|
140
|
+
|
|
141
|
+
async def unsubscribe_from_agent_events(self, agent_name: str, event_type: str, handler: callable):
|
|
142
|
+
"""Unsubscribe from events from a specific agent."""
|
|
143
|
+
if not self.message_broker:
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
full_event_type = f"agent.{agent_name}.{event_type}"
|
|
148
|
+
await self.message_broker.unsubscribe_from_events(full_event_type, handler)
|
|
149
|
+
|
|
150
|
+
# Remove from tracked handlers
|
|
151
|
+
if full_event_type in self._message_handlers:
|
|
152
|
+
try:
|
|
153
|
+
self._message_handlers[full_event_type].remove(handler)
|
|
154
|
+
except ValueError:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
logger.info(f"Agent {self.name} unsubscribed from {agent_name}'s {event_type} events")
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Error unsubscribing from agent events: {e}")
|
|
161
|
+
|
|
162
|
+
async def process_message(self, message: Message) -> Message:
|
|
163
|
+
"""Process a message and return a response."""
|
|
164
|
+
raise NotImplementedError
|
|
165
|
+
|
|
166
|
+
def _extract_text_content(self, message: Message) -> str:
|
|
167
|
+
"""Extract text content from message parts."""
|
|
168
|
+
text_parts = []
|
|
169
|
+
for part in message.parts:
|
|
170
|
+
if part.type == "text":
|
|
171
|
+
text_parts.append(part.content)
|
|
172
|
+
return " ".join(text_parts)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class CalculatorAgent(EnhancedAgent):
|
|
176
|
+
"""Agent that performs mathematical calculations using MCP tools."""
|
|
177
|
+
|
|
178
|
+
def __init__(self, message_broker=None):
|
|
179
|
+
super().__init__(
|
|
180
|
+
name="Calculator Agent",
|
|
181
|
+
description="Performs mathematical calculations and data analysis",
|
|
182
|
+
message_broker=message_broker
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
async def process_message(self, message: Message) -> Message:
|
|
186
|
+
"""Process calculation requests."""
|
|
187
|
+
text = self._extract_text_content(message)
|
|
188
|
+
|
|
189
|
+
if not self.mcp_client:
|
|
190
|
+
await self.initialize()
|
|
191
|
+
|
|
192
|
+
# Parse mathematical expressions
|
|
193
|
+
result_text = await self._handle_calculation_request(text)
|
|
194
|
+
|
|
195
|
+
return Message(parts=[Part(type="text", content=result_text)])
|
|
196
|
+
|
|
197
|
+
async def _handle_calculation_request(self, text: str) -> str:
|
|
198
|
+
"""Handle various types of calculation requests."""
|
|
199
|
+
text_lower = text.lower()
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
# Simple arithmetic patterns
|
|
203
|
+
if "add" in text_lower or "+" in text:
|
|
204
|
+
return await self._handle_arithmetic(text, "add")
|
|
205
|
+
elif "subtract" in text_lower or "-" in text:
|
|
206
|
+
return await self._handle_arithmetic(text, "subtract")
|
|
207
|
+
elif "multiply" in text_lower or "*" in text or "times" in text_lower:
|
|
208
|
+
return await self._handle_arithmetic(text, "multiply")
|
|
209
|
+
elif "divide" in text_lower or "/" in text:
|
|
210
|
+
return await self._handle_arithmetic(text, "divide")
|
|
211
|
+
elif "square root" in text_lower or "sqrt" in text_lower:
|
|
212
|
+
return await self._handle_square_root(text)
|
|
213
|
+
elif "square" in text_lower:
|
|
214
|
+
return await self._handle_square(text)
|
|
215
|
+
else:
|
|
216
|
+
# Try to detect numbers and suggest operations
|
|
217
|
+
numbers = re.findall(r'-?\d+\.?\d*', text)
|
|
218
|
+
if len(numbers) >= 2:
|
|
219
|
+
return f"I found numbers {numbers} in your message. Please specify what operation you'd like me to perform (add, subtract, multiply, divide)."
|
|
220
|
+
elif len(numbers) == 1:
|
|
221
|
+
return f"I found the number {numbers[0]}. I can square it, find its square root, or perform operations with another number."
|
|
222
|
+
else:
|
|
223
|
+
return "I'm a calculator agent. I can help you with mathematical operations like addition, subtraction, multiplication, division, squares, and square roots. Please provide numbers and specify the operation."
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(f"Error in calculation: {e}")
|
|
227
|
+
return f"Sorry, I encountered an error while processing your calculation: {str(e)}"
|
|
228
|
+
|
|
229
|
+
async def _handle_arithmetic(self, text: str, operation: str) -> str:
|
|
230
|
+
"""Handle basic arithmetic operations."""
|
|
231
|
+
numbers = re.findall(r'-?\d+\.?\d*', text)
|
|
232
|
+
|
|
233
|
+
if len(numbers) < 2:
|
|
234
|
+
return f"I need two numbers to perform {operation}. Please provide both numbers."
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
a = float(numbers[0])
|
|
238
|
+
b = float(numbers[1])
|
|
239
|
+
|
|
240
|
+
if self.mcp_client:
|
|
241
|
+
result = await self.mcp_client.calculator(operation, a, b)
|
|
242
|
+
if result.get("success"):
|
|
243
|
+
calc_result = result["result"]
|
|
244
|
+
if "error" in calc_result:
|
|
245
|
+
return f"Calculation error: {calc_result['error']}"
|
|
246
|
+
return f"Calculation: {a} {operation} {b} = {calc_result['result']}"
|
|
247
|
+
else:
|
|
248
|
+
return f"Error calling calculator tool: {result.get('error', 'Unknown error')}"
|
|
249
|
+
else:
|
|
250
|
+
return "Calculator tools are not available. Please try again."
|
|
251
|
+
|
|
252
|
+
except ValueError:
|
|
253
|
+
return "Please provide valid numbers for the calculation."
|
|
254
|
+
|
|
255
|
+
async def _handle_square_root(self, text: str) -> str:
|
|
256
|
+
"""Handle square root operations."""
|
|
257
|
+
numbers = re.findall(r'-?\d+\.?\d*', text)
|
|
258
|
+
|
|
259
|
+
if len(numbers) < 1:
|
|
260
|
+
return "I need a number to find its square root."
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
a = float(numbers[0])
|
|
264
|
+
|
|
265
|
+
if self.mcp_client:
|
|
266
|
+
result = await self.mcp_client.calculator("sqrt", a)
|
|
267
|
+
if result.get("success"):
|
|
268
|
+
calc_result = result["result"]
|
|
269
|
+
if "error" in calc_result:
|
|
270
|
+
return f"Calculation error: {calc_result['error']}"
|
|
271
|
+
return f"Square root of {a} = {calc_result['result']}"
|
|
272
|
+
else:
|
|
273
|
+
return f"Error calling calculator tool: {result.get('error', 'Unknown error')}"
|
|
274
|
+
else:
|
|
275
|
+
return "Calculator tools are not available. Please try again."
|
|
276
|
+
|
|
277
|
+
except ValueError:
|
|
278
|
+
return "Please provide a valid number for the square root calculation."
|
|
279
|
+
|
|
280
|
+
async def _handle_square(self, text: str) -> str:
|
|
281
|
+
"""Handle square operations."""
|
|
282
|
+
numbers = re.findall(r'-?\d+\.?\d*', text)
|
|
283
|
+
|
|
284
|
+
if len(numbers) < 1:
|
|
285
|
+
return "I need a number to square it."
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
a = float(numbers[0])
|
|
289
|
+
|
|
290
|
+
if self.mcp_client:
|
|
291
|
+
result = await self.mcp_client.calculator("square", a)
|
|
292
|
+
if result.get("success"):
|
|
293
|
+
calc_result = result["result"]
|
|
294
|
+
if "error" in calc_result:
|
|
295
|
+
return f"Calculation error: {calc_result['error']}"
|
|
296
|
+
return f"{a} squared = {calc_result['result']}"
|
|
297
|
+
else:
|
|
298
|
+
return f"Error calling calculator tool: {result.get('error', 'Unknown error')}"
|
|
299
|
+
else:
|
|
300
|
+
return "Calculator tools are not available. Please try again."
|
|
301
|
+
|
|
302
|
+
except ValueError:
|
|
303
|
+
return "Please provide a valid number for the square calculation."
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class AnalysisAgent(EnhancedAgent):
|
|
307
|
+
"""Agent that analyzes text and provides weather information using MCP tools."""
|
|
308
|
+
|
|
309
|
+
def __init__(self, message_broker=None):
|
|
310
|
+
super().__init__(
|
|
311
|
+
name="Analysis Agent",
|
|
312
|
+
description="Analyzes text and provides weather information",
|
|
313
|
+
message_broker=message_broker
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
async def process_message(self, message: Message) -> Message:
|
|
317
|
+
"""Process analysis requests."""
|
|
318
|
+
text = self._extract_text_content(message)
|
|
319
|
+
|
|
320
|
+
if not self.mcp_client:
|
|
321
|
+
await self.initialize()
|
|
322
|
+
|
|
323
|
+
result_text = await self._handle_analysis_request(text)
|
|
324
|
+
|
|
325
|
+
return Message(parts=[Part(type="text", content=result_text)])
|
|
326
|
+
|
|
327
|
+
async def _handle_analysis_request(self, text: str) -> str:
|
|
328
|
+
"""Handle various types of analysis requests."""
|
|
329
|
+
text_lower = text.lower()
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
if "weather" in text_lower:
|
|
333
|
+
return await self._handle_weather_request(text)
|
|
334
|
+
elif "analyze" in text_lower or "analysis" in text_lower:
|
|
335
|
+
return await self._handle_text_analysis(text)
|
|
336
|
+
else:
|
|
337
|
+
# Default to text analysis
|
|
338
|
+
return await self._handle_text_analysis(text)
|
|
339
|
+
|
|
340
|
+
except Exception as e:
|
|
341
|
+
logger.error(f"Error in analysis: {e}")
|
|
342
|
+
return f"Sorry, I encountered an error while processing your request: {str(e)}"
|
|
343
|
+
|
|
344
|
+
async def _handle_weather_request(self, text: str) -> str:
|
|
345
|
+
"""Handle weather information requests."""
|
|
346
|
+
# Extract location from text (simple pattern matching)
|
|
347
|
+
location_patterns = [
|
|
348
|
+
r"weather in (.+)",
|
|
349
|
+
r"weather for (.+)",
|
|
350
|
+
r"weather at (.+)",
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
location = None
|
|
354
|
+
for pattern in location_patterns:
|
|
355
|
+
match = re.search(pattern, text.lower())
|
|
356
|
+
if match:
|
|
357
|
+
location = match.group(1).strip()
|
|
358
|
+
break
|
|
359
|
+
|
|
360
|
+
if not location:
|
|
361
|
+
location = "unknown location"
|
|
362
|
+
|
|
363
|
+
if self.mcp_client:
|
|
364
|
+
result = await self.mcp_client.get_weather(location)
|
|
365
|
+
if result.get("success"):
|
|
366
|
+
weather_data = result["result"]
|
|
367
|
+
return f"Weather for {weather_data['location']}: {weather_data['temperature']}, {weather_data['condition']}. Humidity: {weather_data['humidity']}, Wind: {weather_data['wind']}"
|
|
368
|
+
else:
|
|
369
|
+
return f"Error getting weather information: {result.get('error', 'Unknown error')}"
|
|
370
|
+
else:
|
|
371
|
+
return "Weather tools are not available. Please try again."
|
|
372
|
+
|
|
373
|
+
async def _handle_text_analysis(self, text: str) -> str:
|
|
374
|
+
"""Handle text analysis requests."""
|
|
375
|
+
if self.mcp_client:
|
|
376
|
+
result = await self.mcp_client.analyze_text(text)
|
|
377
|
+
if result.get("success"):
|
|
378
|
+
analysis = result["result"]
|
|
379
|
+
return f"Text Analysis: {analysis['word_count']} words, {analysis['sentence_count']} sentences, {analysis['character_count']} characters. Average word length: {analysis['average_word_length']:.1f} characters."
|
|
380
|
+
else:
|
|
381
|
+
return f"Error analyzing text: {result.get('error', 'Unknown error')}"
|
|
382
|
+
else:
|
|
383
|
+
return "Text analysis tools are not available. Please try again."
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class MemoryAgent(EnhancedAgent):
|
|
387
|
+
"""Agent that manages memory and data storage using MCP tools."""
|
|
388
|
+
|
|
389
|
+
def __init__(self, message_broker=None):
|
|
390
|
+
super().__init__(
|
|
391
|
+
name="Memory Agent",
|
|
392
|
+
description="Manages memory and data storage for other agents",
|
|
393
|
+
message_broker=message_broker
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
async def process_message(self, message: Message) -> Message:
|
|
397
|
+
"""Process memory management requests."""
|
|
398
|
+
text = self._extract_text_content(message)
|
|
399
|
+
|
|
400
|
+
if not self.mcp_client:
|
|
401
|
+
await self.initialize()
|
|
402
|
+
|
|
403
|
+
result_text = await self._handle_memory_request(text)
|
|
404
|
+
|
|
405
|
+
return Message(parts=[Part(type="text", content=result_text)])
|
|
406
|
+
|
|
407
|
+
async def _handle_memory_request(self, text: str) -> str:
|
|
408
|
+
"""Handle various types of memory requests."""
|
|
409
|
+
text_lower = text.lower()
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
if "store" in text_lower or "save" in text_lower or "remember" in text_lower:
|
|
413
|
+
return await self._handle_store_request(text)
|
|
414
|
+
elif "retrieve" in text_lower or "get" in text_lower or "recall" in text_lower:
|
|
415
|
+
return await self._handle_retrieve_request(text)
|
|
416
|
+
elif "list" in text_lower or "show" in text_lower:
|
|
417
|
+
return await self._handle_list_request()
|
|
418
|
+
elif "delete" in text_lower or "remove" in text_lower or "forget" in text_lower:
|
|
419
|
+
return await self._handle_delete_request(text)
|
|
420
|
+
else:
|
|
421
|
+
return "I can help you store, retrieve, list, or delete information. Please specify what you'd like me to do."
|
|
422
|
+
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.error(f"Error in memory operation: {e}")
|
|
425
|
+
return f"Sorry, I encountered an error while processing your memory request: {str(e)}"
|
|
426
|
+
|
|
427
|
+
async def _handle_store_request(self, text: str) -> str:
|
|
428
|
+
"""Handle store requests."""
|
|
429
|
+
# Simple pattern matching for key-value pairs
|
|
430
|
+
store_patterns = [
|
|
431
|
+
r"store (.+) as (.+)",
|
|
432
|
+
r"save (.+) as (.+)",
|
|
433
|
+
r"remember (.+) as (.+)",
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
for pattern in store_patterns:
|
|
437
|
+
match = re.search(pattern, text.lower())
|
|
438
|
+
if match:
|
|
439
|
+
value = match.group(1).strip()
|
|
440
|
+
key = match.group(2).strip()
|
|
441
|
+
|
|
442
|
+
if self.mcp_client:
|
|
443
|
+
result = await self.mcp_client.memory_operation("store", key, value)
|
|
444
|
+
if result.get("success"):
|
|
445
|
+
mem_result = result["result"]
|
|
446
|
+
if mem_result.get("success"):
|
|
447
|
+
return f"Stored '{value}' with key '{key}'"
|
|
448
|
+
else:
|
|
449
|
+
return f"Error storing data: {mem_result.get('error', 'Unknown error')}"
|
|
450
|
+
else:
|
|
451
|
+
return f"Error calling memory tool: {result.get('error', 'Unknown error')}"
|
|
452
|
+
else:
|
|
453
|
+
return "Memory tools are not available. Please try again."
|
|
454
|
+
|
|
455
|
+
return "Please use the format: 'store [value] as [key]' or 'save [value] as [key]'"
|
|
456
|
+
|
|
457
|
+
async def _handle_retrieve_request(self, text: str) -> str:
|
|
458
|
+
"""Handle retrieve requests."""
|
|
459
|
+
# Extract key from text
|
|
460
|
+
retrieve_patterns = [
|
|
461
|
+
r"retrieve (.+)",
|
|
462
|
+
r"get (.+)",
|
|
463
|
+
r"recall (.+)",
|
|
464
|
+
]
|
|
465
|
+
|
|
466
|
+
for pattern in retrieve_patterns:
|
|
467
|
+
match = re.search(pattern, text.lower())
|
|
468
|
+
if match:
|
|
469
|
+
key = match.group(1).strip()
|
|
470
|
+
|
|
471
|
+
if self.mcp_client:
|
|
472
|
+
result = await self.mcp_client.memory_operation("retrieve", key)
|
|
473
|
+
if result.get("success"):
|
|
474
|
+
mem_result = result["result"]
|
|
475
|
+
if mem_result.get("found"):
|
|
476
|
+
return f"Retrieved '{key}': {mem_result['value']}"
|
|
477
|
+
else:
|
|
478
|
+
return f"No data found for key '{key}'"
|
|
479
|
+
else:
|
|
480
|
+
return f"Error calling memory tool: {result.get('error', 'Unknown error')}"
|
|
481
|
+
else:
|
|
482
|
+
return "Memory tools are not available. Please try again."
|
|
483
|
+
|
|
484
|
+
return "Please specify what you'd like to retrieve: 'retrieve [key]' or 'get [key]'"
|
|
485
|
+
|
|
486
|
+
async def _handle_list_request(self) -> str:
|
|
487
|
+
"""Handle list requests."""
|
|
488
|
+
if self.mcp_client:
|
|
489
|
+
result = await self.mcp_client.memory_operation("list")
|
|
490
|
+
if result.get("success"):
|
|
491
|
+
mem_result = result["result"]
|
|
492
|
+
keys = mem_result.get("keys", [])
|
|
493
|
+
if keys:
|
|
494
|
+
return f"Stored keys ({len(keys)}): {', '.join(keys)}"
|
|
495
|
+
else:
|
|
496
|
+
return "No data stored in memory"
|
|
497
|
+
else:
|
|
498
|
+
return f"Error calling memory tool: {result.get('error', 'Unknown error')}"
|
|
499
|
+
else:
|
|
500
|
+
return "Memory tools are not available. Please try again."
|
|
501
|
+
|
|
502
|
+
async def _handle_delete_request(self, text: str) -> str:
|
|
503
|
+
"""Handle delete requests."""
|
|
504
|
+
# Extract key from text
|
|
505
|
+
delete_patterns = [
|
|
506
|
+
r"delete (.+)",
|
|
507
|
+
r"remove (.+)",
|
|
508
|
+
r"forget (.+)",
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
for pattern in delete_patterns:
|
|
512
|
+
match = re.search(pattern, text.lower())
|
|
513
|
+
if match:
|
|
514
|
+
key = match.group(1).strip()
|
|
515
|
+
|
|
516
|
+
if self.mcp_client:
|
|
517
|
+
result = await self.mcp_client.memory_operation("delete", key)
|
|
518
|
+
if result.get("success"):
|
|
519
|
+
mem_result = result["result"]
|
|
520
|
+
if mem_result.get("success"):
|
|
521
|
+
return f"Deleted key '{key}'"
|
|
522
|
+
else:
|
|
523
|
+
return f"Key '{key}' not found"
|
|
524
|
+
else:
|
|
525
|
+
return f"Error calling memory tool: {result.get('error', 'Unknown error')}"
|
|
526
|
+
else:
|
|
527
|
+
return "Memory tools are not available. Please try again."
|
|
528
|
+
|
|
529
|
+
return "Please specify what you'd like to delete: 'delete [key]' or 'remove [key]'"
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class MediaAgent(EnhancedAgent):
|
|
533
|
+
"""Agent that manages real-time media sessions using LiveKit."""
|
|
534
|
+
|
|
535
|
+
def __init__(self, message_broker=None):
|
|
536
|
+
super().__init__(
|
|
537
|
+
name="Media Agent",
|
|
538
|
+
description="Manages real-time audio/video sessions using LiveKit",
|
|
539
|
+
message_broker=message_broker
|
|
540
|
+
)
|
|
541
|
+
self.livekit_bridge: Optional[LiveKitBridge] = None
|
|
542
|
+
|
|
543
|
+
async def initialize(self, message_broker=None):
|
|
544
|
+
"""Initialize the agent with LiveKit bridge."""
|
|
545
|
+
try:
|
|
546
|
+
self.livekit_bridge = create_livekit_bridge()
|
|
547
|
+
if self.livekit_bridge:
|
|
548
|
+
logger.info(f"Agent {self.name} initialized with LiveKit bridge")
|
|
549
|
+
else:
|
|
550
|
+
logger.warning(f"Agent {self.name} could not initialize LiveKit bridge - media features disabled")
|
|
551
|
+
except Exception as e:
|
|
552
|
+
logger.error(f"Failed to initialize LiveKit bridge for {self.name}: {e}")
|
|
553
|
+
self.livekit_bridge = None
|
|
554
|
+
|
|
555
|
+
# Set message broker if provided
|
|
556
|
+
if message_broker:
|
|
557
|
+
self.message_broker = message_broker
|
|
558
|
+
# Subscribe to messages for this agent
|
|
559
|
+
await self.subscribe_to_messages()
|
|
560
|
+
|
|
561
|
+
async def process_message(self, message: Message) -> Message:
|
|
562
|
+
"""Process media-related requests."""
|
|
563
|
+
text = self._extract_text_content(message)
|
|
564
|
+
|
|
565
|
+
if not self.livekit_bridge:
|
|
566
|
+
return Message(parts=[Part(
|
|
567
|
+
type="text",
|
|
568
|
+
content="Media functionality is not available. LiveKit bridge not configured."
|
|
569
|
+
)])
|
|
570
|
+
|
|
571
|
+
# Parse the message to determine action
|
|
572
|
+
action = self._parse_media_action(text, message)
|
|
573
|
+
|
|
574
|
+
try:
|
|
575
|
+
if action["type"] == "media-request":
|
|
576
|
+
return await self._handle_media_request(action, message)
|
|
577
|
+
elif action["type"] == "media-join":
|
|
578
|
+
return await self._handle_media_join(action, message)
|
|
579
|
+
elif action["type"] == "list-rooms":
|
|
580
|
+
return await self._handle_list_rooms()
|
|
581
|
+
elif action["type"] == "room-info":
|
|
582
|
+
return await self._handle_room_info(action)
|
|
583
|
+
else:
|
|
584
|
+
return Message(parts=[Part(
|
|
585
|
+
type="text",
|
|
586
|
+
content=(
|
|
587
|
+
"I can help you with media sessions. Available commands:\n"
|
|
588
|
+
"- 'create media session' or 'start video call' - Create a new media session\n"
|
|
589
|
+
"- 'join room [room_name]' - Join an existing room\n"
|
|
590
|
+
"- 'list rooms' - List active rooms\n"
|
|
591
|
+
"- 'room info [room_name]' - Get information about a room"
|
|
592
|
+
)
|
|
593
|
+
)])
|
|
594
|
+
except Exception as e:
|
|
595
|
+
logger.error(f"Error processing media request: {e}")
|
|
596
|
+
return Message(parts=[Part(
|
|
597
|
+
type="text",
|
|
598
|
+
content=f"Error processing media request: {str(e)}"
|
|
599
|
+
)])
|
|
600
|
+
|
|
601
|
+
def _parse_media_action(self, text: str, message: Message) -> Dict[str, Any]:
|
|
602
|
+
"""Parse the message to determine the media action."""
|
|
603
|
+
text_lower = text.lower()
|
|
604
|
+
|
|
605
|
+
# Check for media request keywords
|
|
606
|
+
if any(phrase in text_lower for phrase in [
|
|
607
|
+
"create media", "start video", "start audio", "create room",
|
|
608
|
+
"new session", "video call", "audio call"
|
|
609
|
+
]):
|
|
610
|
+
return {
|
|
611
|
+
"type": "media-request",
|
|
612
|
+
"room_name": self._extract_room_name(text),
|
|
613
|
+
"role": self._extract_role(text),
|
|
614
|
+
"identity": self._extract_identity(text, message)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
# Check for join room keywords
|
|
618
|
+
elif any(phrase in text_lower for phrase in ["join room", "join session", "connect to"]):
|
|
619
|
+
return {
|
|
620
|
+
"type": "media-join",
|
|
621
|
+
"room_name": self._extract_room_name(text, required=True),
|
|
622
|
+
"role": self._extract_role(text),
|
|
623
|
+
"identity": self._extract_identity(text, message)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
# Check for list rooms
|
|
627
|
+
elif "list rooms" in text_lower or "show rooms" in text_lower:
|
|
628
|
+
return {"type": "list-rooms"}
|
|
629
|
+
|
|
630
|
+
# Check for room info
|
|
631
|
+
elif "room info" in text_lower or "room details" in text_lower:
|
|
632
|
+
return {
|
|
633
|
+
"type": "room-info",
|
|
634
|
+
"room_name": self._extract_room_name(text, required=True)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return {"type": "help"}
|
|
638
|
+
|
|
639
|
+
def _extract_room_name(self, text: str, required: bool = False) -> Optional[str]:
|
|
640
|
+
"""Extract room name from text."""
|
|
641
|
+
# Look for patterns like "room MyRoom" or "session MySession"
|
|
642
|
+
patterns = [
|
|
643
|
+
r"room\s+([a-zA-Z0-9_-]+)",
|
|
644
|
+
r"session\s+([a-zA-Z0-9_-]+)",
|
|
645
|
+
r"called\s+([a-zA-Z0-9_-]+)"
|
|
646
|
+
]
|
|
647
|
+
|
|
648
|
+
for pattern in patterns:
|
|
649
|
+
match = re.search(pattern, text, re.IGNORECASE)
|
|
650
|
+
if match:
|
|
651
|
+
return match.group(1)
|
|
652
|
+
|
|
653
|
+
if required:
|
|
654
|
+
return None
|
|
655
|
+
|
|
656
|
+
# Generate a random room name if none specified
|
|
657
|
+
timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
|
|
658
|
+
return f"room-{timestamp}-{uuid.uuid4().hex[:16]}"
|
|
659
|
+
|
|
660
|
+
def _extract_role(self, text: str) -> str:
|
|
661
|
+
"""Extract role from text."""
|
|
662
|
+
text_lower = text.lower()
|
|
663
|
+
|
|
664
|
+
if "admin" in text_lower or "administrator" in text_lower:
|
|
665
|
+
return "admin"
|
|
666
|
+
elif "moderator" in text_lower or "mod" in text_lower:
|
|
667
|
+
return "moderator"
|
|
668
|
+
elif "publisher" in text_lower or "presenter" in text_lower:
|
|
669
|
+
return "publisher"
|
|
670
|
+
elif "viewer" in text_lower or "watch" in text_lower:
|
|
671
|
+
return "viewer"
|
|
672
|
+
else:
|
|
673
|
+
return "participant"
|
|
674
|
+
|
|
675
|
+
def _extract_identity(self, text: str, message: Message) -> str:
|
|
676
|
+
"""Extract participant identity from text or generate one."""
|
|
677
|
+
# Look for identity patterns
|
|
678
|
+
identity_patterns = [
|
|
679
|
+
r"as\s+([a-zA-Z0-9_-]+)",
|
|
680
|
+
r"identity\s+([a-zA-Z0-9_-]+)",
|
|
681
|
+
r"user\s+([a-zA-Z0-9_-]+)"
|
|
682
|
+
]
|
|
683
|
+
|
|
684
|
+
for pattern in identity_patterns:
|
|
685
|
+
match = re.search(pattern, text, re.IGNORECASE)
|
|
686
|
+
if match:
|
|
687
|
+
return match.group(1)
|
|
688
|
+
|
|
689
|
+
# Check message metadata for identity
|
|
690
|
+
if message.metadata and "user_id" in message.metadata:
|
|
691
|
+
return str(message.metadata["user_id"])
|
|
692
|
+
|
|
693
|
+
# Generate a random identity
|
|
694
|
+
return f"user-{uuid.uuid4().hex}"
|
|
695
|
+
|
|
696
|
+
async def _handle_media_request(self, action: Dict[str, Any], message: Message) -> Message:
|
|
697
|
+
"""Handle request to create a new media session."""
|
|
698
|
+
room_name = action["room_name"]
|
|
699
|
+
role = action["role"]
|
|
700
|
+
identity = action["identity"]
|
|
701
|
+
|
|
702
|
+
try:
|
|
703
|
+
# Check if room already exists
|
|
704
|
+
existing_room = await self.livekit_bridge.get_room_info(room_name)
|
|
705
|
+
|
|
706
|
+
if not existing_room:
|
|
707
|
+
# Create new room
|
|
708
|
+
room_metadata = {
|
|
709
|
+
"created_by": identity,
|
|
710
|
+
"created_at": datetime.now().isoformat(),
|
|
711
|
+
"a2a_agent": "media"
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
room_info = await self.livekit_bridge.create_room(
|
|
715
|
+
room_name=room_name,
|
|
716
|
+
metadata=room_metadata
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
logger.info(f"Created new room: {room_name}")
|
|
720
|
+
else:
|
|
721
|
+
room_info = existing_room
|
|
722
|
+
logger.info(f"Using existing room: {room_name}")
|
|
723
|
+
|
|
724
|
+
# Mint access token
|
|
725
|
+
token = self.livekit_bridge.mint_access_token(
|
|
726
|
+
identity=identity,
|
|
727
|
+
room_name=room_name,
|
|
728
|
+
a2a_role=role,
|
|
729
|
+
metadata=f"A2A participant {identity}"
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
# Generate join URL
|
|
733
|
+
join_url = self.livekit_bridge.generate_join_url(room_name, token)
|
|
734
|
+
|
|
735
|
+
# Create response with detailed information
|
|
736
|
+
response_data = {
|
|
737
|
+
"room_name": room_name,
|
|
738
|
+
"room_sid": room_info.get("sid"),
|
|
739
|
+
"join_url": join_url,
|
|
740
|
+
"participant_identity": identity,
|
|
741
|
+
"role": role,
|
|
742
|
+
"expires_at": (datetime.now() + timedelta(hours=1)).isoformat(),
|
|
743
|
+
"max_participants": room_info.get("max_participants", 50)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return Message(parts=[
|
|
747
|
+
Part(type="text", content=f"Media session created successfully! Room: {room_name}"),
|
|
748
|
+
Part(type="data", content=response_data, metadata={"content_type": "application/json"})
|
|
749
|
+
])
|
|
750
|
+
|
|
751
|
+
except Exception as e:
|
|
752
|
+
logger.error(f"Failed to create media session: {e}")
|
|
753
|
+
raise
|
|
754
|
+
|
|
755
|
+
async def _handle_media_join(self, action: Dict[str, Any], message: Message) -> Message:
|
|
756
|
+
"""Handle request to join an existing media session."""
|
|
757
|
+
room_name = action["room_name"]
|
|
758
|
+
role = action["role"]
|
|
759
|
+
identity = action["identity"]
|
|
760
|
+
|
|
761
|
+
if not room_name:
|
|
762
|
+
return Message(parts=[Part(
|
|
763
|
+
type="text",
|
|
764
|
+
content="Please specify a room name to join (e.g., 'join room MyRoom')"
|
|
765
|
+
)])
|
|
766
|
+
|
|
767
|
+
try:
|
|
768
|
+
# Check if room exists
|
|
769
|
+
room_info = await self.livekit_bridge.get_room_info(room_name)
|
|
770
|
+
|
|
771
|
+
if not room_info:
|
|
772
|
+
return Message(parts=[Part(
|
|
773
|
+
type="text",
|
|
774
|
+
content=f"Room '{room_name}' not found. You can create it by saying 'create media session room {room_name}'"
|
|
775
|
+
)])
|
|
776
|
+
|
|
777
|
+
# Mint access token
|
|
778
|
+
token = self.livekit_bridge.mint_access_token(
|
|
779
|
+
identity=identity,
|
|
780
|
+
room_name=room_name,
|
|
781
|
+
a2a_role=role,
|
|
782
|
+
metadata=f"A2A participant {identity}"
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
# Generate join URL
|
|
786
|
+
join_url = self.livekit_bridge.generate_join_url(room_name, token)
|
|
787
|
+
|
|
788
|
+
# Create response
|
|
789
|
+
response_data = {
|
|
790
|
+
"room_name": room_name,
|
|
791
|
+
"join_url": join_url,
|
|
792
|
+
"participant_identity": identity,
|
|
793
|
+
"role": role,
|
|
794
|
+
"expires_at": (datetime.now() + timedelta(hours=1)).isoformat()
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return Message(parts=[
|
|
798
|
+
Part(type="text", content=f"Access granted to room '{room_name}' as {role}"),
|
|
799
|
+
Part(type="data", content=response_data, metadata={"content_type": "application/json"})
|
|
800
|
+
])
|
|
801
|
+
|
|
802
|
+
except Exception as e:
|
|
803
|
+
logger.error(f"Failed to join media session: {e}")
|
|
804
|
+
raise
|
|
805
|
+
|
|
806
|
+
async def _handle_list_rooms(self) -> Message:
|
|
807
|
+
"""Handle request to list active rooms."""
|
|
808
|
+
# Note: This is a simplified implementation.
|
|
809
|
+
# LiveKit's list_rooms API would need to be called here
|
|
810
|
+
return Message(parts=[Part(
|
|
811
|
+
type="text",
|
|
812
|
+
content="Room listing feature coming soon. For now, you can join specific rooms by name."
|
|
813
|
+
)])
|
|
814
|
+
|
|
815
|
+
async def _handle_room_info(self, action: Dict[str, Any]) -> Message:
|
|
816
|
+
"""Handle request for room information."""
|
|
817
|
+
room_name = action["room_name"]
|
|
818
|
+
|
|
819
|
+
if not room_name:
|
|
820
|
+
return Message(parts=[Part(
|
|
821
|
+
type="text",
|
|
822
|
+
content="Please specify a room name (e.g., 'room info MyRoom')"
|
|
823
|
+
)])
|
|
824
|
+
|
|
825
|
+
try:
|
|
826
|
+
room_info = await self.livekit_bridge.get_room_info(room_name)
|
|
827
|
+
|
|
828
|
+
if not room_info:
|
|
829
|
+
return Message(parts=[Part(
|
|
830
|
+
type="text",
|
|
831
|
+
content=f"Room '{room_name}' not found."
|
|
832
|
+
)])
|
|
833
|
+
|
|
834
|
+
participants = await self.livekit_bridge.list_participants(room_name)
|
|
835
|
+
|
|
836
|
+
info_text = f"""Room Information for '{room_name}':
|
|
837
|
+
- Room SID: {room_info.get('sid', 'N/A')}
|
|
838
|
+
- Max Participants: {room_info.get('max_participants', 'N/A')}
|
|
839
|
+
- Current Participants: {len(participants)}
|
|
840
|
+
- Created: {room_info.get('creation_time', 'N/A')}
|
|
841
|
+
- Participants: {', '.join([p['identity'] for p in participants]) if participants else 'None'}"""
|
|
842
|
+
|
|
843
|
+
return Message(parts=[
|
|
844
|
+
Part(type="text", content=info_text),
|
|
845
|
+
Part(type="data", content=room_info, metadata={"content_type": "application/json"})
|
|
846
|
+
])
|
|
847
|
+
|
|
848
|
+
except Exception as e:
|
|
849
|
+
logger.error(f"Failed to get room info: {e}")
|
|
850
|
+
return Message(parts=[Part(
|
|
851
|
+
type="text",
|
|
852
|
+
content=f"Error getting room information: {str(e)}"
|
|
853
|
+
)])
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
# Agent registry
|
|
857
|
+
ENHANCED_AGENTS = {}
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def initialize_agent_registry(message_broker=None):
|
|
861
|
+
"""Initialize the agent registry with message broker support."""
|
|
862
|
+
global ENHANCED_AGENTS
|
|
863
|
+
ENHANCED_AGENTS = {
|
|
864
|
+
"calculator": CalculatorAgent(message_broker=message_broker),
|
|
865
|
+
"analysis": AnalysisAgent(message_broker=message_broker),
|
|
866
|
+
"memory": MemoryAgent(message_broker=message_broker),
|
|
867
|
+
"media": MediaAgent(message_broker=message_broker),
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
async def get_agent(agent_type: str, message_broker=None) -> Optional[EnhancedAgent]:
|
|
872
|
+
"""Get an agent by type."""
|
|
873
|
+
if not ENHANCED_AGENTS:
|
|
874
|
+
initialize_agent_registry(message_broker)
|
|
875
|
+
|
|
876
|
+
agent = ENHANCED_AGENTS.get(agent_type)
|
|
877
|
+
if agent and not agent.mcp_client:
|
|
878
|
+
await agent.initialize(message_broker)
|
|
879
|
+
return agent
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
async def route_message_to_agent(message: Message, message_broker=None) -> Message:
|
|
883
|
+
"""Route a message to the appropriate agent based on content.
|
|
884
|
+
|
|
885
|
+
For agent-to-agent communication, we simply acknowledge receipt and store the message.
|
|
886
|
+
Agents can retrieve messages using get_messages MCP tool.
|
|
887
|
+
"""
|
|
888
|
+
text = " ".join(part.content for part in message.parts if part.type == "text")
|
|
889
|
+
|
|
890
|
+
# Simple acknowledgment response for agent communication
|
|
891
|
+
response_text = f"Message received and logged. Agents can retrieve it using get_messages."
|
|
892
|
+
|
|
893
|
+
# If message broker is available, publish it for other agents to see
|
|
894
|
+
if message_broker:
|
|
895
|
+
try:
|
|
896
|
+
await message_broker.publish_event("agent.message.broadcast", {
|
|
897
|
+
"content": text,
|
|
898
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
899
|
+
"message_id": str(uuid.uuid4())
|
|
900
|
+
})
|
|
901
|
+
logger.info(f"Message broadcast to agent network: {text[:50]}...")
|
|
902
|
+
except Exception as e:
|
|
903
|
+
logger.error(f"Failed to broadcast message: {e}")
|
|
904
|
+
|
|
905
|
+
return Message(parts=[Part(type="text", content=response_text)])
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
async def initialize_all_agents(message_broker=None):
|
|
909
|
+
"""Initialize all agents with MCP connections and message broker."""
|
|
910
|
+
if not ENHANCED_AGENTS:
|
|
911
|
+
initialize_agent_registry(message_broker)
|
|
912
|
+
|
|
913
|
+
for agent in ENHANCED_AGENTS.values():
|
|
914
|
+
await agent.initialize(message_broker)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
async def cleanup_all_agents():
|
|
918
|
+
"""Clean up all agent resources."""
|
|
919
|
+
await cleanup_mock_mcp_client()
|