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,345 @@
1
+ """CodeTether MCP Client Module.
2
+
3
+ This module provides an async client to interact with CodeTether's MCP (Model Context Protocol)
4
+ tools and APIs.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import uuid
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ import aiohttp
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class MCPError(Exception):
19
+ """Exception raised for MCP-specific errors."""
20
+
21
+ def __init__(
22
+ self,
23
+ message: str,
24
+ code: Optional[int] = None,
25
+ data: Optional[Any] = None,
26
+ ):
27
+ self.message = message
28
+ self.code = code
29
+ self.data = data
30
+ super().__init__(self.message)
31
+
32
+
33
+ @dataclass
34
+ class Task:
35
+ """Represents a CodeTether task."""
36
+
37
+ id: str
38
+ title: str
39
+ description: str = ''
40
+ status: str = 'pending'
41
+ codebase_id: str = 'global'
42
+ agent_type: str = 'build'
43
+ priority: int = 0
44
+ created_at: Optional[str] = None
45
+ result: Optional[Dict[str, Any]] = None
46
+ error: Optional[str] = None
47
+
48
+
49
+ @dataclass
50
+ class Agent:
51
+ """Represents a CodeTether agent."""
52
+
53
+ name: str
54
+ description: str = ''
55
+ url: str = ''
56
+
57
+
58
+ @dataclass
59
+ class Message:
60
+ """Represents a message in a session."""
61
+
62
+ role: str
63
+ content: str
64
+ timestamp: Optional[str] = None
65
+
66
+
67
+ class CodeTetherMCP:
68
+ """Async client for CodeTether MCP tools and APIs.
69
+
70
+ Args:
71
+ api_url: The base URL for the CodeTether API.
72
+ """
73
+
74
+ def __init__(self, api_url: str):
75
+ self.api_url = api_url.rstrip('/')
76
+ self.mcp_url = f'{self.api_url}/mcp'
77
+ self._session: Optional[aiohttp.ClientSession] = None
78
+
79
+ async def _get_session(self) -> aiohttp.ClientSession:
80
+ """Get or create an aiohttp client session.
81
+
82
+ Returns:
83
+ The aiohttp ClientSession instance.
84
+ """
85
+ if self._session is None or self._session.closed:
86
+ timeout = aiohttp.ClientTimeout(total=30)
87
+ self._session = aiohttp.ClientSession(timeout=timeout)
88
+ return self._session
89
+
90
+ async def close(self):
91
+ """Close the client session."""
92
+ if self._session and not self._session.closed:
93
+ await self._session.close()
94
+ self._session = None
95
+
96
+ async def __aenter__(self):
97
+ return self
98
+
99
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
100
+ await self.close()
101
+
102
+ async def call_tool(
103
+ self, tool_name: str, arguments: Dict[str, Any]
104
+ ) -> Dict[str, Any]:
105
+ """Call an MCP tool with the given name and arguments.
106
+
107
+ Args:
108
+ tool_name: The name of the tool to call.
109
+ arguments: The arguments to pass to the tool.
110
+
111
+ Returns:
112
+ The result from the tool call.
113
+
114
+ Raises:
115
+ MCPError: If the tool call fails.
116
+ """
117
+ session = await self._get_session()
118
+ request_id = str(uuid.uuid4())
119
+
120
+ payload = {
121
+ 'jsonrpc': '2.0',
122
+ 'id': request_id,
123
+ 'method': 'tools/call',
124
+ 'params': {'name': tool_name, 'arguments': arguments},
125
+ }
126
+
127
+ logger.info(
128
+ f'Calling MCP tool: {tool_name} with request_id: {request_id}'
129
+ )
130
+
131
+ try:
132
+ async with session.post(
133
+ self.mcp_url,
134
+ json=payload,
135
+ headers={'Content-Type': 'application/json'},
136
+ ) as response:
137
+ if response.status != 200:
138
+ error_text = await response.text()
139
+ logger.error(
140
+ f'MCP tool call failed with status {response.status}: {error_text}'
141
+ )
142
+ raise MCPError(f'HTTP {response.status}: {error_text}')
143
+
144
+ result = await response.json()
145
+
146
+ if 'error' in result and result['error'] is not None:
147
+ error = result['error']
148
+ logger.error(f'MCP tool call error: {error}')
149
+ raise MCPError(
150
+ message=error.get('message', 'Unknown error'),
151
+ code=error.get('code'),
152
+ data=error.get('data'),
153
+ )
154
+
155
+ if 'result' not in result:
156
+ logger.error(f'MCP tool call returned no result: {result}')
157
+ raise MCPError('MCP tool call returned no result')
158
+
159
+ logger.info(f'MCP tool {tool_name} completed successfully')
160
+ return result['result']
161
+
162
+ except aiohttp.ClientError as e:
163
+ logger.error(f'Network error calling MCP tool {tool_name}: {e}')
164
+ raise MCPError(f'Network error: {str(e)}')
165
+
166
+ async def create_task(
167
+ self,
168
+ title: str,
169
+ description: str = '',
170
+ codebase_id: str = 'global',
171
+ agent_type: str = 'build',
172
+ priority: int = 0,
173
+ ) -> Task:
174
+ """Create a new task.
175
+
176
+ Args:
177
+ title: The title of the task.
178
+ description: The description of the task.
179
+ codebase_id: The codebase ID to associate with the task.
180
+ agent_type: The type of agent to use.
181
+ priority: The priority of the task (0-10).
182
+
183
+ Returns:
184
+ The created Task object.
185
+ """
186
+ arguments = {
187
+ 'title': title,
188
+ 'description': description,
189
+ 'codebase_id': codebase_id,
190
+ 'agent_type': agent_type,
191
+ 'priority': priority,
192
+ }
193
+
194
+ result = await self.call_tool('create_task', arguments)
195
+
196
+ return Task(
197
+ id=result.get('id', ''),
198
+ title=result.get('title', title),
199
+ description=result.get('description', description),
200
+ status=result.get('status', 'pending'),
201
+ codebase_id=result.get('codebase_id', codebase_id),
202
+ agent_type=result.get('agent_type', agent_type),
203
+ priority=result.get('priority', priority),
204
+ created_at=result.get('created_at'),
205
+ result=result.get('result'),
206
+ error=result.get('error'),
207
+ )
208
+
209
+ async def list_tasks(
210
+ self, status: Optional[str] = None, codebase_id: Optional[str] = None
211
+ ) -> List[Task]:
212
+ """List tasks with optional filtering.
213
+
214
+ Args:
215
+ status: Filter tasks by status.
216
+ codebase_id: Filter tasks by codebase ID.
217
+
218
+ Returns:
219
+ A list of Task objects.
220
+ """
221
+ arguments: Dict[str, Any] = {}
222
+ if status is not None:
223
+ arguments['status'] = status
224
+ if codebase_id is not None:
225
+ arguments['codebase_id'] = codebase_id
226
+
227
+ result = await self.call_tool('list_tasks', arguments)
228
+
229
+ tasks = []
230
+ for task_data in result.get('tasks', []):
231
+ tasks.append(
232
+ Task(
233
+ id=task_data.get('id', ''),
234
+ title=task_data.get('title', ''),
235
+ description=task_data.get('description', ''),
236
+ status=task_data.get('status', 'pending'),
237
+ codebase_id=task_data.get('codebase_id', 'global'),
238
+ agent_type=task_data.get('agent_type', 'build'),
239
+ priority=task_data.get('priority', 0),
240
+ created_at=task_data.get('created_at'),
241
+ result=task_data.get('result'),
242
+ error=task_data.get('error'),
243
+ )
244
+ )
245
+
246
+ return tasks
247
+
248
+ async def get_task(self, task_id: str) -> Optional[Task]:
249
+ """Get a task by ID.
250
+
251
+ Args:
252
+ task_id: The ID of the task to retrieve.
253
+
254
+ Returns:
255
+ The Task object if found, None otherwise.
256
+ """
257
+ result = await self.call_tool('get_task', {'task_id': task_id})
258
+
259
+ if not result:
260
+ return None
261
+
262
+ return Task(
263
+ id=result.get('id', ''),
264
+ title=result.get('title', ''),
265
+ description=result.get('description', ''),
266
+ status=result.get('status', 'pending'),
267
+ codebase_id=result.get('codebase_id', 'global'),
268
+ agent_type=result.get('agent_type', 'build'),
269
+ priority=result.get('priority', 0),
270
+ created_at=result.get('created_at'),
271
+ result=result.get('result'),
272
+ error=result.get('error'),
273
+ )
274
+
275
+ async def cancel_task(self, task_id: str) -> bool:
276
+ """Cancel a task.
277
+
278
+ Args:
279
+ task_id: The ID of the task to cancel.
280
+
281
+ Returns:
282
+ True if the task was cancelled, False otherwise.
283
+ """
284
+ result = await self.call_tool('cancel_task', {'task_id': task_id})
285
+ return result.get('success', False)
286
+
287
+ async def get_session_messages(self, session_id: str) -> List[Message]:
288
+ """Get messages from a session.
289
+
290
+ Args:
291
+ session_id: The ID of the session.
292
+
293
+ Returns:
294
+ A list of Message objects.
295
+ """
296
+ result = await self.call_tool('get_session', {'session_id': session_id})
297
+
298
+ messages = []
299
+ for msg_data in result.get('messages', []):
300
+ messages.append(
301
+ Message(
302
+ role=msg_data.get('role', ''),
303
+ content=msg_data.get('content', ''),
304
+ timestamp=msg_data.get('timestamp'),
305
+ )
306
+ )
307
+
308
+ return messages
309
+
310
+ async def discover_agents(self) -> List[Agent]:
311
+ """Discover available agents.
312
+
313
+ Returns:
314
+ A list of Agent objects.
315
+ """
316
+ result = await self.call_tool('discover_agents', {})
317
+
318
+ agents = []
319
+ for agent_data in result.get('agents', []):
320
+ agents.append(
321
+ Agent(
322
+ name=agent_data.get('name', ''),
323
+ description=agent_data.get('description', ''),
324
+ url=agent_data.get('url', ''),
325
+ )
326
+ )
327
+
328
+ return agents
329
+
330
+ async def send_message(
331
+ self, agent_name: str, message: str
332
+ ) -> Dict[str, Any]:
333
+ """Send a message to an agent.
334
+
335
+ Args:
336
+ agent_name: The name of the agent to send the message to.
337
+ message: The message content.
338
+
339
+ Returns:
340
+ The response from the agent.
341
+ """
342
+ result = await self.call_tool(
343
+ 'send_message', {'agent_name': agent_name, 'message': message}
344
+ )
345
+ return result
@@ -0,0 +1,16 @@
1
+ import os
2
+ from pydantic import BaseSettings
3
+
4
+
5
+ class Settings(BaseSettings):
6
+ LIVEKIT_URL: str = 'wss://your-livekit-url.livekit.cloud'
7
+ CODETETHER_API_URL: str = 'http://localhost:8000'
8
+ FUNCTIONGEMMA_MODEL_PATH: str = 'google/functiongemma-2b'
9
+ GOOGLE_API_KEY: str = ''
10
+ LOG_LEVEL: str = 'info'
11
+
12
+ class Config:
13
+ env_prefix = 'CODETETHER_VOICE_AGENT_'
14
+
15
+
16
+ settings = Settings()