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
a2a_server/agent_card.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Card implementation for A2A protocol.
|
|
3
|
+
|
|
4
|
+
Provides utilities for creating, managing, and serving agent cards
|
|
5
|
+
for agent discovery and capability advertisement.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import List, Optional, Dict, Any
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .models import (
|
|
13
|
+
AgentCard as AgentCardModel,
|
|
14
|
+
AgentProvider,
|
|
15
|
+
AgentCapabilities,
|
|
16
|
+
AgentSkill,
|
|
17
|
+
AuthenticationScheme,
|
|
18
|
+
AgentExtension,
|
|
19
|
+
AdditionalInterfaces,
|
|
20
|
+
LiveKitInterface
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentCard:
|
|
25
|
+
"""Helper class for creating and managing A2A Agent Cards."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
name: str,
|
|
30
|
+
description: str,
|
|
31
|
+
url: str,
|
|
32
|
+
provider: AgentProvider,
|
|
33
|
+
capabilities: Optional[AgentCapabilities] = None,
|
|
34
|
+
authentication: Optional[List[AuthenticationScheme]] = None,
|
|
35
|
+
skills: Optional[List[AgentSkill]] = None,
|
|
36
|
+
additional_interfaces: Optional[AdditionalInterfaces] = None,
|
|
37
|
+
version: str = "1.0"
|
|
38
|
+
):
|
|
39
|
+
self.card = AgentCardModel(
|
|
40
|
+
name=name,
|
|
41
|
+
description=description,
|
|
42
|
+
url=url,
|
|
43
|
+
provider=provider,
|
|
44
|
+
capabilities=capabilities or AgentCapabilities(),
|
|
45
|
+
authentication=authentication or [],
|
|
46
|
+
skills=skills or [],
|
|
47
|
+
additional_interfaces=additional_interfaces,
|
|
48
|
+
version=version
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def add_skill(
|
|
52
|
+
self,
|
|
53
|
+
skill_id: str,
|
|
54
|
+
name: str,
|
|
55
|
+
description: str,
|
|
56
|
+
input_modes: Optional[List[str]] = None,
|
|
57
|
+
output_modes: Optional[List[str]] = None,
|
|
58
|
+
examples: Optional[List[Dict[str, Any]]] = None
|
|
59
|
+
) -> 'AgentCard':
|
|
60
|
+
"""Add a skill to the agent card."""
|
|
61
|
+
skill = AgentSkill(
|
|
62
|
+
id=skill_id,
|
|
63
|
+
name=name,
|
|
64
|
+
description=description,
|
|
65
|
+
input_modes=input_modes or ["text"],
|
|
66
|
+
output_modes=output_modes or ["text"],
|
|
67
|
+
examples=examples
|
|
68
|
+
)
|
|
69
|
+
self.card.skills.append(skill)
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def add_authentication(
|
|
73
|
+
self,
|
|
74
|
+
scheme: str,
|
|
75
|
+
description: Optional[str] = None
|
|
76
|
+
) -> 'AgentCard':
|
|
77
|
+
"""Add an authentication scheme to the agent card."""
|
|
78
|
+
auth = AuthenticationScheme(
|
|
79
|
+
scheme=scheme,
|
|
80
|
+
description=description
|
|
81
|
+
)
|
|
82
|
+
self.card.authentication.append(auth)
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def enable_streaming(self) -> 'AgentCard':
|
|
86
|
+
"""Enable streaming capability."""
|
|
87
|
+
if not self.card.capabilities:
|
|
88
|
+
self.card.capabilities = AgentCapabilities()
|
|
89
|
+
self.card.capabilities.streaming = True
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
def enable_push_notifications(self) -> 'AgentCard':
|
|
93
|
+
"""Enable push notifications capability."""
|
|
94
|
+
if not self.card.capabilities:
|
|
95
|
+
self.card.capabilities = AgentCapabilities()
|
|
96
|
+
self.card.capabilities.push_notifications = True
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def enable_state_history(self) -> 'AgentCard':
|
|
100
|
+
"""Enable state transition history capability."""
|
|
101
|
+
if not self.card.capabilities:
|
|
102
|
+
self.card.capabilities = AgentCapabilities()
|
|
103
|
+
self.card.capabilities.state_transition_history = True
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def enable_media(self) -> 'AgentCard':
|
|
107
|
+
"""Enable media capability."""
|
|
108
|
+
if not self.card.capabilities:
|
|
109
|
+
self.card.capabilities = AgentCapabilities()
|
|
110
|
+
self.card.capabilities.media = True
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
def add_livekit_interface(
|
|
114
|
+
self,
|
|
115
|
+
token_endpoint: str,
|
|
116
|
+
join_url_template: Optional[str] = None,
|
|
117
|
+
server_managed: bool = True
|
|
118
|
+
) -> 'AgentCard':
|
|
119
|
+
"""Add LiveKit interface configuration.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
token_endpoint: Endpoint for obtaining LiveKit access tokens
|
|
123
|
+
join_url_template: Template for generating join URLs
|
|
124
|
+
server_managed: Whether the server manages LiveKit resources
|
|
125
|
+
"""
|
|
126
|
+
if not self.card.additional_interfaces:
|
|
127
|
+
self.card.additional_interfaces = AdditionalInterfaces()
|
|
128
|
+
|
|
129
|
+
self.card.additional_interfaces.livekit = LiveKitInterface(
|
|
130
|
+
token_endpoint=token_endpoint,
|
|
131
|
+
join_url_template=join_url_template,
|
|
132
|
+
server_managed=server_managed
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Also enable media capability
|
|
136
|
+
self.enable_media()
|
|
137
|
+
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def add_mcp_interface(
|
|
141
|
+
self,
|
|
142
|
+
endpoint: str,
|
|
143
|
+
protocol: str = "http",
|
|
144
|
+
description: Optional[str] = None
|
|
145
|
+
) -> 'AgentCard':
|
|
146
|
+
"""Add MCP (Model Context Protocol) interface for agent synchronization.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
endpoint: HTTP endpoint for MCP JSON-RPC (e.g., http://localhost:9000/mcp/v1/rpc)
|
|
150
|
+
protocol: Protocol type (http, stdio, sse)
|
|
151
|
+
description: Optional description of available MCP tools
|
|
152
|
+
"""
|
|
153
|
+
if not self.card.additional_interfaces:
|
|
154
|
+
self.card.additional_interfaces = AdditionalInterfaces()
|
|
155
|
+
|
|
156
|
+
# Add MCP interface as custom field
|
|
157
|
+
if not hasattr(self.card.additional_interfaces, 'mcp'):
|
|
158
|
+
# Store as extra field in additional_interfaces
|
|
159
|
+
mcp_info = {
|
|
160
|
+
"endpoint": endpoint,
|
|
161
|
+
"protocol": protocol,
|
|
162
|
+
"description": description or "MCP tools for agent synchronization and coordination"
|
|
163
|
+
}
|
|
164
|
+
# Store as an extra field (allowed by model_config)
|
|
165
|
+
if not hasattr(self.card.additional_interfaces, '__pydantic_extra__'):
|
|
166
|
+
self.card.additional_interfaces.__pydantic_extra__ = {}
|
|
167
|
+
self.card.additional_interfaces.__pydantic_extra__["mcp"] = mcp_info
|
|
168
|
+
|
|
169
|
+
return self
|
|
170
|
+
|
|
171
|
+
def add_extension(
|
|
172
|
+
self,
|
|
173
|
+
uri: str,
|
|
174
|
+
description: Optional[str] = None,
|
|
175
|
+
required: bool = False
|
|
176
|
+
) -> 'AgentCard':
|
|
177
|
+
"""Add a protocol extension to the agent card."""
|
|
178
|
+
if not self.card.capabilities:
|
|
179
|
+
self.card.capabilities = AgentCapabilities()
|
|
180
|
+
if not self.card.capabilities.extensions:
|
|
181
|
+
self.card.capabilities.extensions = []
|
|
182
|
+
|
|
183
|
+
extension = AgentExtension(
|
|
184
|
+
uri=uri,
|
|
185
|
+
description=description,
|
|
186
|
+
required=required
|
|
187
|
+
)
|
|
188
|
+
self.card.capabilities.extensions.append(extension)
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
192
|
+
"""Convert the agent card to a dictionary."""
|
|
193
|
+
return self.card.model_dump(exclude_none=True)
|
|
194
|
+
|
|
195
|
+
def to_json(self, indent: Optional[int] = 2) -> str:
|
|
196
|
+
"""Convert the agent card to JSON."""
|
|
197
|
+
return self.card.model_dump_json(exclude_none=True, indent=indent)
|
|
198
|
+
|
|
199
|
+
def save_to_file(self, file_path: str) -> None:
|
|
200
|
+
"""Save the agent card to a JSON file."""
|
|
201
|
+
path = Path(file_path)
|
|
202
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
203
|
+
|
|
204
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
205
|
+
f.write(self.to_json())
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'AgentCard':
|
|
209
|
+
"""Create an AgentCard from a dictionary."""
|
|
210
|
+
card_model = AgentCardModel.model_validate(data)
|
|
211
|
+
instance = cls.__new__(cls)
|
|
212
|
+
instance.card = card_model
|
|
213
|
+
return instance
|
|
214
|
+
|
|
215
|
+
@classmethod
|
|
216
|
+
def from_json(cls, json_str: str) -> 'AgentCard':
|
|
217
|
+
"""Create an AgentCard from a JSON string."""
|
|
218
|
+
data = json.loads(json_str)
|
|
219
|
+
return cls.from_dict(data)
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def from_file(cls, file_path: str) -> 'AgentCard':
|
|
223
|
+
"""Load an AgentCard from a JSON file."""
|
|
224
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
225
|
+
return cls.from_json(f.read())
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class AgentCardBuilder:
|
|
229
|
+
"""Fluent builder for creating Agent Cards."""
|
|
230
|
+
|
|
231
|
+
def __init__(self):
|
|
232
|
+
self._name: Optional[str] = None
|
|
233
|
+
self._description: Optional[str] = None
|
|
234
|
+
self._url: Optional[str] = None
|
|
235
|
+
self._provider: Optional[AgentProvider] = None
|
|
236
|
+
self._capabilities: Optional[AgentCapabilities] = None
|
|
237
|
+
self._authentication: List[AuthenticationScheme] = []
|
|
238
|
+
self._skills: List[AgentSkill] = []
|
|
239
|
+
self._additional_interfaces: Optional[AdditionalInterfaces] = None
|
|
240
|
+
self._version: str = "1.0"
|
|
241
|
+
|
|
242
|
+
def name(self, name: str) -> 'AgentCardBuilder':
|
|
243
|
+
"""Set the agent name."""
|
|
244
|
+
self._name = name
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
def description(self, description: str) -> 'AgentCardBuilder':
|
|
248
|
+
"""Set the agent description."""
|
|
249
|
+
self._description = description
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
def url(self, url: str) -> 'AgentCardBuilder':
|
|
253
|
+
"""Set the agent URL."""
|
|
254
|
+
self._url = url
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
def provider(
|
|
258
|
+
self,
|
|
259
|
+
organization: str,
|
|
260
|
+
url: str
|
|
261
|
+
) -> 'AgentCardBuilder':
|
|
262
|
+
"""Set the agent provider information."""
|
|
263
|
+
self._provider = AgentProvider(
|
|
264
|
+
organization=organization,
|
|
265
|
+
url=url
|
|
266
|
+
)
|
|
267
|
+
return self
|
|
268
|
+
|
|
269
|
+
def with_streaming(self) -> 'AgentCardBuilder':
|
|
270
|
+
"""Enable streaming capability."""
|
|
271
|
+
if not self._capabilities:
|
|
272
|
+
self._capabilities = AgentCapabilities()
|
|
273
|
+
self._capabilities.streaming = True
|
|
274
|
+
return self
|
|
275
|
+
|
|
276
|
+
def with_push_notifications(self) -> 'AgentCardBuilder':
|
|
277
|
+
"""Enable push notifications capability."""
|
|
278
|
+
if not self._capabilities:
|
|
279
|
+
self._capabilities = AgentCapabilities()
|
|
280
|
+
self._capabilities.push_notifications = True
|
|
281
|
+
return self
|
|
282
|
+
|
|
283
|
+
def with_state_history(self) -> 'AgentCardBuilder':
|
|
284
|
+
"""Enable state transition history capability."""
|
|
285
|
+
if not self._capabilities:
|
|
286
|
+
self._capabilities = AgentCapabilities()
|
|
287
|
+
self._capabilities.state_transition_history = True
|
|
288
|
+
return self
|
|
289
|
+
|
|
290
|
+
def with_media(self) -> 'AgentCardBuilder':
|
|
291
|
+
"""Enable media capability."""
|
|
292
|
+
if not self._capabilities:
|
|
293
|
+
self._capabilities = AgentCapabilities()
|
|
294
|
+
self._capabilities.media = True
|
|
295
|
+
return self
|
|
296
|
+
|
|
297
|
+
def with_livekit_interface(
|
|
298
|
+
self,
|
|
299
|
+
token_endpoint: str,
|
|
300
|
+
join_url_template: Optional[str] = None,
|
|
301
|
+
server_managed: bool = True
|
|
302
|
+
) -> 'AgentCardBuilder':
|
|
303
|
+
"""Add LiveKit interface configuration."""
|
|
304
|
+
if not self._additional_interfaces:
|
|
305
|
+
self._additional_interfaces = AdditionalInterfaces()
|
|
306
|
+
|
|
307
|
+
self._additional_interfaces.livekit = LiveKitInterface(
|
|
308
|
+
token_endpoint=token_endpoint,
|
|
309
|
+
join_url_template=join_url_template,
|
|
310
|
+
server_managed=server_managed
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Also enable media capability
|
|
314
|
+
self.with_media()
|
|
315
|
+
|
|
316
|
+
return self
|
|
317
|
+
|
|
318
|
+
def with_extension(
|
|
319
|
+
self,
|
|
320
|
+
uri: str,
|
|
321
|
+
description: Optional[str] = None,
|
|
322
|
+
required: bool = False
|
|
323
|
+
) -> 'AgentCardBuilder':
|
|
324
|
+
"""Add a protocol extension."""
|
|
325
|
+
if not self._capabilities:
|
|
326
|
+
self._capabilities = AgentCapabilities()
|
|
327
|
+
if not self._capabilities.extensions:
|
|
328
|
+
self._capabilities.extensions = []
|
|
329
|
+
|
|
330
|
+
extension = AgentExtension(
|
|
331
|
+
uri=uri,
|
|
332
|
+
description=description,
|
|
333
|
+
required=required
|
|
334
|
+
)
|
|
335
|
+
self._capabilities.extensions.append(extension)
|
|
336
|
+
return self
|
|
337
|
+
|
|
338
|
+
def with_authentication(
|
|
339
|
+
self,
|
|
340
|
+
scheme: str,
|
|
341
|
+
description: Optional[str] = None
|
|
342
|
+
) -> 'AgentCardBuilder':
|
|
343
|
+
"""Add an authentication scheme."""
|
|
344
|
+
auth = AuthenticationScheme(
|
|
345
|
+
scheme=scheme,
|
|
346
|
+
description=description
|
|
347
|
+
)
|
|
348
|
+
self._authentication.append(auth)
|
|
349
|
+
return self
|
|
350
|
+
|
|
351
|
+
def with_skill(
|
|
352
|
+
self,
|
|
353
|
+
skill_id: str,
|
|
354
|
+
name: str,
|
|
355
|
+
description: str,
|
|
356
|
+
input_modes: Optional[List[str]] = None,
|
|
357
|
+
output_modes: Optional[List[str]] = None,
|
|
358
|
+
examples: Optional[List[Dict[str, Any]]] = None
|
|
359
|
+
) -> 'AgentCardBuilder':
|
|
360
|
+
"""Add a skill."""
|
|
361
|
+
skill = AgentSkill(
|
|
362
|
+
id=skill_id,
|
|
363
|
+
name=name,
|
|
364
|
+
description=description,
|
|
365
|
+
input_modes=input_modes or ["text"],
|
|
366
|
+
output_modes=output_modes or ["text"],
|
|
367
|
+
examples=examples
|
|
368
|
+
)
|
|
369
|
+
self._skills.append(skill)
|
|
370
|
+
return self
|
|
371
|
+
|
|
372
|
+
def version(self, version: str) -> 'AgentCardBuilder':
|
|
373
|
+
"""Set the agent card version."""
|
|
374
|
+
self._version = version
|
|
375
|
+
return self
|
|
376
|
+
|
|
377
|
+
def build(self) -> AgentCard:
|
|
378
|
+
"""Build the agent card."""
|
|
379
|
+
if not all([self._name, self._description, self._url, self._provider]):
|
|
380
|
+
raise ValueError("Name, description, URL, and provider are required")
|
|
381
|
+
|
|
382
|
+
return AgentCard(
|
|
383
|
+
name=self._name,
|
|
384
|
+
description=self._description,
|
|
385
|
+
url=self._url,
|
|
386
|
+
provider=self._provider,
|
|
387
|
+
capabilities=self._capabilities,
|
|
388
|
+
authentication=self._authentication,
|
|
389
|
+
skills=self._skills,
|
|
390
|
+
additional_interfaces=self._additional_interfaces,
|
|
391
|
+
version=self._version
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# Convenience function for creating agent cards
|
|
396
|
+
def create_agent_card(
|
|
397
|
+
name: str,
|
|
398
|
+
description: str,
|
|
399
|
+
url: str,
|
|
400
|
+
organization: str,
|
|
401
|
+
organization_url: str
|
|
402
|
+
) -> AgentCardBuilder:
|
|
403
|
+
"""Create a new agent card builder with basic information."""
|
|
404
|
+
return (AgentCardBuilder()
|
|
405
|
+
.name(name)
|
|
406
|
+
.description(description)
|
|
407
|
+
.url(url)
|
|
408
|
+
.provider(organization, organization_url))
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A2A Server using OpenAI Agents SDK
|
|
3
|
+
This replaces the custom MCP implementation with the official OpenAI Agents SDK.
|
|
4
|
+
"""
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from fastapi import FastAPI, HTTPException
|
|
11
|
+
from fastapi.responses import StreamingResponse
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from agents import Agent, Runner, function_tool
|
|
15
|
+
from agents.memory import SQLiteSession
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TaskRequest(BaseModel):
|
|
21
|
+
"""Request to create a task."""
|
|
22
|
+
input: str
|
|
23
|
+
session_id: Optional[str] = None
|
|
24
|
+
agent_name: Optional[str] = "Assistant"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TaskResponse(BaseModel):
|
|
28
|
+
"""Response from task execution."""
|
|
29
|
+
output: str
|
|
30
|
+
session_id: Optional[str] = None
|
|
31
|
+
timestamp: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Define agent tools using the @function_tool decorator
|
|
35
|
+
@function_tool
|
|
36
|
+
def calculator(operation: str, a: float, b: float = 0.0) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Perform mathematical calculations.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
operation: The operation to perform (add, subtract, multiply, divide, square, sqrt)
|
|
42
|
+
a: First number
|
|
43
|
+
b: Second number (optional for unary operations)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The result of the calculation as a string
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
if operation == "add":
|
|
50
|
+
result = a + b
|
|
51
|
+
elif operation == "subtract":
|
|
52
|
+
result = a - b
|
|
53
|
+
elif operation == "multiply":
|
|
54
|
+
result = a * b
|
|
55
|
+
elif operation == "divide":
|
|
56
|
+
if b == 0:
|
|
57
|
+
return "Error: Division by zero"
|
|
58
|
+
result = a / b
|
|
59
|
+
elif operation == "square":
|
|
60
|
+
result = a ** 2
|
|
61
|
+
elif operation == "sqrt":
|
|
62
|
+
if a < 0:
|
|
63
|
+
return "Error: Cannot take square root of negative number"
|
|
64
|
+
result = a ** 0.5
|
|
65
|
+
else:
|
|
66
|
+
return f"Error: Unknown operation '{operation}'"
|
|
67
|
+
|
|
68
|
+
return f"Result: {result}"
|
|
69
|
+
except Exception as e:
|
|
70
|
+
return f"Error: {str(e)}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@function_tool
|
|
74
|
+
def get_weather(city: str) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Get weather information for a city.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
city: The name of the city
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Weather information as a string
|
|
83
|
+
"""
|
|
84
|
+
# Mock weather data
|
|
85
|
+
return f"The weather in {city} is sunny with a temperature of 72°F (22°C)."
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@function_tool
|
|
89
|
+
def echo(message: str) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Echo back a message.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
message: The message to echo
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The same message
|
|
98
|
+
"""
|
|
99
|
+
return message
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AgentsServer:
|
|
103
|
+
"""FastAPI server using OpenAI Agents SDK."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, port: int = 9000):
|
|
106
|
+
self.app = FastAPI(title="A2A Agents Server", version="0.1.0")
|
|
107
|
+
self.port = port
|
|
108
|
+
self.sessions_db = "a2a_sessions.db"
|
|
109
|
+
|
|
110
|
+
# Create default agents
|
|
111
|
+
self.agents = {
|
|
112
|
+
"Assistant": Agent(
|
|
113
|
+
name="Assistant",
|
|
114
|
+
instructions="You are a helpful assistant with access to various tools.",
|
|
115
|
+
tools=[calculator, get_weather, echo]
|
|
116
|
+
),
|
|
117
|
+
"Calculator": Agent(
|
|
118
|
+
name="Calculator Agent",
|
|
119
|
+
instructions="You specialize in mathematical calculations. Use the calculator tool for all math operations.",
|
|
120
|
+
tools=[calculator]
|
|
121
|
+
),
|
|
122
|
+
"Weather": Agent(
|
|
123
|
+
name="Weather Agent",
|
|
124
|
+
instructions="You specialize in providing weather information. Use the weather tool to get current conditions.",
|
|
125
|
+
tools=[get_weather]
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Setup routes
|
|
130
|
+
self._setup_routes()
|
|
131
|
+
|
|
132
|
+
def _setup_routes(self):
|
|
133
|
+
"""Setup FastAPI routes."""
|
|
134
|
+
|
|
135
|
+
@self.app.get("/")
|
|
136
|
+
async def root():
|
|
137
|
+
"""Root endpoint with server info."""
|
|
138
|
+
return {
|
|
139
|
+
"name": "A2A Agents Server",
|
|
140
|
+
"version": "0.1.0",
|
|
141
|
+
"framework": "OpenAI Agents SDK",
|
|
142
|
+
"agents": list(self.agents.keys()),
|
|
143
|
+
"endpoints": {
|
|
144
|
+
"chat": "/chat",
|
|
145
|
+
"agents": "/agents",
|
|
146
|
+
"health": "/health"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@self.app.get("/health")
|
|
151
|
+
async def health():
|
|
152
|
+
"""Health check endpoint."""
|
|
153
|
+
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
|
|
154
|
+
|
|
155
|
+
@self.app.get("/agents")
|
|
156
|
+
async def list_agents():
|
|
157
|
+
"""List available agents."""
|
|
158
|
+
return {
|
|
159
|
+
"agents": [
|
|
160
|
+
{
|
|
161
|
+
"name": name,
|
|
162
|
+
"instructions": agent.instructions,
|
|
163
|
+
"tools": [tool.__name__ if hasattr(tool, '__name__') else str(tool) for tool in agent.tools]
|
|
164
|
+
}
|
|
165
|
+
for name, agent in self.agents.items()
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@self.app.post("/chat", response_model=TaskResponse)
|
|
170
|
+
async def chat(request: TaskRequest):
|
|
171
|
+
"""
|
|
172
|
+
Run an agent with the given input.
|
|
173
|
+
|
|
174
|
+
Supports session memory if session_id is provided.
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
# Get the agent
|
|
178
|
+
agent = self.agents.get(request.agent_name)
|
|
179
|
+
if not agent:
|
|
180
|
+
raise HTTPException(
|
|
181
|
+
status_code=404,
|
|
182
|
+
detail=f"Agent '{request.agent_name}' not found"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Create session if session_id provided
|
|
186
|
+
session = None
|
|
187
|
+
if request.session_id:
|
|
188
|
+
session = SQLiteSession(request.session_id, self.sessions_db)
|
|
189
|
+
|
|
190
|
+
# Run the agent
|
|
191
|
+
result = await Runner.run(
|
|
192
|
+
agent,
|
|
193
|
+
input=request.input,
|
|
194
|
+
session=session
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return TaskResponse(
|
|
198
|
+
output=result.final_output,
|
|
199
|
+
session_id=request.session_id,
|
|
200
|
+
timestamp=datetime.now().isoformat()
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Error running agent: {e}", exc_info=True)
|
|
205
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
206
|
+
|
|
207
|
+
@self.app.get("/chat/stream")
|
|
208
|
+
async def chat_stream(input: str, agent_name: str = "Assistant", session_id: Optional[str] = None):
|
|
209
|
+
"""
|
|
210
|
+
Stream agent responses using Server-Sent Events.
|
|
211
|
+
"""
|
|
212
|
+
async def event_generator():
|
|
213
|
+
try:
|
|
214
|
+
# Get the agent
|
|
215
|
+
agent = self.agents.get(agent_name)
|
|
216
|
+
if not agent:
|
|
217
|
+
yield f"data: {{'error': 'Agent not found'}}\n\n"
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
# Create session if needed
|
|
221
|
+
session = None
|
|
222
|
+
if session_id:
|
|
223
|
+
session = SQLiteSession(session_id, self.sessions_db)
|
|
224
|
+
|
|
225
|
+
# Stream the response
|
|
226
|
+
result = Runner.run_streamed(agent, input=input, session=session)
|
|
227
|
+
|
|
228
|
+
async for event in result.stream_events():
|
|
229
|
+
if event.type == "run_item_stream_event":
|
|
230
|
+
yield f"data: {{'type': 'chunk', 'content': '{event.item}'}}\n\n"
|
|
231
|
+
|
|
232
|
+
# Send final output
|
|
233
|
+
yield f"data: {{'type': 'final', 'output': '{result.final_output}'}}\n\n"
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Error in stream: {e}", exc_info=True)
|
|
237
|
+
yield f"data: {{'error': '{str(e)}'}}\n\n"
|
|
238
|
+
|
|
239
|
+
return StreamingResponse(
|
|
240
|
+
event_generator(),
|
|
241
|
+
media_type="text/event-stream",
|
|
242
|
+
headers={
|
|
243
|
+
"Cache-Control": "no-cache",
|
|
244
|
+
"Connection": "keep-alive",
|
|
245
|
+
"X-Accel-Buffering": "no"
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def start(self):
|
|
250
|
+
"""Start the server."""
|
|
251
|
+
import uvicorn
|
|
252
|
+
config = uvicorn.Config(
|
|
253
|
+
self.app,
|
|
254
|
+
host="0.0.0.0",
|
|
255
|
+
port=self.port,
|
|
256
|
+
log_level="info"
|
|
257
|
+
)
|
|
258
|
+
server = uvicorn.Server(config)
|
|
259
|
+
await server.serve()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def main():
|
|
263
|
+
"""Main entry point."""
|
|
264
|
+
server = AgentsServer(port=9000)
|
|
265
|
+
logger.info(f"Starting A2A Agents Server on port {server.port}")
|
|
266
|
+
await server.start()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == "__main__":
|
|
270
|
+
logging.basicConfig(level=logging.INFO)
|
|
271
|
+
asyncio.run(main())
|