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.
Files changed (66) hide show
  1. a2a_server/__init__.py +29 -0
  2. a2a_server/a2a_agent_card.py +365 -0
  3. a2a_server/a2a_errors.py +1133 -0
  4. a2a_server/a2a_executor.py +926 -0
  5. a2a_server/a2a_router.py +1033 -0
  6. a2a_server/a2a_types.py +344 -0
  7. a2a_server/agent_card.py +408 -0
  8. a2a_server/agents_server.py +271 -0
  9. a2a_server/auth_api.py +349 -0
  10. a2a_server/billing_api.py +638 -0
  11. a2a_server/billing_service.py +712 -0
  12. a2a_server/billing_webhooks.py +501 -0
  13. a2a_server/config.py +96 -0
  14. a2a_server/database.py +2165 -0
  15. a2a_server/email_inbound.py +398 -0
  16. a2a_server/email_notifications.py +486 -0
  17. a2a_server/enhanced_agents.py +919 -0
  18. a2a_server/enhanced_server.py +160 -0
  19. a2a_server/hosted_worker.py +1049 -0
  20. a2a_server/integrated_agents_server.py +347 -0
  21. a2a_server/keycloak_auth.py +750 -0
  22. a2a_server/livekit_bridge.py +439 -0
  23. a2a_server/marketing_tools.py +1364 -0
  24. a2a_server/mcp_client.py +196 -0
  25. a2a_server/mcp_http_server.py +2256 -0
  26. a2a_server/mcp_server.py +191 -0
  27. a2a_server/message_broker.py +725 -0
  28. a2a_server/mock_mcp.py +273 -0
  29. a2a_server/models.py +494 -0
  30. a2a_server/monitor_api.py +5904 -0
  31. a2a_server/opencode_bridge.py +1594 -0
  32. a2a_server/redis_task_manager.py +518 -0
  33. a2a_server/server.py +726 -0
  34. a2a_server/task_manager.py +668 -0
  35. a2a_server/task_queue.py +742 -0
  36. a2a_server/tenant_api.py +333 -0
  37. a2a_server/tenant_middleware.py +219 -0
  38. a2a_server/tenant_service.py +760 -0
  39. a2a_server/user_auth.py +721 -0
  40. a2a_server/vault_client.py +576 -0
  41. a2a_server/worker_sse.py +873 -0
  42. agent_worker/__init__.py +8 -0
  43. agent_worker/worker.py +4877 -0
  44. codetether/__init__.py +10 -0
  45. codetether/__main__.py +4 -0
  46. codetether/cli.py +112 -0
  47. codetether/worker_cli.py +57 -0
  48. codetether-1.2.2.dist-info/METADATA +570 -0
  49. codetether-1.2.2.dist-info/RECORD +66 -0
  50. codetether-1.2.2.dist-info/WHEEL +5 -0
  51. codetether-1.2.2.dist-info/entry_points.txt +4 -0
  52. codetether-1.2.2.dist-info/licenses/LICENSE +202 -0
  53. codetether-1.2.2.dist-info/top_level.txt +5 -0
  54. codetether_voice_agent/__init__.py +6 -0
  55. codetether_voice_agent/agent.py +445 -0
  56. codetether_voice_agent/codetether_mcp.py +345 -0
  57. codetether_voice_agent/config.py +16 -0
  58. codetether_voice_agent/functiongemma_caller.py +380 -0
  59. codetether_voice_agent/session_playback.py +247 -0
  60. codetether_voice_agent/tools/__init__.py +21 -0
  61. codetether_voice_agent/tools/definitions.py +135 -0
  62. codetether_voice_agent/tools/handlers.py +380 -0
  63. run_server.py +314 -0
  64. ui/monitor-tailwind.html +1790 -0
  65. ui/monitor.html +1775 -0
  66. ui/monitor.js +2662 -0
