supervaizer 0.10.5__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 (76) hide show
  1. supervaizer/__init__.py +97 -0
  2. supervaizer/__version__.py +10 -0
  3. supervaizer/account.py +308 -0
  4. supervaizer/account_service.py +93 -0
  5. supervaizer/admin/routes.py +1293 -0
  6. supervaizer/admin/static/js/job-start-form.js +373 -0
  7. supervaizer/admin/templates/agent_detail.html +145 -0
  8. supervaizer/admin/templates/agents.html +249 -0
  9. supervaizer/admin/templates/agents_grid.html +82 -0
  10. supervaizer/admin/templates/base.html +233 -0
  11. supervaizer/admin/templates/case_detail.html +230 -0
  12. supervaizer/admin/templates/cases_list.html +182 -0
  13. supervaizer/admin/templates/cases_table.html +134 -0
  14. supervaizer/admin/templates/console.html +389 -0
  15. supervaizer/admin/templates/dashboard.html +153 -0
  16. supervaizer/admin/templates/job_detail.html +192 -0
  17. supervaizer/admin/templates/job_start_test.html +109 -0
  18. supervaizer/admin/templates/jobs_list.html +180 -0
  19. supervaizer/admin/templates/jobs_table.html +122 -0
  20. supervaizer/admin/templates/navigation.html +163 -0
  21. supervaizer/admin/templates/recent_activity.html +81 -0
  22. supervaizer/admin/templates/server.html +105 -0
  23. supervaizer/admin/templates/server_status_cards.html +121 -0
  24. supervaizer/admin/templates/supervaize_instructions.html +212 -0
  25. supervaizer/agent.py +956 -0
  26. supervaizer/case.py +432 -0
  27. supervaizer/cli.py +395 -0
  28. supervaizer/common.py +324 -0
  29. supervaizer/deploy/__init__.py +16 -0
  30. supervaizer/deploy/cli.py +305 -0
  31. supervaizer/deploy/commands/__init__.py +9 -0
  32. supervaizer/deploy/commands/clean.py +294 -0
  33. supervaizer/deploy/commands/down.py +119 -0
  34. supervaizer/deploy/commands/local.py +460 -0
  35. supervaizer/deploy/commands/plan.py +167 -0
  36. supervaizer/deploy/commands/status.py +169 -0
  37. supervaizer/deploy/commands/up.py +281 -0
  38. supervaizer/deploy/docker.py +377 -0
  39. supervaizer/deploy/driver_factory.py +42 -0
  40. supervaizer/deploy/drivers/__init__.py +39 -0
  41. supervaizer/deploy/drivers/aws_app_runner.py +607 -0
  42. supervaizer/deploy/drivers/base.py +196 -0
  43. supervaizer/deploy/drivers/cloud_run.py +570 -0
  44. supervaizer/deploy/drivers/do_app_platform.py +504 -0
  45. supervaizer/deploy/health.py +404 -0
  46. supervaizer/deploy/state.py +210 -0
  47. supervaizer/deploy/templates/Dockerfile.template +44 -0
  48. supervaizer/deploy/templates/debug_env.py +69 -0
  49. supervaizer/deploy/templates/docker-compose.yml.template +37 -0
  50. supervaizer/deploy/templates/dockerignore.template +66 -0
  51. supervaizer/deploy/templates/entrypoint.sh +20 -0
  52. supervaizer/deploy/utils.py +52 -0
  53. supervaizer/event.py +181 -0
  54. supervaizer/examples/controller_template.py +196 -0
  55. supervaizer/instructions.py +145 -0
  56. supervaizer/job.py +392 -0
  57. supervaizer/job_service.py +156 -0
  58. supervaizer/lifecycle.py +417 -0
  59. supervaizer/parameter.py +233 -0
  60. supervaizer/protocol/__init__.py +11 -0
  61. supervaizer/protocol/a2a/__init__.py +21 -0
  62. supervaizer/protocol/a2a/model.py +227 -0
  63. supervaizer/protocol/a2a/routes.py +99 -0
  64. supervaizer/py.typed +1 -0
  65. supervaizer/routes.py +917 -0
  66. supervaizer/server.py +553 -0
  67. supervaizer/server_utils.py +54 -0
  68. supervaizer/storage.py +462 -0
  69. supervaizer/telemetry.py +81 -0
  70. supervaizer/utils/__init__.py +16 -0
  71. supervaizer/utils/version_check.py +56 -0
  72. supervaizer-0.10.5.dist-info/METADATA +317 -0
  73. supervaizer-0.10.5.dist-info/RECORD +76 -0
  74. supervaizer-0.10.5.dist-info/WHEEL +4 -0
  75. supervaizer-0.10.5.dist-info/entry_points.txt +2 -0
  76. supervaizer-0.10.5.dist-info/licenses/LICENSE.md +346 -0
