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,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integrated A2A + OpenAI Agents SDK Server
|
|
3
|
+
Combines A2A protocol with OpenAI Agents SDK for better tool handling
|
|
4
|
+
"""
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional, List, Dict, Any
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from agents import Agent, Runner, function_tool
|
|
12
|
+
from agents.memory import SQLiteSession
|
|
13
|
+
|
|
14
|
+
from .server import A2AServer
|
|
15
|
+
from .models import Message, Part, AgentCard as A2AAgentCard
|
|
16
|
+
from .message_broker import MessageBroker
|
|
17
|
+
from .task_manager import TaskManager
|
|
18
|
+
from .agent_card import AgentCard
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Define tools using OpenAI Agents SDK decorators
|
|
24
|
+
@function_tool
|
|
25
|
+
def calculator(operation: str, a: float, b: float = 0.0) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Perform mathematical calculations.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
operation: The operation to perform (add, subtract, multiply, divide, square, sqrt)
|
|
31
|
+
a: First number
|
|
32
|
+
b: Second number (optional for unary operations)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The result of the calculation
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
if operation == "add":
|
|
39
|
+
result = a + b
|
|
40
|
+
elif operation == "subtract":
|
|
41
|
+
result = a - b
|
|
42
|
+
elif operation == "multiply":
|
|
43
|
+
result = a * b
|
|
44
|
+
elif operation == "divide":
|
|
45
|
+
if b == 0:
|
|
46
|
+
return "Error: Division by zero"
|
|
47
|
+
result = a / b
|
|
48
|
+
elif operation == "square":
|
|
49
|
+
result = a ** 2
|
|
50
|
+
elif operation == "sqrt":
|
|
51
|
+
if a < 0:
|
|
52
|
+
return "Error: Cannot take square root of negative number"
|
|
53
|
+
result = a ** 0.5
|
|
54
|
+
else:
|
|
55
|
+
return f"Error: Unknown operation '{operation}'"
|
|
56
|
+
|
|
57
|
+
return f"Result: {result}"
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return f"Error: {str(e)}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@function_tool
|
|
63
|
+
def get_weather(city: str) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Get weather information for a city.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
city: The name of the city
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Weather information
|
|
72
|
+
"""
|
|
73
|
+
return f"The weather in {city} is sunny with a temperature of 72°F (22°C)."
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@function_tool
|
|
77
|
+
def analyze_text(text: str, analysis_type: str = "sentiment") -> str:
|
|
78
|
+
"""
|
|
79
|
+
Analyze text for various properties.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
text: The text to analyze
|
|
83
|
+
analysis_type: Type of analysis (sentiment, keywords, summary)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Analysis results
|
|
87
|
+
"""
|
|
88
|
+
if analysis_type == "sentiment":
|
|
89
|
+
return f"Text sentiment: Positive (based on analysis of: '{text[:50]}...')"
|
|
90
|
+
elif analysis_type == "keywords":
|
|
91
|
+
words = text.split()[:5]
|
|
92
|
+
return f"Key terms: {', '.join(words)}"
|
|
93
|
+
elif analysis_type == "summary":
|
|
94
|
+
return f"Summary: {text[:100]}..."
|
|
95
|
+
return f"Analysis type '{analysis_type}' not supported"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@function_tool
|
|
99
|
+
def remember_fact(key: str, value: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Store a fact in memory.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
key: The key to store the fact under
|
|
105
|
+
value: The value to remember
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Confirmation message
|
|
109
|
+
"""
|
|
110
|
+
# This will be handled by session memory automatically
|
|
111
|
+
return f"Remembered: {key} = {value}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class IntegratedAgentsServer(A2AServer):
|
|
115
|
+
"""
|
|
116
|
+
Enhanced A2A Server using OpenAI Agents SDK.
|
|
117
|
+
Provides A2A protocol compatibility with advanced agent capabilities.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
agent_card: AgentCard,
|
|
123
|
+
task_manager: TaskManager,
|
|
124
|
+
message_broker: MessageBroker,
|
|
125
|
+
auth_callback=None,
|
|
126
|
+
sessions_db: str = None
|
|
127
|
+
):
|
|
128
|
+
super().__init__(agent_card, task_manager, message_broker, auth_callback)
|
|
129
|
+
|
|
130
|
+
# Use SESSIONS_DB_PATH from env, fallback to /tmp for ephemeral storage
|
|
131
|
+
if sessions_db is None:
|
|
132
|
+
sessions_db = os.environ.get('SESSIONS_DB_PATH')
|
|
133
|
+
if not sessions_db:
|
|
134
|
+
import tempfile
|
|
135
|
+
sessions_db = os.path.join(tempfile.gettempdir(), "a2a_sessions.db")
|
|
136
|
+
|
|
137
|
+
self.sessions_db = sessions_db
|
|
138
|
+
self._agents_initialized = False
|
|
139
|
+
|
|
140
|
+
# Ensure the database directory exists and is writable
|
|
141
|
+
db_dir = os.path.dirname(self.sessions_db)
|
|
142
|
+
if db_dir and not os.path.exists(db_dir):
|
|
143
|
+
os.makedirs(db_dir, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
# Create OpenAI Agents SDK agents
|
|
146
|
+
self.agents = {
|
|
147
|
+
"assistant": Agent(
|
|
148
|
+
name="Assistant",
|
|
149
|
+
instructions="You are a helpful assistant with access to various tools. Be concise and helpful.",
|
|
150
|
+
tools=[calculator, get_weather, analyze_text, remember_fact]
|
|
151
|
+
),
|
|
152
|
+
"calculator": Agent(
|
|
153
|
+
name="Calculator Agent",
|
|
154
|
+
instructions="You specialize in mathematical calculations. Always use the calculator tool for math operations.",
|
|
155
|
+
tools=[calculator]
|
|
156
|
+
),
|
|
157
|
+
"analyst": Agent(
|
|
158
|
+
name="Analysis Agent",
|
|
159
|
+
instructions="You specialize in text analysis. Use the analyze_text tool to provide insights.",
|
|
160
|
+
tools=[analyze_text]
|
|
161
|
+
),
|
|
162
|
+
"memory": Agent(
|
|
163
|
+
name="Memory Agent",
|
|
164
|
+
instructions="You help users remember and recall information. Use the remember_fact tool.",
|
|
165
|
+
tools=[remember_fact]
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Session cache for conversation history
|
|
170
|
+
self.session_cache: Dict[str, SQLiteSession] = {}
|
|
171
|
+
|
|
172
|
+
logger.info("Initialized IntegratedAgentsServer with OpenAI Agents SDK")
|
|
173
|
+
|
|
174
|
+
def _get_session(self, conversation_id: str) -> SQLiteSession:
|
|
175
|
+
"""Get or create a session for conversation history."""
|
|
176
|
+
if conversation_id not in self.session_cache:
|
|
177
|
+
self.session_cache[conversation_id] = SQLiteSession(
|
|
178
|
+
conversation_id,
|
|
179
|
+
self.sessions_db
|
|
180
|
+
)
|
|
181
|
+
return self.session_cache[conversation_id]
|
|
182
|
+
|
|
183
|
+
def _select_agent(self, message_text: str) -> Agent:
|
|
184
|
+
"""Select the most appropriate agent based on message content."""
|
|
185
|
+
text_lower = message_text.lower()
|
|
186
|
+
|
|
187
|
+
# Simple keyword-based routing
|
|
188
|
+
if any(word in text_lower for word in ['calculate', 'math', 'add', 'subtract', 'multiply', 'divide']):
|
|
189
|
+
return self.agents["calculator"]
|
|
190
|
+
elif any(word in text_lower for word in ['analyze', 'sentiment', 'keywords', 'summary']):
|
|
191
|
+
return self.agents["analyst"]
|
|
192
|
+
elif any(word in text_lower for word in ['remember', 'recall', 'store', 'memory']):
|
|
193
|
+
return self.agents["memory"]
|
|
194
|
+
else:
|
|
195
|
+
return self.agents["assistant"]
|
|
196
|
+
|
|
197
|
+
async def _process_message(self, message: Message, skill_id: Optional[str] = None) -> Message:
|
|
198
|
+
"""
|
|
199
|
+
Process message using OpenAI Agents SDK.
|
|
200
|
+
Maintains A2A protocol compatibility while using advanced agent features.
|
|
201
|
+
"""
|
|
202
|
+
try:
|
|
203
|
+
# Extract text from message parts
|
|
204
|
+
text_parts = [part.content for part in message.parts if part.type == "text"]
|
|
205
|
+
input_text = " ".join(text_parts)
|
|
206
|
+
|
|
207
|
+
# Get or create session for this conversation
|
|
208
|
+
# Use message ID or generate one
|
|
209
|
+
conversation_id = getattr(message, 'conversation_id', 'default')
|
|
210
|
+
session = self._get_session(conversation_id)
|
|
211
|
+
|
|
212
|
+
# Select appropriate agent
|
|
213
|
+
agent = self._select_agent(input_text)
|
|
214
|
+
|
|
215
|
+
logger.info(f"Processing message with {agent.name}: {input_text[:50]}...")
|
|
216
|
+
|
|
217
|
+
# Publish to message broker for UI monitoring
|
|
218
|
+
await self.message_broker.publish(
|
|
219
|
+
"agent.message.received",
|
|
220
|
+
{
|
|
221
|
+
"agent": agent.name,
|
|
222
|
+
"message": input_text[:100],
|
|
223
|
+
"timestamp": datetime.now().isoformat(),
|
|
224
|
+
"conversation_id": conversation_id
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Run the agent with session memory
|
|
229
|
+
result = await Runner.run(
|
|
230
|
+
agent,
|
|
231
|
+
input=input_text,
|
|
232
|
+
session=session
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Publish response to message broker for UI monitoring
|
|
236
|
+
await self.message_broker.publish(
|
|
237
|
+
"agent.message.sent",
|
|
238
|
+
{
|
|
239
|
+
"agent": agent.name,
|
|
240
|
+
"response": result.final_output[:100],
|
|
241
|
+
"timestamp": datetime.now().isoformat(),
|
|
242
|
+
"conversation_id": conversation_id
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
logger.info(f"Agent {agent.name} response: {result.final_output[:50]}...")
|
|
247
|
+
|
|
248
|
+
# Convert back to A2A Message format
|
|
249
|
+
response_parts = [
|
|
250
|
+
Part(
|
|
251
|
+
type="text",
|
|
252
|
+
content=result.final_output
|
|
253
|
+
)
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
return Message(parts=response_parts)
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"Error processing message with Agents SDK: {e}", exc_info=True)
|
|
260
|
+
|
|
261
|
+
# Fallback response
|
|
262
|
+
return Message(parts=[
|
|
263
|
+
Part(
|
|
264
|
+
type="text",
|
|
265
|
+
content=f"I encountered an error processing your message: {str(e)}"
|
|
266
|
+
)
|
|
267
|
+
])
|
|
268
|
+
|
|
269
|
+
async def start(self, host: str = "0.0.0.0", port: int = 8000) -> None:
|
|
270
|
+
"""Start the integrated A2A + Agents SDK server."""
|
|
271
|
+
# Start message broker
|
|
272
|
+
await self.message_broker.start()
|
|
273
|
+
logger.info("Message broker started for agent communication")
|
|
274
|
+
|
|
275
|
+
self._agents_initialized = True
|
|
276
|
+
|
|
277
|
+
# Call parent start method to run A2A server
|
|
278
|
+
await super().start(host=host, port=port)
|
|
279
|
+
|
|
280
|
+
async def cleanup(self):
|
|
281
|
+
"""Clean up server resources."""
|
|
282
|
+
if self._agents_initialized:
|
|
283
|
+
# Clear session cache
|
|
284
|
+
self.session_cache.clear()
|
|
285
|
+
|
|
286
|
+
if self.message_broker:
|
|
287
|
+
await self.message_broker.stop()
|
|
288
|
+
|
|
289
|
+
self._agents_initialized = False
|
|
290
|
+
|
|
291
|
+
logger.info("Integrated Agents server cleanup completed")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def create_integrated_agent_card() -> AgentCard:
|
|
295
|
+
"""Create an agent card for the integrated server."""
|
|
296
|
+
from .models import AgentProvider
|
|
297
|
+
|
|
298
|
+
provider = AgentProvider(
|
|
299
|
+
organization="A2A Protocol Server",
|
|
300
|
+
url="https://github.com/rileyseaburg/codetether"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
card = AgentCard(
|
|
304
|
+
name="A2A Coordination Server",
|
|
305
|
+
description="Agent-to-Agent communication hub enabling distributed task delegation and inter-agent collaboration",
|
|
306
|
+
url="http://localhost:8000",
|
|
307
|
+
provider=provider
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Core A2A capabilities
|
|
311
|
+
card.add_skill(
|
|
312
|
+
skill_id="task_delegation",
|
|
313
|
+
name="Task Delegation",
|
|
314
|
+
description="Create and delegate tasks to other agents in the network",
|
|
315
|
+
input_modes=["text", "structured"],
|
|
316
|
+
output_modes=["text", "structured"]
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
card.add_skill(
|
|
320
|
+
skill_id="agent_discovery",
|
|
321
|
+
name="Agent Discovery",
|
|
322
|
+
description="Discover and query available agents and their capabilities",
|
|
323
|
+
input_modes=["text"],
|
|
324
|
+
output_modes=["structured"]
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
card.add_skill(
|
|
328
|
+
skill_id="message_routing",
|
|
329
|
+
name="Message Routing",
|
|
330
|
+
description="Route messages between agents for asynchronous communication",
|
|
331
|
+
input_modes=["text", "structured"],
|
|
332
|
+
output_modes=["text", "structured"]
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
card.add_skill(
|
|
336
|
+
skill_id="task_monitoring",
|
|
337
|
+
name="Task Monitoring",
|
|
338
|
+
description="Monitor task status and receive updates from executing agents",
|
|
339
|
+
input_modes=["structured"],
|
|
340
|
+
output_modes=["structured"]
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Enable A2A capabilities
|
|
344
|
+
card.enable_streaming()
|
|
345
|
+
card.enable_push_notifications()
|
|
346
|
+
|
|
347
|
+
return card
|