a2a_server/models.py ADDED
@@ -0,0 +1,494 @@
1
+ """
2
+ Pydantic models for A2A protocol data structures.
3
+
4
+ These models are based on the A2A specification and provide validation
5
+ and serialization for all protocol objects.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional, Union, Literal
9
+ from pydantic import BaseModel, Field
10
+ from datetime import datetime
11
+ from enum import Enum
12
+
13
+
14
+ class AgentProvider(BaseModel):
15
+ """Represents the service provider of an agent."""
16
+
17
+ organization: str = Field(
18
+ ..., description="The name of the agent provider's organization"
19
+ )
20
+ url: str = Field(
21
+ ...,
22
+ description="A URL for the agent provider's website or relevant documentation",
23
+ )
24
+
25
+
26
+ class AgentExtension(BaseModel):
27
+ """A declaration of a protocol extension supported by an Agent."""
28
+
29
+ uri: str = Field(
30
+ ..., description='The unique URI identifying the extension'
31
+ )
32
+ description: Optional[str] = Field(
33
+ None,
34
+ description='A human-readable description of how this agent uses the extension',
35
+ )
36
+ required: bool = Field(
37
+ False,
38
+ description="If true, the client must understand and comply with the extension's requirements",
39
+ )
40
+
41
+
42
+ class LiveKitInterface(BaseModel):
43
+ """Configuration for LiveKit media interface."""
44
+
45
+ token_endpoint: str = Field(
46
+ ..., description='Endpoint for obtaining LiveKit access tokens'
47
+ )
48
+ join_url_template: Optional[str] = Field(
49
+ None, description='Template for generating join URLs'
50
+ )
51
+ server_managed: bool = Field(
52
+ True, description='Whether the server manages LiveKit resources'
53
+ )
54
+
55
+
56
+ class AdditionalInterfaces(BaseModel):
57
+ """Additional interfaces supported by the agent beyond core A2A."""
58
+
59
+ model_config = {'extra': 'allow'}
60
+
61
+ livekit: Optional[LiveKitInterface] = Field(
62
+ None, description='LiveKit real-time media interface configuration'
63
+ )
64
+
65
+
66
+ class AgentCapabilities(BaseModel):
67
+ """Defines optional capabilities supported by an agent."""
68
+
69
+ streaming: Optional[bool] = Field(
70
+ None,
71
+ description='Indicates if the agent supports Server-Sent Events (SSE) for streaming responses',
72
+ )
73
+ push_notifications: Optional[bool] = Field(
74
+ None,
75
+ description='Indicates if the agent supports sending push notifications for asynchronous task updates',
76
+ )
77
+ state_transition_history: Optional[bool] = Field(
78
+ None,
79
+ description='Indicates if the agent provides a history of state transitions for a task',
80
+ )
81
+ media: Optional[bool] = Field(
82
+ None,
83
+ description='Indicates if the agent supports real-time media sessions',
84
+ )
85
+ extensions: Optional[List[AgentExtension]] = Field(
86
+ None, description='A list of protocol extensions supported by the agent'
87
+ )
88
+
89
+
90
+ class AgentSkill(BaseModel):
91
+ """Describes a specific skill or capability that an agent can perform."""
92
+
93
+ id: str = Field(
94
+ ..., description='A unique identifier for the skill within the agent'
95
+ )
96
+ name: str = Field(..., description='A human-readable name for the skill')
97
+ description: str = Field(
98
+ ..., description='A detailed description of what the skill does'
99
+ )
100
+ input_modes: List[str] = Field(
101
+ default_factory=list,
102
+ description='List of supported input content types',
103
+ )
104
+ output_modes: List[str] = Field(
105
+ default_factory=list,
106
+ description='List of supported output content types',
107
+ )
108
+ examples: Optional[List[Dict[str, Any]]] = Field(
109
+ None, description='Example inputs and outputs for the skill'
110
+ )
111
+
112
+
113
+ class AuthenticationScheme(BaseModel):
114
+ """Describes an authentication scheme required by the agent."""
115
+
116
+ scheme: str = Field(
117
+ ...,
118
+ description="The authentication scheme (e.g., 'Bearer', 'Basic', 'OAuth2')",
119
+ )
120
+ description: Optional[str] = Field(
121
+ None,
122
+ description='Human-readable description of the authentication requirements',
123
+ )
124
+
125
+
126
+ class AgentCard(BaseModel):
127
+ """The Agent Card is the core discovery document for an A2A agent."""
128
+
129
+ name: str = Field(..., description='A human-readable name for the agent')
130
+ description: str = Field(
131
+ ..., description="A description of the agent's purpose and capabilities"
132
+ )
133
+ url: str = Field(
134
+ ..., description='The base URL where the A2A server can be reached'
135
+ )
136
+ provider: AgentProvider = Field(
137
+ ...,
138
+ description='Information about the organization providing this agent',
139
+ )
140
+ capabilities: Optional[AgentCapabilities] = Field(
141
+ None, description='Optional capabilities supported by the agent'
142
+ )
143
+ authentication: List[AuthenticationScheme] = Field(
144
+ default_factory=list,
145
+ description='Authentication schemes required to interact with the agent',
146
+ )
147
+ skills: List[AgentSkill] = Field(
148
+ default_factory=list, description='List of skills the agent can perform'
149
+ )
150
+ additional_interfaces: Optional[AdditionalInterfaces] = Field(
151
+ None, description='Additional interfaces supported beyond core A2A'
152
+ )
153
+ version: str = Field('1.0', description='Version of the agent card format')
154
+
155
+
156
+ class TaskStatus(str, Enum):
157
+ """
158
+ Enumeration of possible task statuses.
159
+
160
+ Aligned with the A2A protocol specification:
161
+ - submitted: Task created and acknowledged
162
+ - working: Actively processing
163
+ - completed: Finished successfully (TERMINAL)
164
+ - failed: Done but failed (TERMINAL)
165
+ - cancelled: Cancelled before completion (TERMINAL)
166
+ - input-required: Awaiting additional input
167
+ - rejected: Agent declined the task (TERMINAL)
168
+ - auth-required: Needs out-of-band authentication
169
+
170
+ Note: PENDING is deprecated and maps to SUBMITTED for backwards compatibility.
171
+ """
172
+
173
+ # A2A Protocol States
174
+ SUBMITTED = 'submitted'
175
+ WORKING = 'working'
176
+ INPUT_REQUIRED = 'input-required'
177
+ COMPLETED = 'completed'
178
+ CANCELLED = 'cancelled'
179
+ FAILED = 'failed'
180
+ REJECTED = 'rejected'
181
+ AUTH_REQUIRED = 'auth-required'
182
+
183
+ # Legacy alias - maps to SUBMITTED for backwards compatibility
184
+ PENDING = 'pending'
185
+
186
+ @classmethod
187
+ def from_string(cls, value: str) -> 'TaskStatus':
188
+ """
189
+ Convert a string to TaskStatus, handling legacy 'pending' value.
190
+
191
+ Args:
192
+ value: String representation of the status
193
+
194
+ Returns:
195
+ TaskStatus enum value
196
+
197
+ Raises:
198
+ ValueError: If the value is not a valid status
199
+ """
200
+ # Normalize to lowercase
201
+ normalized = value.lower().strip()
202
+
203
+ # Handle legacy 'pending' -> 'submitted' mapping
204
+ if normalized == 'pending':
205
+ return cls.SUBMITTED
206
+
207
+ try:
208
+ return cls(normalized)
209
+ except ValueError:
210
+ raise ValueError(
211
+ f"Invalid task status: '{value}'. "
212
+ f'Valid values: {[s.value for s in cls]}'
213
+ )
214
+
215
+ def is_terminal(self) -> bool:
216
+ """Check if this status is a terminal state."""
217
+ return self in _TERMINAL_STATUSES
218
+
219
+ def is_active(self) -> bool:
220
+ """Check if this status is an active (non-terminal) state."""
221
+ return self not in _TERMINAL_STATUSES
222
+
223
+
224
+ # Terminal states - tasks in these states cannot transition further
225
+ _TERMINAL_STATUSES = {
226
+ TaskStatus.COMPLETED,
227
+ TaskStatus.FAILED,
228
+ TaskStatus.CANCELLED,
229
+ TaskStatus.REJECTED,
230
+ }
231
+
232
+
233
+ class Task(BaseModel):
234
+ """Represents a stateful unit of work being processed by an A2A Server for an A2A Client."""
235
+
236
+ id: str = Field(..., description='A unique identifier for the task')
237
+ status: TaskStatus = Field(
238
+ ..., description='The current status of the task'
239
+ )
240
+ created_at: datetime = Field(..., description='When the task was created')
241
+ updated_at: datetime = Field(
242
+ ..., description='When the task was last updated'
243
+ )
244
+ title: Optional[str] = Field(
245
+ None, description='A human-readable title for the task'
246
+ )
247
+ description: Optional[str] = Field(
248
+ None, description='A description of what the task is doing'
249
+ )
250
+ progress: Optional[float] = Field(
251
+ None, ge=0.0, le=1.0, description='Progress percentage (0.0 to 1.0)'
252
+ )
253
+ messages: Optional[List['Message']] = Field(
254
+ None, description='Messages exchanged during the task'
255
+ )
256
+ worker_id: Optional[str] = Field(
257
+ None, description='ID of the worker that claimed this task'
258
+ )
259
+ claimed_at: Optional[datetime] = Field(
260
+ None, description='When the task was claimed by a worker'
261
+ )
262
+
263
+
264
+ class Part(BaseModel):
265
+ """A component of a message that can contain text, files, or structured data."""
266
+
267
+ type: str = Field(
268
+ ..., description="The type of content (e.g., 'text', 'file', 'data')"
269
+ )
270
+ content: Any = Field(..., description='The actual content of the part')
271
+ metadata: Optional[Dict[str, Any]] = Field(
272
+ None, description='Additional metadata for the part'
273
+ )
274
+
275
+
276
+ class Message(BaseModel):
277
+ """A message exchanged between agents."""
278
+
279
+ parts: List[Part] = Field(..., description='List of message parts')
280
+ metadata: Optional[Dict[str, Any]] = Field(
281
+ None, description='Additional message metadata'
282
+ )
283
+
284
+
285
+ class JSONRPCRequest(BaseModel):
286
+ """JSON-RPC 2.0 request structure."""
287
+
288
+ jsonrpc: Literal['2.0'] = Field('2.0', description='JSON-RPC version')
289
+ method: str = Field(..., description='The name of the method to be invoked')
290
+ params: Optional[Dict[str, Any]] = Field(
291
+ None, description='Parameters for the method'
292
+ )
293
+ id: Optional[Union[str, int]] = Field(
294
+ None, description='Unique identifier for the request'
295
+ )
296
+
297
+
298
+ class JSONRPCResponse(BaseModel):
299
+ """JSON-RPC 2.0 response structure."""
300
+
301
+ jsonrpc: Literal['2.0'] = Field('2.0', description='JSON-RPC version')
302
+ id: Optional[Union[str, int]] = Field(
303
+ None, description='Identifier matching the request'
304
+ )
305
+ result: Optional[Any] = Field(
306
+ None, description='The result of the method call'
307
+ )
308
+ error: Optional[Dict[str, Any]] = Field(
309
+ None, description='Error information if the call failed'
310
+ )
311
+
312
+
313
+ class JSONRPCError(BaseModel):
314
+ """JSON-RPC 2.0 error structure."""
315
+
316
+ code: int = Field(..., description='A number that indicates the error type')
317
+ message: str = Field(..., description='A short description of the error')
318
+ data: Optional[Any] = Field(
319
+ None, description='Additional information about the error'
320
+ )
321
+
322
+
323
+ # Method-specific request/response models
324
+ class SendMessageRequest(BaseModel):
325
+ """Request to send a message to an agent."""
326
+
327
+ message: Message = Field(..., description='The message to send')
328
+ task_id: Optional[str] = Field(
329
+ None, description='Optional task ID to associate with the message'
330
+ )
331
+ skill_id: Optional[str] = Field(
332
+ None, description='Optional skill ID to use for processing'
333
+ )
334
+
335
+
336
+ class SendMessageResponse(BaseModel):
337
+ """Response from sending a message."""
338
+
339
+ task: Task = Field(
340
+ ..., description='The task created or updated by the message'
341
+ )
342
+ message: Optional[Message] = Field(
343
+ None, description='Optional response message'
344
+ )
345
+
346
+
347
+ class GetTaskRequest(BaseModel):
348
+ """Request to get information about a task."""
349
+
350
+ task_id: str = Field(..., description='The ID of the task to retrieve')
351
+
352
+
353
+ class GetTaskResponse(BaseModel):
354
+ """Response containing task information."""
355
+
356
+ task: Task = Field(..., description='The requested task')
357
+
358
+
359
+ class CancelTaskRequest(BaseModel):
360
+ """Request to cancel a task."""
361
+
362
+ task_id: str = Field(..., description='The ID of the task to cancel')
363
+
364
+
365
+ class CancelTaskResponse(BaseModel):
366
+ """Response confirming task cancellation."""
367
+
368
+ task: Task = Field(..., description='The cancelled task')
369
+
370
+
371
+ class StreamMessageRequest(BaseModel):
372
+ """Request to stream a message with real-time updates."""
373
+
374
+ message: Message = Field(..., description='The message to send')
375
+ task_id: Optional[str] = Field(
376
+ None, description='Optional task ID to associate with the message'
377
+ )
378
+ skill_id: Optional[str] = Field(
379
+ None, description='Optional skill ID to use for processing'
380
+ )
381
+
382
+
383
+ class TaskStatusUpdateEvent(BaseModel):
384
+ """Event indicating a change in task status."""
385
+
386
+ task: Task = Field(..., description='The updated task')
387
+ message: Optional[Message] = Field(
388
+ None, description='Optional message associated with the update'
389
+ )
390
+ final: bool = Field(
391
+ False, description='Whether this is the final update for the task'
392
+ )
393
+
394
+
395
+ class StreamingMessageResponse(BaseModel):
396
+ """Response for streaming message operations."""
397
+
398
+ event: TaskStatusUpdateEvent = Field(
399
+ ..., description='The task status update event'
400
+ )
401
+
402
+
403
+ # Media-specific request/response models
404
+ class MediaRequestRequest(BaseModel):
405
+ """Request to create or join a media session."""
406
+
407
+ room_name: Optional[str] = Field(
408
+ None,
409
+ description='Specific room name to create/join (auto-generated if not provided)',
410
+ )
411
+ participant_identity: str = Field(
412
+ ..., description='Identity of the participant'
413
+ )
414
+ role: str = Field(
415
+ 'participant',
416
+ description='Role for the participant (admin, moderator, publisher, participant, viewer)',
417
+ )
418
+ metadata: Optional[Dict[str, Any]] = Field(
419
+ None, description='Optional metadata for the room or participant'
420
+ )
421
+ max_participants: int = Field(
422
+ 50,
423
+ description='Maximum number of participants (only used when creating new rooms)',
424
+ )
425
+
426
+
427
+ class MediaRequestResponse(BaseModel):
428
+ """Response containing media session information."""
429
+
430
+ task: Task = Field(..., description='The task managing this media session')
431
+ room_name: str = Field(..., description='Name of the LiveKit room')
432
+ room_sid: Optional[str] = Field(None, description='LiveKit room SID')
433
+ join_url: str = Field(..., description='URL to join the media session')
434
+ access_token: str = Field(..., description='LiveKit access token')
435
+ participant_identity: str = Field(
436
+ ..., description='Identity of the participant'
437
+ )
438
+ expires_at: datetime = Field(
439
+ ..., description='When the access token expires'
440
+ )
441
+
442
+
443
+ class MediaJoinRequest(BaseModel):
444
+ """Request to join an existing media session."""
445
+
446
+ room_name: str = Field(..., description='Name of the room to join')
447
+ participant_identity: str = Field(
448
+ ..., description='Identity of the participant'
449
+ )
450
+ role: str = Field('participant', description='Role for the participant')
451
+ metadata: Optional[str] = Field(
452
+ None, description='Optional metadata for the participant'
453
+ )
454
+
455
+
456
+ class MediaJoinResponse(BaseModel):
457
+ """Response for joining a media session."""
458
+
459
+ join_url: str = Field(..., description='URL to join the media session')
460
+ access_token: str = Field(..., description='LiveKit access token')
461
+ participant_identity: str = Field(
462
+ ..., description='Identity of the participant'
463
+ )
464
+ expires_at: datetime = Field(
465
+ ..., description='When the access token expires'
466
+ )
467
+
468
+
469
+ class LiveKitTokenRequest(BaseModel):
470
+ """Request for a LiveKit access token."""
471
+
472
+ room_name: str = Field(..., description='Name of the room')
473
+ identity: str = Field(..., description='Identity of the participant')
474
+ role: str = Field('participant', description='Role for the participant')
475
+ metadata: Optional[str] = Field(
476
+ None, description='Optional metadata for the participant'
477
+ )
478
+ ttl_minutes: int = Field(
479
+ 60, ge=1, le=1440, description='Token time-to-live in minutes (1-1440)'
480
+ )
481
+
482
+
483
+ class LiveKitTokenResponse(BaseModel):
484
+ """Response containing LiveKit access token."""
485
+
486
+ access_token: str = Field(..., description='LiveKit JWT access token')
487
+ join_url: str = Field(..., description='URL to join the media session')
488
+ expires_at: datetime = Field(
489
+ ..., description='When the access token expires'
490
+ )
491
+
492
+
493
+ # Rebuild Task model to resolve forward reference to Message
494
+ Task.model_rebuild()