supervaizer/server.py ADDED
@@ -0,0 +1,553 @@
1
+ # Copyright (c) 2024-2025 Alain Prasquier - Supervaize.com. All rights reserved.
2
+ #
3
+ # This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ # If a copy of the MPL was not distributed with this file, you can obtain one at
5
+ # https://mozilla.org/MPL/2.0/.
6
+
7
+ import os
8
+ import secrets
9
+ import sys
10
+ import time
11
+ import uuid
12
+ from datetime import datetime
13
+ from typing import Any, ClassVar, Dict, List, Optional, TypeVar
14
+ from urllib.parse import urlunparse
15
+
16
+ from cryptography.hazmat.backends import default_backend
17
+ from cryptography.hazmat.primitives import serialization
18
+ from cryptography.hazmat.primitives.asymmetric import rsa
19
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
20
+ from fastapi import FastAPI, HTTPException, Request, Security, status
21
+ from fastapi.exceptions import RequestValidationError
22
+ from fastapi.responses import JSONResponse
23
+ from fastapi.security import APIKeyHeader
24
+ from pydantic import BaseModel, field_validator, Field
25
+ from rich import inspect
26
+
27
+ from supervaizer.__version__ import API_VERSION, VERSION
28
+ from supervaizer.account import Account
29
+ from supervaizer.admin.routes import create_admin_routes
30
+ from supervaizer.agent import Agent
31
+ from supervaizer.common import (
32
+ ApiResult,
33
+ ApiSuccess,
34
+ SvBaseModel,
35
+ decrypt_value,
36
+ encrypt_value,
37
+ log,
38
+ )
39
+ from supervaizer.instructions import display_instructions
40
+ from supervaizer.protocol.a2a import create_routes as create_a2a_routes
41
+ from supervaizer.routes import (
42
+ create_agents_routes,
43
+ create_default_routes,
44
+ create_utils_routes,
45
+ get_server,
46
+ )
47
+ from supervaizer.storage import StorageManager, load_running_entities_on_startup
48
+
49
+ insp = inspect
50
+
51
+ T = TypeVar("T")
52
+
53
+ # Additional imports for server persistence
54
+
55
+
56
+ class ServerInfo(BaseModel):
57
+ """Complete server information for storage."""
58
+
59
+ id: str = "server_instance" # Fixed ID for singleton
60
+ host: str
61
+ port: int
62
+ api_version: str
63
+ environment: str
64
+ agents: List[Dict[str, str]]
65
+ start_time: float
66
+ created_at: str
67
+ updated_at: str
68
+
69
+
70
+ def save_server_info_to_storage(server_instance: "Server") -> None:
71
+ """Save server information to storage."""
72
+ try:
73
+ storage = StorageManager()
74
+
75
+ # Get agent information
76
+ agents = []
77
+ if hasattr(server_instance, "agents") and server_instance.agents:
78
+ for agent in server_instance.agents:
79
+ agents.append(
80
+ {
81
+ "name": agent.name,
82
+ "description": agent.description,
83
+ "version": agent.version,
84
+ "api_path": agent.path,
85
+ "slug": agent.slug,
86
+ "instructions_path": agent.instructions_path,
87
+ }
88
+ )
89
+
90
+ # Create server info
91
+ server_info = ServerInfo(
92
+ host=getattr(server_instance, "host", "0.0.0.0"),
93
+ port=getattr(server_instance, "port", 8000),
94
+ api_version=API_VERSION,
95
+ environment=os.getenv("SUPERVAIZER_ENVIRONMENT", "development"),
96
+ agents=agents,
97
+ start_time=time.time(),
98
+ created_at=datetime.now().isoformat(),
99
+ updated_at=datetime.now().isoformat(),
100
+ )
101
+
102
+ # Save to storage
103
+ storage.save_object("ServerInfo", server_info.model_dump())
104
+
105
+ log.info(
106
+ f"[Server] Server info saved to storage: {server_info.host}:{server_info.port}"
107
+ )
108
+
109
+ except Exception as e:
110
+ log.error(f"[Server] Failed to save server info to storage: {e}")
111
+
112
+
113
+ def get_server_info_from_storage() -> Optional[ServerInfo]:
114
+ """Get server information from storage."""
115
+ storage = StorageManager()
116
+ server_data = storage.get_object_by_id("ServerInfo", "server_instance")
117
+
118
+ if server_data:
119
+ return ServerInfo.model_validate(server_data)
120
+ return None
121
+
122
+
123
+ class ServerAbstract(SvBaseModel):
124
+ """
125
+ API Server for the Supervaize Controller.
126
+
127
+ The server is a FastAPI application (see https://fastapi.tiangolo.com/ for details and advanced parameters)
128
+
129
+ This represents the main server instance that manages agents and provides
130
+ the API endpoints for the Supervaize Control API. It handles agent registration,
131
+ job execution, and communication with the Supervaize platform.
132
+
133
+ The server can be configured with various endpoints (A2A, ACP, admin interface)
134
+ and supports encryption/decryption of parameters using RSA keys.
135
+
136
+ Note that when the supervisor ccount is set, the A2A protocol is automatically activated to provide HEALTH CHECK endpoints.
137
+
138
+ public_url: full url (including scheme and port) to use for outbound connections and registration.
139
+ This is especially important in Docker environments where the binding
140
+ address (0.0.0.0) can't be used for outbound connections. Set to
141
+ 'host.docker.internal' for Docker or the appropriate service name
142
+ in container environments.
143
+ Examples:
144
+ - In Docker, set to 'http://host.docker.internal' to reach the host machine
145
+ - In Kubernetes, might be set to the service name or external DNS
146
+ If not provided, falls back to using the listening host.
147
+
148
+ """
149
+
150
+ supervaizer_VERSION: ClassVar[str] = VERSION
151
+ scheme: str = Field(description="URL scheme (http or https)")
152
+ host: str = Field(
153
+ description="Host to bind the server to (e.g., 0.0.0.0 for all interfaces)"
154
+ )
155
+ port: int = Field(description="Port to bind the server to")
156
+ environment: str = Field(description="Environment name (e.g., dev, staging, prod)")
157
+ mac_addr: str = Field(description="MAC address to use for server identification")
158
+ debug: bool = Field(description="Whether to enable debug mode")
159
+ agents: List[Agent] = Field(
160
+ description="List of agents to register with the server"
161
+ )
162
+ app: FastAPI = Field(description="FastAPI application instance")
163
+ reload: bool = Field(description="Whether to enable auto-reload")
164
+ supervisor_account: Optional[Account] = Field(
165
+ default=None,
166
+ description="Account of the supervisor - can be created at supervaize.com",
167
+ )
168
+ a2a_endpoints: bool = Field(
169
+ default=True, description="Whether to enable A2A endpoints"
170
+ )
171
+ private_key: RSAPrivateKey = Field(
172
+ description="RSA private key for secret parameters encryption - Used in server-to-agent communication - Not needed by user"
173
+ )
174
+ public_key: RSAPublicKey = Field(
175
+ description="RSA public key for secret parameters encryption - Used in agent-to-server communication - Not needed by user"
176
+ )
177
+ public_url: Optional[str] = Field(
178
+ default=None,
179
+ description="Public including scheme and port to use for inbound connections",
180
+ )
181
+ api_key: Optional[str] = Field(
182
+ default=None,
183
+ description="Force the API key to access the supervaizer endpoints - if not provided, a random key will be generated",
184
+ )
185
+ api_key_header: Optional[APIKeyHeader] = Field(
186
+ default=None, description="API key header for authentication"
187
+ )
188
+
189
+ model_config = {
190
+ "reference_group": "Core",
191
+ "arbitrary_types_allowed": True, # for FastAPI
192
+ "json_schema_extra": {
193
+ "examples": [
194
+ {
195
+ "agents": "[agent]",
196
+ "a2a_enabled": True,
197
+ "supervisor_account": None,
198
+ },
199
+ {
200
+ "scheme": "http",
201
+ "host": "0.0.0.0",
202
+ "port": 8000,
203
+ "environment": "dev",
204
+ "mac_addr": "00-11-22-33-44-55",
205
+ "debug": False,
206
+ "reload": False,
207
+ "a2a_endpoints": True,
208
+ },
209
+ ]
210
+ },
211
+ }
212
+
213
+ @field_validator("scheme")
214
+ def scheme_validator(cls, v: str) -> str:
215
+ if "://" in v:
216
+ raise ValueError(f"Scheme should not include '://': {v}")
217
+ return v
218
+
219
+ @field_validator("host")
220
+ def host_validator(cls, v: str) -> str:
221
+ if "://" in v:
222
+ raise ValueError(f"Host should not include '://': {v}")
223
+ return v
224
+
225
+ def get_agent_by_name(self, agent_name: str) -> Optional[Agent]:
226
+ for agent in self.agents:
227
+ if agent.name == agent_name:
228
+ return agent
229
+ return None
230
+
231
+
232
+ class Server(ServerAbstract):
233
+ def __init__(
234
+ self,
235
+ agents: List[Agent],
236
+ supervisor_account: Optional[Account] = None,
237
+ a2a_endpoints: bool = True,
238
+ admin_interface: bool = True,
239
+ scheme: str = "http",
240
+ environment: str = os.getenv("SUPERVAIZER_ENVIRONMENT", "dev"),
241
+ host: str = os.getenv("SUPERVAIZER_HOST", "0.0.0.0"),
242
+ port: int = int(os.getenv("SUPERVAIZER_PORT", 8000)),
243
+ debug: bool = False,
244
+ reload: bool = False,
245
+ mac_addr: str = "",
246
+ private_key: Optional[RSAPrivateKey] = None,
247
+ public_url: Optional[str] = os.getenv("SUPERVAIZER_PUBLIC_URL", None),
248
+ api_key: Optional[str] = os.getenv(
249
+ "SUPERVAIZER_API_KEY", secrets.token_urlsafe(32)
250
+ ),
251
+ **kwargs: Any,
252
+ ) -> None:
253
+ """Initialize the server with the given configuration.
254
+
255
+ Args:
256
+ agents: List of agents to register with the server
257
+ supervisor_account: Account of the supervisor
258
+ a2a_endpoints: Whether to enable A2A endpoints
259
+ admin_interface: Whether to enable admin interface
260
+ scheme: URL scheme (http or https)
261
+ environment: Environment name (e.g., dev, staging, prod)
262
+ host: Host to bind the server to (e.g., 0.0.0.0 for all interfaces)
263
+ port: Port to bind the server to
264
+ debug: Whether to enable debug mode
265
+ reload: Whether to enable auto-reload
266
+ mac_addr: MAC address to use for server identification
267
+ private_key: RSA private key for encryption
268
+
269
+ api_key: API key for securing endpoints
270
+
271
+ """
272
+ if not mac_addr:
273
+ node_id = uuid.getnode()
274
+ mac_addr = "-".join(
275
+ format(node_id, "012X")[i : i + 2] for i in range(0, 12, 2)
276
+ )
277
+
278
+ if private_key is None:
279
+ private_key = rsa.generate_private_key(
280
+ public_exponent=65537,
281
+ key_size=2048,
282
+ backend=default_backend(),
283
+ )
284
+
285
+ public_key = private_key.public_key()
286
+
287
+ # Create root app to handle version prefix
288
+ docs_url = "/docs" # Swagger UI
289
+ redoc_url = "/redoc" # ReDoc
290
+ openapi_url = "/openapi.json"
291
+
292
+ app = FastAPI(
293
+ debug=debug,
294
+ title="Supervaizer API",
295
+ description=(
296
+ f"API version: {API_VERSION} Controller version: {VERSION}\n\n"
297
+ "API for controlling and managing Supervaize agents. \n\nMore information at "
298
+ "[https://doc.supervaize.com](https://doc.supervaize.com)\n\n"
299
+ "## Authentication\n\n"
300
+ "Some endpoints require API key authentication. Protected endpoints expect "
301
+ "the API key in the X-API-Key header.\n\n"
302
+ f"[Swagger]({docs_url})\n"
303
+ f"[Redoc]({redoc_url})\n"
304
+ f"[OpenAPI]({openapi_url})\n"
305
+ ),
306
+ version=API_VERSION,
307
+ terms_of_service="https://supervaize.com/terms/",
308
+ contact={
309
+ "name": "Support Team",
310
+ "url": "https://supervaize.com/dev_contact/",
311
+ "email": "integration_support@supervaize.com",
312
+ },
313
+ license_info={
314
+ "name": "Mozilla Public License 2.0",
315
+ "url": "https://mozilla.org/MPL/2.0/",
316
+ },
317
+ docs_url=docs_url,
318
+ redoc_url=redoc_url,
319
+ openapi_url=openapi_url,
320
+ )
321
+
322
+ # Add exception handler for 422 validation errors
323
+ @app.exception_handler(RequestValidationError)
324
+ async def validation_exception_handler(
325
+ request: Request, exc: RequestValidationError
326
+ ) -> JSONResponse:
327
+ log.error(f"[422 Error] {exc.errors()}")
328
+ return JSONResponse(
329
+ status_code=422,
330
+ content={"detail": exc.errors(), "body": exc.body},
331
+ )
332
+
333
+ # Create API key header security
334
+ API_KEY_NAME = "X-API-Key"
335
+ api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
336
+
337
+ super().__init__(
338
+ scheme=scheme,
339
+ host=host,
340
+ port=port,
341
+ environment=environment,
342
+ mac_addr=mac_addr,
343
+ debug=debug,
344
+ agents=agents,
345
+ app=app,
346
+ reload=reload,
347
+ supervisor_account=supervisor_account,
348
+ a2a_endpoints=a2a_endpoints,
349
+ private_key=private_key,
350
+ public_key=public_key,
351
+ public_url=public_url,
352
+ api_key=api_key,
353
+ api_key_header=api_key_header,
354
+ **kwargs,
355
+ )
356
+
357
+ # Create routes
358
+ if self.supervisor_account:
359
+ log.info(
360
+ "[Server launch] 🚀 Deploy Supervaizer routes - also activates A2A routes"
361
+ )
362
+ self.app.include_router(create_default_routes(self))
363
+ self.app.include_router(create_utils_routes(self))
364
+ self.app.include_router(create_agents_routes(self))
365
+ self.a2a_endpoints = True # Needed by supervaize.
366
+ if self.a2a_endpoints:
367
+ log.info("[Server launch] 📢 Deploy A2A routes ")
368
+ self.app.include_router(create_a2a_routes(self))
369
+
370
+ # Deploy admin routes if API key is available
371
+ if self.api_key and admin_interface:
372
+ log.info(
373
+ f"[Server launch] 💼 Deploy Admin interface @ {self.public_url}/admin"
374
+ )
375
+ self.app.include_router(create_admin_routes(), prefix="/admin")
376
+
377
+ # Save server info to storage for admin interface
378
+ save_server_info_to_storage(self)
379
+
380
+ # Load running entities from storage into memory
381
+ try:
382
+ load_running_entities_on_startup()
383
+ except Exception as e:
384
+ log.error(f"[Server launch] Failed to load running entities: {e}")
385
+ raise
386
+
387
+ # Override the get_server dependency to return this instance
388
+ async def get_current_server() -> "Server":
389
+ return self
390
+
391
+ # Update the dependency
392
+ self.app.dependency_overrides[get_server] = get_current_server
393
+
394
+ if api_key:
395
+ log.info("[Server launch] API Key authentication enabled")
396
+ # Print the API key if it was generated
397
+ if os.getenv("SUPERVAIZER_API_KEY") is None:
398
+ log.warning(f"[Server launch] Using auto-generated API key: {api_key}")
399
+ else:
400
+ log.info("[Server launch] API Key authentication disabled")
401
+
402
+ if not self.public_url:
403
+ self.public_url = f"{self.scheme}://{self.host}:{self.port}"
404
+
405
+ async def verify_api_key(
406
+ self, api_key: str = Security(APIKeyHeader(name="X-API-Key"))
407
+ ) -> bool:
408
+ """Verify that the API key is valid.
409
+
410
+ Args:
411
+ api_key: The API key from the request header
412
+
413
+ Returns:
414
+ True if the API key is valid
415
+
416
+ Raises:
417
+ HTTPException: If the API key is invalid or not provided when required
418
+ """
419
+ if self.api_key is None:
420
+ # API key authentication is disabled
421
+ return True
422
+
423
+ if api_key != self.api_key:
424
+ raise HTTPException(
425
+ status_code=status.HTTP_403_FORBIDDEN,
426
+ detail="Invalid API key",
427
+ headers={"WWW-Authenticate": "APIKey"},
428
+ )
429
+
430
+ return True
431
+
432
+ @property
433
+ def url(self) -> str:
434
+ """Get the server's local URL."""
435
+ return urlunparse((self.scheme, f"{self.host}:{self.port}", "", "", "", ""))
436
+
437
+ @property
438
+ def uri(self) -> str:
439
+ """Get the server's URI."""
440
+ return f"server:{self.mac_addr}"
441
+
442
+ @property
443
+ def registration_info(self) -> Dict[str, Any]:
444
+ """Get registration info for the server."""
445
+ assert self.public_key is not None, "Public key not initialized"
446
+ return {
447
+ "url": self.public_url,
448
+ "uri": self.uri,
449
+ "api_version": API_VERSION,
450
+ "environment": self.environment,
451
+ "public_key": str(
452
+ self.public_key.public_bytes(
453
+ encoding=serialization.Encoding.PEM,
454
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
455
+ ).decode("utf-8")
456
+ ),
457
+ "api_key": self.api_key,
458
+ "docs": {
459
+ "swagger": f"{self.public_url}{self.app.docs_url}",
460
+ "redoc": f"{self.public_url}{self.app.redoc_url}",
461
+ "openapi": f"{self.public_url}{self.app.openapi_url}",
462
+ },
463
+ "agents": [agent.registration_info for agent in self.agents],
464
+ }
465
+
466
+ def launch(self, log_level: Optional[str] = "INFO") -> None:
467
+ if log_level:
468
+ log.remove()
469
+ log.add(
470
+ sys.stderr,
471
+ colorize=True,
472
+ format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>|<level> {level}</level> | <level>{message}</level>",
473
+ level=log_level,
474
+ )
475
+
476
+ # Add log handler for admin streaming if API key is enabled
477
+ if self.api_key:
478
+
479
+ def log_queue_handler(message: Any) -> None:
480
+ record = message.record
481
+ try:
482
+ # Import here to avoid circular imports and ensure module is loaded
483
+ import supervaizer.admin.routes as admin_routes
484
+
485
+ admin_routes.add_log_to_queue(
486
+ timestamp=record["time"].isoformat(),
487
+ level=record["level"].name,
488
+ message=record["message"],
489
+ )
490
+ except ImportError:
491
+ # Silently ignore import errors to avoid breaking logging
492
+ pass
493
+ except Exception:
494
+ # Silently ignore other errors to avoid breaking logging
495
+ pass
496
+
497
+ # Add the handler with a specific format to avoid recursion
498
+ log.add(log_queue_handler, level=log_level, format="{message}")
499
+
500
+ log_level = (
501
+ log_level.lower()
502
+ ) # needs to be lower case of uvicorn and uppercase of loguru
503
+
504
+ log.info(
505
+ f"[Server launch] Starting Supervaize Controller API v{VERSION} - Log : {log_level} "
506
+ )
507
+
508
+ # self.instructions()
509
+ if self.supervisor_account:
510
+ # Register the server with the supervisor account
511
+ server_registration_result: ApiResult = (
512
+ self.supervisor_account.register_server(server=self)
513
+ )
514
+ # log.debug(f"[Server launch] Server registration result: {server_registration_result}")
515
+ # inspect(server_registration_result)
516
+ assert isinstance(
517
+ server_registration_result, ApiSuccess
518
+ ) # If ApiError, exception should have been raised before
519
+ # Get the agent details from the server
520
+ for agent in self.agents:
521
+ updated_agent = agent.update_agent_from_server(self)
522
+ if updated_agent:
523
+ log.info(f"[Server launch] Updated agent {updated_agent.name}")
524
+
525
+ import uvicorn
526
+
527
+ uvicorn.run(
528
+ self.app,
529
+ host=self.host,
530
+ port=self.port,
531
+ reload=self.reload,
532
+ log_level=log_level,
533
+ )
534
+
535
+ def instructions(self) -> None:
536
+ server_url = f"http://{self.host}:{self.port}"
537
+ display_instructions(
538
+ server_url, f"Starting server on {server_url} \n Waiting for instructions.."
539
+ )
540
+
541
+ def decrypt(self, encrypted_parameters: str) -> str:
542
+ """Decrypt parameters using the server's private key."""
543
+ result = decrypt_value(encrypted_parameters, self.private_key)
544
+ if result is None:
545
+ raise ValueError("Failed to decrypt parameters")
546
+ return result
547
+
548
+ def encrypt(self, parameters: str) -> str:
549
+ """Encrypt parameters using the server's public key."""
550
+ result = encrypt_value(parameters, self.public_key)
551
+ if result is None:
552
+ raise ValueError("Failed to encrypt parameters")
553
+ return result
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2024-2025 Alain Prasquier - Supervaize.com. All rights reserved.
2
+ #
3
+ # This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ # If a copy of the MPL was not distributed with this file, you can obtain one at
5
+ # https://mozilla.org/MPL/2.0/.
6
+
7
+
8
+ from datetime import datetime
9
+ from enum import Enum
10
+
11
+ from fastapi.encoders import jsonable_encoder
12
+ from fastapi.responses import JSONResponse
13
+ from loguru import logger as log
14
+ from pydantic import BaseModel
15
+
16
+
17
+ class ErrorType(str, Enum):
18
+ """Enumeration of possible error types"""
19
+
20
+ JOB_NOT_FOUND = "job_not_found"
21
+ JOB_ALREADY_EXISTS = "job_already_exists"
22
+ AGENT_NOT_FOUND = "agent_not_found"
23
+ INVALID_REQUEST = "invalid_request"
24
+ INTERNAL_ERROR = "internal_error"
25
+ INVALID_PARAMETERS = "invalid_parameters"
26
+
27
+
28
+ class ErrorResponse(BaseModel):
29
+ """Standard error response model"""
30
+
31
+ error: str
32
+ error_type: ErrorType
33
+ detail: str | None = None
34
+ timestamp: datetime = datetime.now()
35
+ status_code: int
36
+
37
+
38
+ def create_error_response(
39
+ error_type: ErrorType, detail: str, status_code: int, traceback: str | None = None
40
+ ) -> JSONResponse:
41
+ """Helper function to create consistent error responses"""
42
+ error_response = ErrorResponse(
43
+ error=error_type.value.replace("_", " ").title(),
44
+ error_type=error_type,
45
+ detail=detail,
46
+ status_code=status_code,
47
+ )
48
+ log.error(detail)
49
+ if traceback:
50
+ log.error(traceback)
51
+ return JSONResponse(
52
+ status_code=status_code,
53
+ content=jsonable_encoder(error_response),
54
+ )