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
@@ -0,0 +1,135 @@
1
+ from typing import Any, Dict, List
2
+
3
+ CREATE_TASK_SCHEMA = {
4
+ 'name': 'create_task',
5
+ 'description': 'Create a new task in the CodeTether system. Use this when the user wants to create and assign a new task.',
6
+ 'parameters': {
7
+ 'type': 'object',
8
+ 'properties': {
9
+ 'title': {
10
+ 'type': 'string',
11
+ 'description': 'The title or name of the task',
12
+ },
13
+ 'description': {
14
+ 'type': 'string',
15
+ 'description': 'A detailed description of what the task involves',
16
+ },
17
+ 'codebase_id': {
18
+ 'type': 'string',
19
+ 'description': "The ID of the codebase to associate with this task. Defaults to 'global' if not specified.",
20
+ },
21
+ 'agent_type': {
22
+ 'type': 'string',
23
+ 'description': "The type of agent to handle this task (e.g., 'build', 'test', 'review', 'documentation'). Defaults to 'build'.",
24
+ },
25
+ 'priority': {
26
+ 'type': 'integer',
27
+ 'description': 'The priority level of the task (0-10, where 10 is highest priority). Defaults to 0.',
28
+ },
29
+ },
30
+ 'required': ['title'],
31
+ },
32
+ }
33
+
34
+ LIST_TASKS_SCHEMA = {
35
+ 'name': 'list_tasks',
36
+ 'description': 'List tasks in the CodeTether system with optional filtering. Use this to show the user what tasks exist.',
37
+ 'parameters': {
38
+ 'type': 'object',
39
+ 'properties': {
40
+ 'status': {
41
+ 'type': 'string',
42
+ 'description': "Filter tasks by their status (e.g., 'pending', 'running', 'completed', 'failed', 'cancelled')",
43
+ },
44
+ 'codebase_id': {
45
+ 'type': 'string',
46
+ 'description': 'Filter tasks by codebase ID',
47
+ },
48
+ },
49
+ 'required': [],
50
+ },
51
+ }
52
+
53
+ GET_TASK_SCHEMA = {
54
+ 'name': 'get_task',
55
+ 'description': 'Get detailed information about a specific task by its ID. Use this when the user asks about a particular task.',
56
+ 'parameters': {
57
+ 'type': 'object',
58
+ 'properties': {
59
+ 'task_id': {
60
+ 'type': 'string',
61
+ 'description': 'The unique identifier of the task',
62
+ },
63
+ },
64
+ 'required': ['task_id'],
65
+ },
66
+ }
67
+
68
+ CANCEL_TASK_SCHEMA = {
69
+ 'name': 'cancel_task',
70
+ 'description': 'Cancel a running or pending task. Use this when the user wants to stop a task that is in progress.',
71
+ 'parameters': {
72
+ 'type': 'object',
73
+ 'properties': {
74
+ 'task_id': {
75
+ 'type': 'string',
76
+ 'description': 'The unique identifier of the task to cancel',
77
+ },
78
+ },
79
+ 'required': ['task_id'],
80
+ },
81
+ }
82
+
83
+ GET_SESSION_HISTORY_SCHEMA = {
84
+ 'name': 'get_session_history',
85
+ 'description': 'Retrieve the message history for a specific session. Use this when the user wants to review previous conversation context.',
86
+ 'parameters': {
87
+ 'type': 'object',
88
+ 'properties': {
89
+ 'session_id': {
90
+ 'type': 'string',
91
+ 'description': 'The unique identifier of the session',
92
+ },
93
+ },
94
+ 'required': ['session_id'],
95
+ },
96
+ }
97
+
98
+ DISCOVER_AGENTS_SCHEMA = {
99
+ 'name': 'discover_agents',
100
+ 'description': 'Discover available CodeTether agents that can be messaged. Use this to show the user what agents are available.',
101
+ 'parameters': {
102
+ 'type': 'object',
103
+ 'properties': {},
104
+ 'required': [],
105
+ },
106
+ }
107
+
108
+ SEND_MESSAGE_SCHEMA = {
109
+ 'name': 'send_message',
110
+ 'description': 'Send a message to a specific CodeTether agent. Use this for agent-to-agent communication.',
111
+ 'parameters': {
112
+ 'type': 'object',
113
+ 'properties': {
114
+ 'agent_name': {
115
+ 'type': 'string',
116
+ 'description': 'The name of the agent to send the message to',
117
+ },
118
+ 'message': {
119
+ 'type': 'string',
120
+ 'description': 'The message content to send to the agent',
121
+ },
122
+ },
123
+ 'required': ['agent_name', 'message'],
124
+ },
125
+ }
126
+
127
+ ALL_TOOL_SCHEMAS: List[Dict[str, Any]] = [
128
+ CREATE_TASK_SCHEMA,
129
+ LIST_TASKS_SCHEMA,
130
+ GET_TASK_SCHEMA,
131
+ CANCEL_TASK_SCHEMA,
132
+ GET_SESSION_HISTORY_SCHEMA,
133
+ DISCOVER_AGENTS_SCHEMA,
134
+ SEND_MESSAGE_SCHEMA,
135
+ ]
@@ -0,0 +1,380 @@
1
+ import logging
2
+ from typing import Any, Dict, Optional
3
+
4
+ try:
5
+ from codetether_voice_agent.codetether_mcp import CodeTetherMCP
6
+ except ImportError:
7
+ from codetether_mcp import CodeTetherMCP
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ async def create_task_handler(
13
+ mcp_client: CodeTetherMCP,
14
+ title: str,
15
+ description: str = '',
16
+ codebase_id: str = 'global',
17
+ agent_type: str = 'build',
18
+ priority: int = 0,
19
+ ) -> str:
20
+ """Create a new task and return a human-readable response.
21
+
22
+ Args:
23
+ mcp_client: The CodeTether MCP client instance.
24
+ title: The title of the task.
25
+ description: A detailed description of the task.
26
+ codebase_id: The codebase ID to associate with the task.
27
+ agent_type: The type of agent to handle the task.
28
+ priority: The priority level of the task (0-10).
29
+
30
+ Returns:
31
+ A string suitable for voice output describing the created task.
32
+ """
33
+ try:
34
+ task = await mcp_client.create_task(
35
+ title=title,
36
+ description=description,
37
+ codebase_id=codebase_id,
38
+ agent_type=agent_type,
39
+ priority=priority,
40
+ )
41
+
42
+ priority_label = 'low'
43
+ if priority >= 7:
44
+ priority_label = 'high'
45
+ elif priority >= 4:
46
+ priority_label = 'medium'
47
+
48
+ return (
49
+ f"I've created a new task titled '{task.title}'. "
50
+ f"The task ID is {task.id} and it's set to {task.status} status. "
51
+ f"It's assigned to a {task.agent_type} agent with {priority_label} priority."
52
+ )
53
+ except Exception as e:
54
+ logger.error(f'Failed to create task: {e}')
55
+ return f"I'm sorry, I couldn't create the task. An error occurred: {str(e)}"
56
+
57
+
58
+ async def list_tasks_handler(
59
+ mcp_client: CodeTetherMCP,
60
+ status: Optional[str] = None,
61
+ codebase_id: Optional[str] = None,
62
+ ) -> str:
63
+ """List tasks with optional filtering and return a human-readable response.
64
+
65
+ Args:
66
+ mcp_client: The CodeTether MCP client instance.
67
+ status: Optional status filter.
68
+ codebase_id: Optional codebase ID filter.
69
+
70
+ Returns:
71
+ A string suitable for voice output describing the tasks.
72
+ """
73
+ try:
74
+ tasks = await mcp_client.list_tasks(
75
+ status=status, codebase_id=codebase_id
76
+ )
77
+
78
+ if not tasks:
79
+ if status:
80
+ return f"There are no tasks with status '{status}'."
81
+ return 'There are no tasks in the system.'
82
+
83
+ task_count = len(tasks)
84
+ status_msg = f" with status '{status}'" if status else ''
85
+ codebase_msg = f" in codebase '{codebase_id}'" if codebase_id else ''
86
+
87
+ response = f'There are {task_count} task{"" if task_count == 1 else "s"}{status_msg}{codebase_msg}. '
88
+
89
+ if task_count <= 3:
90
+ task_details = []
91
+ for task in tasks:
92
+ task_details.append(f"'{task.title}' ({task.status})")
93
+ response += 'They are: ' + ', '.join(task_details) + '.'
94
+ else:
95
+ pending_count = sum(1 for t in tasks if t.status == 'pending')
96
+ running_count = sum(1 for t in tasks if t.status == 'running')
97
+ completed_count = sum(1 for t in tasks if t.status == 'completed')
98
+
99
+ response += f'Summary: {pending_count} pending, {running_count} running, {completed_count} completed. '
100
+ response += f"The first one is '{tasks[0].title}' with {tasks[0].status} status."
101
+
102
+ return response
103
+ except Exception as e:
104
+ logger.error(f'Failed to list tasks: {e}')
105
+ return f"I'm sorry, I couldn't retrieve the tasks. An error occurred: {str(e)}"
106
+
107
+
108
+ async def get_task_handler(
109
+ mcp_client: CodeTetherMCP,
110
+ task_id: str,
111
+ ) -> str:
112
+ """Get details of a specific task and return a human-readable response.
113
+
114
+ Args:
115
+ mcp_client: The CodeTether MCP client instance.
116
+ task_id: The unique identifier of the task.
117
+
118
+ Returns:
119
+ A string suitable for voice output describing the task details.
120
+ """
121
+ try:
122
+ task = await mcp_client.get_task(task_id)
123
+
124
+ if task is None:
125
+ return f"I couldn't find a task with ID '{task_id}'."
126
+
127
+ priority_label = 'low'
128
+ if task.priority >= 7:
129
+ priority_label = 'high'
130
+ elif task.priority >= 4:
131
+ priority_label = 'medium'
132
+
133
+ response = f"Task '{task.title}' has ID {task.id}. "
134
+ response += f"It's currently {task.status} and assigned to a {task.agent_type} agent. "
135
+ response += f'Priority is {priority_label} (level {task.priority}).'
136
+
137
+ if task.description:
138
+ response += f' The description is: {task.description}.'
139
+
140
+ if task.result:
141
+ response += ' The task has completed with a result.'
142
+ elif task.error:
143
+ response += f' However, there was an error: {task.error}'
144
+
145
+ return response
146
+ except Exception as e:
147
+ logger.error(f'Failed to get task: {e}')
148
+ return f"I'm sorry, I couldn't retrieve the task details. An error occurred: {str(e)}"
149
+
150
+
151
+ async def cancel_task_handler(
152
+ mcp_client: CodeTetherMCP,
153
+ task_id: str,
154
+ ) -> str:
155
+ """Cancel a task and return a human-readable response.
156
+
157
+ Args:
158
+ mcp_client: The CodeTether MCP client instance.
159
+ task_id: The unique identifier of the task to cancel.
160
+
161
+ Returns:
162
+ A string suitable for voice output describing the cancellation result.
163
+ """
164
+ try:
165
+ success = await mcp_client.cancel_task(task_id)
166
+
167
+ if success:
168
+ return f"I've successfully cancelled task {task_id}."
169
+ else:
170
+ return f"I wasn't able to cancel task {task_id}. It may have already been completed or cancelled."
171
+ except Exception as e:
172
+ logger.error(f'Failed to cancel task: {e}')
173
+ return f"I'm sorry, I couldn't cancel the task. An error occurred: {str(e)}"
174
+
175
+
176
+ async def get_session_history_handler(
177
+ mcp_client: CodeTetherMCP,
178
+ session_id: str,
179
+ ) -> str:
180
+ """Get session message history and return a human-readable response.
181
+
182
+ Args:
183
+ mcp_client: The CodeTether MCP client instance.
184
+ session_id: The unique identifier of the session.
185
+
186
+ Returns:
187
+ A string suitable for voice output describing the session history.
188
+ """
189
+ try:
190
+ messages = await mcp_client.get_session_messages(session_id)
191
+
192
+ if not messages:
193
+ return f"I couldn't find any messages for session '{session_id}'. The session may not exist or may be empty."
194
+
195
+ message_count = len(messages)
196
+ response = f'This session contains {message_count} message{"" if message_count == 1 else "s"}. '
197
+
198
+ user_messages = [m for m in messages if m.role == 'user']
199
+ assistant_messages = [
200
+ m for m in messages if m.role in ('assistant', 'agent')
201
+ ]
202
+
203
+ if user_messages:
204
+ response += f'It includes {len(user_messages)} from the user and {len(assistant_messages)} from assistants. '
205
+
206
+ if message_count <= 5:
207
+ response += "Here's what was discussed: "
208
+ for i, msg in enumerate(messages[:5]):
209
+ role = msg.role.capitalize()
210
+ content = (
211
+ msg.content[:100] + '...'
212
+ if len(msg.content) > 100
213
+ else msg.content
214
+ )
215
+ response += f'{role}: {content}. '
216
+ else:
217
+ response += f"The most recent message is from the {messages[-1].role}: '{messages[-1].content[:100]}...'"
218
+
219
+ return response
220
+ except Exception as e:
221
+ logger.error(f'Failed to get session history: {e}')
222
+ return f"I'm sorry, I couldn't retrieve the session history. An error occurred: {str(e)}"
223
+
224
+
225
+ async def discover_agents_handler(
226
+ mcp_client: CodeTetherMCP,
227
+ ) -> str:
228
+ """Discover available agents and return a human-readable response.
229
+
230
+ Args:
231
+ mcp_client: The CodeTether MCP client instance.
232
+
233
+ Returns:
234
+ A string suitable for voice output listing the available agents.
235
+ """
236
+ try:
237
+ agents = await mcp_client.discover_agents()
238
+
239
+ if not agents:
240
+ return 'There are no agents currently available in the system.'
241
+
242
+ agent_count = len(agents)
243
+ response = f'I found {agent_count} available agent{"" if agent_count == 1 else "s"}: '
244
+
245
+ agent_names = [agent.name for agent in agents]
246
+ agent_descriptions = [
247
+ agent.description for agent in agents if agent.description
248
+ ]
249
+
250
+ response += ', '.join(agent_names) + '. '
251
+
252
+ if agent_descriptions:
253
+ response += 'Notable agents include: '
254
+ for i, (name, desc) in enumerate(
255
+ zip(agent_names[:3], agent_descriptions[:3])
256
+ ):
257
+ response += f'{name} - {desc}. '
258
+
259
+ return response
260
+ except Exception as e:
261
+ logger.error(f'Failed to discover agents: {e}')
262
+ return f"I'm sorry, I couldn't discover available agents. An error occurred: {str(e)}"
263
+
264
+
265
+ async def send_message_handler(
266
+ mcp_client: CodeTetherMCP,
267
+ agent_name: str,
268
+ message: str,
269
+ ) -> str:
270
+ """Send a message to an agent and return a human-readable response.
271
+
272
+ Args:
273
+ mcp_client: The CodeTether MCP client instance.
274
+ agent_name: The name of the agent to send the message to.
275
+ message: The message content to send.
276
+
277
+ Returns:
278
+ A string suitable for voice output describing the message sending result.
279
+ """
280
+ try:
281
+ result = await mcp_client.send_message(
282
+ agent_name=agent_name, message=message
283
+ )
284
+
285
+ response = f'Message sent to {agent_name}. '
286
+
287
+ if isinstance(result, dict):
288
+ if result.get('success'):
289
+ response += 'The agent has received your message.'
290
+ if 'response' in result:
291
+ response += f' They responded: {result["response"][:200]}...'
292
+ if 'error' in result:
293
+ response += f' However, there was an issue: {result["error"]}'
294
+ else:
295
+ response += 'The message was delivered successfully.'
296
+
297
+ return response
298
+ except Exception as e:
299
+ logger.error(f'Failed to send message: {e}')
300
+ return f"I'm sorry, I couldn't send the message to {agent_name}. An error occurred: {str(e)}"
301
+
302
+
303
+ async def register_all_tools(
304
+ mcp_client: CodeTetherMCP,
305
+ ) -> Dict[str, Any]:
306
+ """Register all tool handlers with their schemas for the voice agent.
307
+
308
+ Args:
309
+ mcp_client: The CodeTether MCP client instance.
310
+
311
+ Returns:
312
+ A dictionary mapping tool names to their handler functions.
313
+ """
314
+ from .definitions import (
315
+ CREATE_TASK_SCHEMA,
316
+ LIST_TASKS_SCHEMA,
317
+ GET_TASK_SCHEMA,
318
+ CANCEL_TASK_SCHEMA,
319
+ GET_SESSION_HISTORY_SCHEMA,
320
+ DISCOVER_AGENTS_SCHEMA,
321
+ SEND_MESSAGE_SCHEMA,
322
+ )
323
+
324
+ tools = {
325
+ 'create_task': {
326
+ 'schema': CREATE_TASK_SCHEMA,
327
+ 'handler': lambda args: create_task_handler(
328
+ mcp_client,
329
+ title=args.get('title', ''),
330
+ description=args.get('description', ''),
331
+ codebase_id=args.get('codebase_id', 'global'),
332
+ agent_type=args.get('agent_type', 'build'),
333
+ priority=args.get('priority', 0),
334
+ ),
335
+ },
336
+ 'list_tasks': {
337
+ 'schema': LIST_TASKS_SCHEMA,
338
+ 'handler': lambda args: list_tasks_handler(
339
+ mcp_client,
340
+ status=args.get('status'),
341
+ codebase_id=args.get('codebase_id'),
342
+ ),
343
+ },
344
+ 'get_task': {
345
+ 'schema': GET_TASK_SCHEMA,
346
+ 'handler': lambda args: get_task_handler(
347
+ mcp_client,
348
+ task_id=args.get('task_id', ''),
349
+ ),
350
+ },
351
+ 'cancel_task': {
352
+ 'schema': CANCEL_TASK_SCHEMA,
353
+ 'handler': lambda args: cancel_task_handler(
354
+ mcp_client,
355
+ task_id=args.get('task_id', ''),
356
+ ),
357
+ },
358
+ 'get_session_history': {
359
+ 'schema': GET_SESSION_HISTORY_SCHEMA,
360
+ 'handler': lambda args: get_session_history_handler(
361
+ mcp_client,
362
+ session_id=args.get('session_id', ''),
363
+ ),
364
+ },
365
+ 'discover_agents': {
366
+ 'schema': DISCOVER_AGENTS_SCHEMA,
367
+ 'handler': lambda args: discover_agents_handler(mcp_client),
368
+ },
369
+ 'send_message': {
370
+ 'schema': SEND_MESSAGE_SCHEMA,
371
+ 'handler': lambda args: send_message_handler(
372
+ mcp_client,
373
+ agent_name=args.get('agent_name', ''),
374
+ message=args.get('message', ''),
375
+ ),
376
+ },
377
+ }
378
+
379
+ logger.info(f'Registered {len(tools)} tools for voice agent')
380
+ return tools