supervaizer 0.9.8__py3-none-any.whl → 0.10.1__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.
- supervaizer/__init__.py +11 -2
- supervaizer/__version__.py +1 -1
- supervaizer/account.py +4 -0
- supervaizer/account_service.py +7 -1
- supervaizer/admin/routes.py +24 -8
- supervaizer/admin/templates/agents.html +74 -0
- supervaizer/admin/templates/agents_grid.html +5 -3
- supervaizer/admin/templates/navigation.html +11 -1
- supervaizer/admin/templates/supervaize_instructions.html +212 -0
- supervaizer/agent.py +28 -6
- supervaizer/case.py +46 -14
- supervaizer/cli.py +247 -7
- supervaizer/common.py +45 -4
- supervaizer/deploy/__init__.py +16 -0
- supervaizer/deploy/cli.py +296 -0
- supervaizer/deploy/commands/__init__.py +9 -0
- supervaizer/deploy/commands/clean.py +294 -0
- supervaizer/deploy/commands/down.py +119 -0
- supervaizer/deploy/commands/local.py +460 -0
- supervaizer/deploy/commands/plan.py +167 -0
- supervaizer/deploy/commands/status.py +169 -0
- supervaizer/deploy/commands/up.py +281 -0
- supervaizer/deploy/docker.py +378 -0
- supervaizer/deploy/driver_factory.py +42 -0
- supervaizer/deploy/drivers/__init__.py +39 -0
- supervaizer/deploy/drivers/aws_app_runner.py +607 -0
- supervaizer/deploy/drivers/base.py +196 -0
- supervaizer/deploy/drivers/cloud_run.py +570 -0
- supervaizer/deploy/drivers/do_app_platform.py +504 -0
- supervaizer/deploy/health.py +404 -0
- supervaizer/deploy/state.py +210 -0
- supervaizer/deploy/templates/Dockerfile.template +44 -0
- supervaizer/deploy/templates/debug_env.py +69 -0
- supervaizer/deploy/templates/docker-compose.yml.template +37 -0
- supervaizer/deploy/templates/dockerignore.template +66 -0
- supervaizer/deploy/templates/entrypoint.sh +20 -0
- supervaizer/deploy/utils.py +52 -0
- supervaizer/examples/controller_template.py +1 -1
- supervaizer/job.py +18 -5
- supervaizer/job_service.py +6 -5
- supervaizer/parameter.py +13 -1
- supervaizer/protocol/__init__.py +2 -2
- supervaizer/protocol/a2a/routes.py +1 -1
- supervaizer/routes.py +141 -17
- supervaizer/server.py +5 -11
- supervaizer/utils/__init__.py +16 -0
- supervaizer/utils/version_check.py +56 -0
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/METADATA +105 -34
- supervaizer-0.10.1.dist-info/RECORD +76 -0
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/WHEEL +1 -1
- supervaizer/protocol/acp/__init__.py +0 -21
- supervaizer/protocol/acp/model.py +0 -198
- supervaizer/protocol/acp/routes.py +0 -74
- supervaizer-0.9.8.dist-info/RECORD +0 -52
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/entry_points.txt +0 -0
- {supervaizer-0.9.8.dist-info → supervaizer-0.10.1.dist-info}/licenses/LICENSE.md +0 -0
supervaizer/routes.py
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import traceback
|
|
8
8
|
from functools import wraps
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from typing import (
|
|
10
11
|
TYPE_CHECKING,
|
|
11
12
|
Any,
|
|
@@ -26,11 +27,12 @@ from fastapi import (
|
|
|
26
27
|
Depends,
|
|
27
28
|
HTTPException,
|
|
28
29
|
Query,
|
|
30
|
+
Request,
|
|
29
31
|
Security,
|
|
30
32
|
status as http_status,
|
|
31
33
|
)
|
|
32
|
-
from fastapi.responses import JSONResponse
|
|
33
|
-
from
|
|
34
|
+
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, Response
|
|
35
|
+
from fastapi.templating import Jinja2Templates
|
|
34
36
|
|
|
35
37
|
from supervaizer.agent import (
|
|
36
38
|
Agent,
|
|
@@ -51,8 +53,6 @@ if TYPE_CHECKING:
|
|
|
51
53
|
|
|
52
54
|
T = TypeVar("T")
|
|
53
55
|
|
|
54
|
-
insp = inspect
|
|
55
|
-
|
|
56
56
|
|
|
57
57
|
class CaseUpdateRequest(SvBaseModel):
|
|
58
58
|
"""Request model for updating a case with answer to a question."""
|
|
@@ -259,10 +259,13 @@ def create_default_routes(server: "Server") -> APIRouter:
|
|
|
259
259
|
)
|
|
260
260
|
|
|
261
261
|
# Update the case with the answer
|
|
262
|
-
case.update(update)
|
|
262
|
+
# case.update(update) - Redundant, receive_human_input calls update()
|
|
263
263
|
|
|
264
264
|
# Transition the case from AWAITING to IN_PROGRESS
|
|
265
|
-
case.receive_human_input()
|
|
265
|
+
case.receive_human_input(update)
|
|
266
|
+
|
|
267
|
+
# TODO CALL CUSTOM HOOKS HERE - AS DEFINED IN THE AGENT CONFIGURATION
|
|
268
|
+
# TODO REDEFINE AGENT TO ADD CUSTOM HOOKS HERE
|
|
266
269
|
|
|
267
270
|
log.info(
|
|
268
271
|
f"[Case update] Job {job_id}, Case {case_id} - Answer processed successfully"
|
|
@@ -383,6 +386,54 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
383
386
|
**agent.registration_info,
|
|
384
387
|
)
|
|
385
388
|
|
|
389
|
+
@router.get(
|
|
390
|
+
f"/{agent.instructions_path}",
|
|
391
|
+
summary=f"Get supervaize instructions page for agent {agent.name}",
|
|
392
|
+
description="HTML page displaying agent registration information and instructions",
|
|
393
|
+
response_class=HTMLResponse,
|
|
394
|
+
tags=tags,
|
|
395
|
+
)
|
|
396
|
+
@handle_route_errors()
|
|
397
|
+
async def supervaize_instructions(
|
|
398
|
+
request: Request, agent: Agent = Depends(get_agent)
|
|
399
|
+
) -> Response:
|
|
400
|
+
"""Serve the supervaize instructions HTML page for this agent."""
|
|
401
|
+
log.info(
|
|
402
|
+
f"📥 GET /{agent.instructions_path} [Supervaize Instructions] for agent{agent.name}"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
registration_info = agent.registration_info
|
|
406
|
+
|
|
407
|
+
# Convert instructions_path string to Path object
|
|
408
|
+
instructions_path = Path(agent.instructions_path)
|
|
409
|
+
|
|
410
|
+
# Check if file exists - if not, return empty HTML
|
|
411
|
+
if not instructions_path.exists() or not instructions_path.is_file():
|
|
412
|
+
return HTMLResponse(content="", status_code=200)
|
|
413
|
+
|
|
414
|
+
# Serve the file (check if it's a Jinja2 template or static HTML)
|
|
415
|
+
with open(instructions_path, "r", encoding="utf-8") as f:
|
|
416
|
+
content = f.read()
|
|
417
|
+
# Simple check: if it contains Jinja2 syntax, render as template
|
|
418
|
+
if "{{" in content or "{%" in content:
|
|
419
|
+
# Render as Jinja2 template
|
|
420
|
+
custom_templates = Jinja2Templates(
|
|
421
|
+
directory=str(instructions_path.parent)
|
|
422
|
+
)
|
|
423
|
+
return custom_templates.TemplateResponse(
|
|
424
|
+
instructions_path.name,
|
|
425
|
+
{
|
|
426
|
+
"request": request,
|
|
427
|
+
"registration_info": registration_info,
|
|
428
|
+
},
|
|
429
|
+
)
|
|
430
|
+
else:
|
|
431
|
+
# Serve as static HTML file
|
|
432
|
+
return FileResponse(
|
|
433
|
+
str(instructions_path),
|
|
434
|
+
media_type="text/html",
|
|
435
|
+
)
|
|
436
|
+
|
|
386
437
|
@router.post(
|
|
387
438
|
"/validate-agent-parameters",
|
|
388
439
|
summary=f"Validate agent parameters for agent: {agent.name}",
|
|
@@ -406,30 +457,62 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
406
457
|
)
|
|
407
458
|
|
|
408
459
|
if not agent.parameters_setup:
|
|
409
|
-
|
|
460
|
+
result = {
|
|
410
461
|
"valid": True,
|
|
411
462
|
"message": "Agent has no parameter setup defined",
|
|
412
463
|
"errors": [],
|
|
413
464
|
"invalid_parameters": {},
|
|
414
465
|
}
|
|
466
|
+
log.info(f"📤 Agent {agent.name}: No parameter setup defined → {result}")
|
|
467
|
+
return result
|
|
468
|
+
|
|
469
|
+
if body_params is None:
|
|
470
|
+
body_params = {}
|
|
415
471
|
|
|
416
472
|
encrypted_agent_parameters = body_params.get("encrypted_agent_parameters")
|
|
417
473
|
|
|
418
|
-
# Decrypt agent parameters if provided
|
|
419
474
|
agent_parameters: Dict[str, Any] = {}
|
|
420
475
|
if encrypted_agent_parameters:
|
|
476
|
+
# Basic debug trace
|
|
477
|
+
log.info(
|
|
478
|
+
f"📥 Received encrypted_agent_parameters, length: {len(encrypted_agent_parameters)}"
|
|
479
|
+
)
|
|
480
|
+
|
|
421
481
|
try:
|
|
422
|
-
from supervaizer.common import decrypt_value
|
|
423
482
|
import json
|
|
424
483
|
|
|
484
|
+
from supervaizer.common import decrypt_value
|
|
485
|
+
|
|
425
486
|
agent_parameters_str = decrypt_value(
|
|
426
487
|
encrypted_agent_parameters, server.private_key
|
|
427
488
|
)
|
|
428
489
|
agent_parameters = (
|
|
429
490
|
json.loads(agent_parameters_str) if agent_parameters_str else {}
|
|
430
491
|
)
|
|
492
|
+
|
|
493
|
+
# Debug: Log the parsed data type and structure
|
|
494
|
+
log.info(f"🔍 Parsed agent_parameters type: {type(agent_parameters)}")
|
|
495
|
+
if isinstance(agent_parameters, list):
|
|
496
|
+
log.info(
|
|
497
|
+
f"🔍 Converting list to dict with {len(agent_parameters)} items"
|
|
498
|
+
)
|
|
499
|
+
# Convert list to dict if needed (common when frontend sends array)
|
|
500
|
+
agent_parameters = {
|
|
501
|
+
f"param_{i}": param for i, param in enumerate(agent_parameters)
|
|
502
|
+
}
|
|
503
|
+
elif isinstance(agent_parameters, dict):
|
|
504
|
+
log.info(
|
|
505
|
+
f"🔍 Agent parameters keys: {list(agent_parameters.keys())}"
|
|
506
|
+
)
|
|
507
|
+
else:
|
|
508
|
+
log.warning(
|
|
509
|
+
f"🔍 Unexpected type: {type(agent_parameters)}, converting to empty dict"
|
|
510
|
+
)
|
|
511
|
+
agent_parameters = {}
|
|
512
|
+
|
|
431
513
|
except Exception as e:
|
|
432
|
-
|
|
514
|
+
log.error(f"❌ Decryption failed: {type(e).__name__}: {str(e)}")
|
|
515
|
+
result = {
|
|
433
516
|
"valid": False,
|
|
434
517
|
"message": f"Failed to decrypt agent parameters: {str(e)}",
|
|
435
518
|
"errors": [f"Decryption failed: {str(e)}"],
|
|
@@ -437,11 +520,18 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
437
520
|
"encrypted_agent_parameters": f"Decryption failed: {str(e)}"
|
|
438
521
|
},
|
|
439
522
|
}
|
|
523
|
+
log.info(f"📤 Agent {agent.name}: Decryption failed → {result}")
|
|
524
|
+
return result
|
|
525
|
+
|
|
526
|
+
# Log the incoming request details
|
|
527
|
+
log.info(
|
|
528
|
+
f"🔍 Agent {agent.name}: Incoming request - encrypted_params: {bool(encrypted_agent_parameters)}, parsed_params: {agent_parameters}"
|
|
529
|
+
)
|
|
440
530
|
|
|
441
531
|
# Validate agent parameters
|
|
442
532
|
validation_result = agent.parameters_setup.validate_parameters(agent_parameters)
|
|
443
533
|
|
|
444
|
-
|
|
534
|
+
result = {
|
|
445
535
|
"valid": validation_result["valid"],
|
|
446
536
|
"message": "Agent parameters validated successfully"
|
|
447
537
|
if validation_result["valid"]
|
|
@@ -450,6 +540,9 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
450
540
|
"invalid_parameters": validation_result["invalid_parameters"],
|
|
451
541
|
}
|
|
452
542
|
|
|
543
|
+
log.info(f"📤 Agent {agent.name}: Validation result → {result}")
|
|
544
|
+
return result
|
|
545
|
+
|
|
453
546
|
@router.post(
|
|
454
547
|
"/validate-method-fields",
|
|
455
548
|
summary=f"Validate method fields for agent: {agent.name}",
|
|
@@ -472,40 +565,59 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
472
565
|
f"📥 POST /validate-method-fields [Validate method fields] {agent.name}"
|
|
473
566
|
)
|
|
474
567
|
|
|
568
|
+
if body_params is None:
|
|
569
|
+
body_params = {}
|
|
570
|
+
|
|
475
571
|
method_name = body_params.get("method_name", "job_start")
|
|
476
572
|
job_fields = body_params.get("job_fields", {})
|
|
477
573
|
|
|
574
|
+
# Log the incoming request details
|
|
575
|
+
log.info(
|
|
576
|
+
f"🔍 Agent {agent.name}: Incoming request - method: {method_name}, fields: {job_fields}"
|
|
577
|
+
)
|
|
578
|
+
|
|
478
579
|
# Get the method to validate against
|
|
479
580
|
if not agent.methods:
|
|
480
|
-
|
|
581
|
+
result = {
|
|
481
582
|
"valid": False,
|
|
482
583
|
"message": "Agent has no methods defined",
|
|
483
584
|
"errors": ["Agent has no methods defined"],
|
|
484
585
|
"invalid_fields": {},
|
|
485
586
|
}
|
|
587
|
+
log.info(f"📤 Agent {agent.name}: No methods defined → {result}")
|
|
588
|
+
return result
|
|
486
589
|
|
|
487
590
|
if method_name == "job_start":
|
|
488
591
|
method = agent.methods.job_start
|
|
489
592
|
elif agent.methods.custom and method_name in agent.methods.custom:
|
|
490
593
|
method = agent.methods.custom[method_name]
|
|
491
594
|
else:
|
|
492
|
-
|
|
595
|
+
result = {
|
|
493
596
|
"valid": False,
|
|
494
597
|
"message": f"Method '{method_name}' not found",
|
|
495
598
|
"errors": [f"Method '{method_name}' not found"],
|
|
496
599
|
"invalid_fields": {},
|
|
497
600
|
}
|
|
601
|
+
log.info(
|
|
602
|
+
f"📤 Agent {agent.name}: Method '{method_name}' not found → {result}"
|
|
603
|
+
)
|
|
604
|
+
return result
|
|
498
605
|
|
|
499
606
|
# Validate method fields
|
|
500
607
|
validation_result = method.validate_method_fields(job_fields)
|
|
501
608
|
|
|
502
|
-
|
|
609
|
+
result = {
|
|
503
610
|
"valid": validation_result["valid"],
|
|
504
611
|
"message": validation_result["message"],
|
|
505
612
|
"errors": validation_result["errors"],
|
|
506
613
|
"invalid_fields": validation_result["invalid_fields"],
|
|
507
614
|
}
|
|
508
615
|
|
|
616
|
+
log.info(
|
|
617
|
+
f"📤 Agent {agent.name}: Method '{method_name}' validation result → {result}"
|
|
618
|
+
)
|
|
619
|
+
return result
|
|
620
|
+
|
|
509
621
|
if not agent.methods:
|
|
510
622
|
raise ValueError(f"Agent {agent.name} has no methods defined")
|
|
511
623
|
|
|
@@ -540,8 +652,15 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
540
652
|
"""Start a new job for this agent"""
|
|
541
653
|
log.info(f"📥 POST /jobs [Start job] {agent.name} with params {body_params}")
|
|
542
654
|
|
|
543
|
-
|
|
544
|
-
|
|
655
|
+
if body_params is None:
|
|
656
|
+
body_params = {}
|
|
657
|
+
|
|
658
|
+
job_context_data = body_params.get("job_context")
|
|
659
|
+
if job_context_data is None:
|
|
660
|
+
raise ValueError("job_context is required")
|
|
661
|
+
|
|
662
|
+
sv_context: JobContext = JobContext(**job_context_data)
|
|
663
|
+
job_fields = body_params.get("job_fields", {})
|
|
545
664
|
|
|
546
665
|
# Get job encrypted parameters if available
|
|
547
666
|
encrypted_agent_parameters = body_params.get("encrypted_agent_parameters")
|
|
@@ -649,7 +768,9 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
649
768
|
agent: Agent = Depends(get_agent),
|
|
650
769
|
) -> AgentResponse:
|
|
651
770
|
log.info(f"📥 POST /stop [Stop agent] {agent.name} with params {params}")
|
|
652
|
-
|
|
771
|
+
# Pass job_context as 'context' parameter to match agent method expectations
|
|
772
|
+
job_context = params.get("job_context", {})
|
|
773
|
+
result = agent.job_stop({"context": job_context})
|
|
653
774
|
res_info = result.registration_info if result else {}
|
|
654
775
|
return AgentResponse(
|
|
655
776
|
name=agent.name,
|
|
@@ -763,6 +884,9 @@ def create_agent_custom_routes(server: "Server", agent: Agent) -> APIRouter:
|
|
|
763
884
|
)
|
|
764
885
|
log.info(f"body_params: {body_params}")
|
|
765
886
|
|
|
887
|
+
if body_params is None:
|
|
888
|
+
raise ValueError("body_params cannot be None")
|
|
889
|
+
|
|
766
890
|
sv_context: JobContext = body_params.job_context
|
|
767
891
|
job_fields = body_params.job_fields.to_dict()
|
|
768
892
|
|
supervaizer/server.py
CHANGED
|
@@ -38,7 +38,6 @@ from supervaizer.common import (
|
|
|
38
38
|
)
|
|
39
39
|
from supervaizer.instructions import display_instructions
|
|
40
40
|
from supervaizer.protocol.a2a import create_routes as create_a2a_routes
|
|
41
|
-
from supervaizer.protocol.acp import create_routes as create_acp_routes
|
|
42
41
|
from supervaizer.routes import (
|
|
43
42
|
create_agents_routes,
|
|
44
43
|
create_default_routes,
|
|
@@ -82,6 +81,9 @@ def save_server_info_to_storage(server_instance: "Server") -> None:
|
|
|
82
81
|
"name": agent.name,
|
|
83
82
|
"description": agent.description,
|
|
84
83
|
"version": agent.version,
|
|
84
|
+
"api_path": agent.path,
|
|
85
|
+
"slug": agent.slug,
|
|
86
|
+
"instructions_path": agent.instructions_path,
|
|
85
87
|
}
|
|
86
88
|
)
|
|
87
89
|
|
|
@@ -166,9 +168,6 @@ class ServerAbstract(SvBaseModel):
|
|
|
166
168
|
a2a_endpoints: bool = Field(
|
|
167
169
|
default=True, description="Whether to enable A2A endpoints"
|
|
168
170
|
)
|
|
169
|
-
acp_endpoints: bool = Field(
|
|
170
|
-
default=True, description="Whether to enable ACP endpoints"
|
|
171
|
-
)
|
|
172
171
|
private_key: RSAPrivateKey = Field(
|
|
173
172
|
description="RSA private key for secret parameters encryption - Used in server-to-agent communication - Not needed by user"
|
|
174
173
|
)
|
|
@@ -206,7 +205,6 @@ class ServerAbstract(SvBaseModel):
|
|
|
206
205
|
"debug": False,
|
|
207
206
|
"reload": False,
|
|
208
207
|
"a2a_endpoints": True,
|
|
209
|
-
"acp_endpoints": True,
|
|
210
208
|
},
|
|
211
209
|
]
|
|
212
210
|
},
|
|
@@ -237,7 +235,6 @@ class Server(ServerAbstract):
|
|
|
237
235
|
agents: List[Agent],
|
|
238
236
|
supervisor_account: Optional[Account] = None,
|
|
239
237
|
a2a_endpoints: bool = True,
|
|
240
|
-
acp_endpoints: bool = True,
|
|
241
238
|
admin_interface: bool = True,
|
|
242
239
|
scheme: str = "http",
|
|
243
240
|
environment: str = os.getenv("SUPERVAIZER_ENVIRONMENT", "dev"),
|
|
@@ -259,7 +256,6 @@ class Server(ServerAbstract):
|
|
|
259
256
|
agents: List of agents to register with the server
|
|
260
257
|
supervisor_account: Account of the supervisor
|
|
261
258
|
a2a_endpoints: Whether to enable A2A endpoints
|
|
262
|
-
acp_endpoints: Whether to enable ACP endpoints
|
|
263
259
|
admin_interface: Whether to enable admin interface
|
|
264
260
|
scheme: URL scheme (http or https)
|
|
265
261
|
environment: Environment name (e.g., dev, staging, prod)
|
|
@@ -350,7 +346,6 @@ class Server(ServerAbstract):
|
|
|
350
346
|
reload=reload,
|
|
351
347
|
supervisor_account=supervisor_account,
|
|
352
348
|
a2a_endpoints=a2a_endpoints,
|
|
353
|
-
acp_endpoints=acp_endpoints,
|
|
354
349
|
private_key=private_key,
|
|
355
350
|
public_key=public_key,
|
|
356
351
|
public_url=public_url,
|
|
@@ -371,9 +366,6 @@ class Server(ServerAbstract):
|
|
|
371
366
|
if self.a2a_endpoints:
|
|
372
367
|
log.info("[Server launch] 📢 Deploy A2A routes ")
|
|
373
368
|
self.app.include_router(create_a2a_routes(self))
|
|
374
|
-
if self.acp_endpoints:
|
|
375
|
-
log.info("[Server launch] 📢 Deploy ACP routes")
|
|
376
|
-
self.app.include_router(create_acp_routes(self))
|
|
377
369
|
|
|
378
370
|
# Deploy admin routes if API key is available
|
|
379
371
|
if self.api_key and admin_interface:
|
|
@@ -519,6 +511,8 @@ class Server(ServerAbstract):
|
|
|
519
511
|
server_registration_result: ApiResult = (
|
|
520
512
|
self.supervisor_account.register_server(server=self)
|
|
521
513
|
)
|
|
514
|
+
# log.debug(f"[Server launch] Server registration result: {server_registration_result}")
|
|
515
|
+
# inspect(server_registration_result)
|
|
522
516
|
assert isinstance(
|
|
523
517
|
server_registration_result, ApiSuccess
|
|
524
518
|
) # If ApiError, exception should have been raised before
|
|
@@ -0,0 +1,16 @@
|
|
|
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 supervaizer.utils.version_check import (
|
|
9
|
+
check_is_latest_version,
|
|
10
|
+
get_latest_version,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"check_is_latest_version",
|
|
15
|
+
"get_latest_version",
|
|
16
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from packaging import version
|
|
12
|
+
except ImportError:
|
|
13
|
+
version = None
|
|
14
|
+
|
|
15
|
+
from supervaizer import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def get_latest_version() -> str | None:
|
|
19
|
+
"""
|
|
20
|
+
Retrieve the latest version number of supervaizer from PyPI.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
The latest version string (e.g., "0.9.8") if successful, None otherwise.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
27
|
+
response = await client.get("https://pypi.org/pypi/supervaizer/json")
|
|
28
|
+
response.raise_for_status()
|
|
29
|
+
data = response.json()
|
|
30
|
+
return data.get("info", {}).get("version")
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def check_is_latest_version() -> tuple[bool, str | None]:
|
|
36
|
+
"""
|
|
37
|
+
Check if the currently running supervaizer version is the latest available on PyPI.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A tuple of (is_latest: bool, latest_version: str | None).
|
|
41
|
+
- is_latest: True if current version is latest or if check failed
|
|
42
|
+
- latest_version: The latest version string if available, None otherwise
|
|
43
|
+
"""
|
|
44
|
+
current_version = __version__.VERSION
|
|
45
|
+
latest_version = await get_latest_version()
|
|
46
|
+
|
|
47
|
+
if latest_version is None:
|
|
48
|
+
return True, None
|
|
49
|
+
|
|
50
|
+
# Compare versions using packaging.version for proper semantic version comparison
|
|
51
|
+
if version is not None:
|
|
52
|
+
is_latest = version.parse(current_version) >= version.parse(latest_version)
|
|
53
|
+
else:
|
|
54
|
+
# Fallback to simple string comparison if packaging is not available
|
|
55
|
+
is_latest = current_version >= latest_version
|
|
56
|
+
return is_latest, latest_version
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supervaizer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Summary: Controller system for Supervaize
|
|
5
5
|
Project-URL: Homepage, https://supervaize.com
|
|
6
6
|
Project-URL: Repository, https://github.com/supervaize/supervaizer
|
|
@@ -16,52 +16,59 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Requires-Python: >=3.12
|
|
18
18
|
Requires-Dist: art>=6.5
|
|
19
|
-
Requires-Dist: cryptography>=
|
|
19
|
+
Requires-Dist: cryptography>=46.0.2
|
|
20
20
|
Requires-Dist: demjson3>=3.0.6
|
|
21
21
|
Requires-Dist: deprecated>=1.2.18
|
|
22
|
-
Requires-Dist: fastapi>=0.
|
|
22
|
+
Requires-Dist: fastapi>=0.118.0
|
|
23
23
|
Requires-Dist: httpx>=0.28.1
|
|
24
24
|
Requires-Dist: jinja2>=3.1.6
|
|
25
25
|
Requires-Dist: loguru>=0.7.3
|
|
26
|
-
Requires-Dist: orjson>=3.11.
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
26
|
+
Requires-Dist: orjson>=3.11.3
|
|
27
|
+
Requires-Dist: packaging>=24.0
|
|
28
|
+
Requires-Dist: psutil>=7.1.0
|
|
29
|
+
Requires-Dist: pydantic>=2.12.0
|
|
30
30
|
Requires-Dist: python-slugify>=8.0.4
|
|
31
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
31
32
|
Requires-Dist: rich>=14.1.0
|
|
32
33
|
Requires-Dist: shortuuid>=1.0.13
|
|
33
34
|
Requires-Dist: sse-starlette>=3.0.2
|
|
34
35
|
Requires-Dist: tinydb>=4.8.1
|
|
35
|
-
Requires-Dist: typer>=0.
|
|
36
|
-
Requires-Dist: uvicorn>=0.
|
|
36
|
+
Requires-Dist: typer>=0.19.2
|
|
37
|
+
Requires-Dist: uvicorn>=0.36.0
|
|
38
|
+
Provides-Extra: deploy
|
|
39
|
+
Requires-Dist: boto3>=1.34.0; extra == 'deploy'
|
|
40
|
+
Requires-Dist: docker>=7.0.0; extra == 'deploy'
|
|
41
|
+
Requires-Dist: google-cloud-artifact-registry>=1.8.0; extra == 'deploy'
|
|
42
|
+
Requires-Dist: google-cloud-run>=0.10.0; extra == 'deploy'
|
|
43
|
+
Requires-Dist: google-cloud-secret-manager>=2.18.0; extra == 'deploy'
|
|
37
44
|
Provides-Extra: dev
|
|
38
|
-
Requires-Dist: hatch>=1.14.
|
|
39
|
-
Requires-Dist: jsonschema>=4.25.
|
|
40
|
-
Requires-Dist: mypy>=1.
|
|
45
|
+
Requires-Dist: hatch>=1.14.2; extra == 'dev'
|
|
46
|
+
Requires-Dist: jsonschema>=4.25.1; extra == 'dev'
|
|
47
|
+
Requires-Dist: mypy>=1.18.2; extra == 'dev'
|
|
41
48
|
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
|
|
42
|
-
Requires-Dist: pytest-asyncio>=1.
|
|
43
|
-
Requires-Dist: pytest-cov>=
|
|
44
|
-
Requires-Dist: pytest-mock>=3.
|
|
45
|
-
Requires-Dist: pytest-sugar>=1.
|
|
46
|
-
Requires-Dist: pytest>=8.4.
|
|
49
|
+
Requires-Dist: pytest-asyncio>=1.2.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest-mock>=3.15.1; extra == 'dev'
|
|
52
|
+
Requires-Dist: pytest-sugar>=1.1.1; extra == 'dev'
|
|
53
|
+
Requires-Dist: pytest>=8.4.2; extra == 'dev'
|
|
47
54
|
Requires-Dist: respx>=0.22.0; extra == 'dev'
|
|
48
|
-
Requires-Dist: ruff>=0.
|
|
55
|
+
Requires-Dist: ruff>=0.13.3; extra == 'dev'
|
|
49
56
|
Requires-Dist: types-deprecated>=1.2.15.20250304; extra == 'dev'
|
|
50
57
|
Requires-Dist: types-python-slugify>=8.0.2.20240310; extra == 'dev'
|
|
51
58
|
Description-Content-Type: text/markdown
|
|
52
59
|
|
|
53
60
|
# SUPERVAIZER
|
|
54
61
|
|
|
55
|
-
[
|
|
62
|
+
[Operate AI Agents with confidence]
|
|
56
63
|
|
|
57
|
-
A Python toolkit for building, managing, and connecting AI agents with full [Agent-to-Agent (A2A)](https://
|
|
64
|
+
A Python toolkit for building, managing, and connecting AI agents with full [Agent-to-Agent (A2A)](https://a2a-protocol.org/) protocol support.
|
|
58
65
|
|
|
59
66
|
[](https://www.python.org/downloads/)
|
|
60
|
-
[](https://google.github.io/A2A/)
|
|
62
|
-
[](https://github.com/i-am-bee/ACP)
|
|
67
|
+
[](https://a2a-protocol.org/)
|
|
63
68
|
[](https://github.com/supervaize/supervaizer)
|
|
64
69
|
|
|
70
|
+
> **⚠️ Beta Disclaimer**: SUPERVAIZER is currently in beta mode. Not everything works as expected yet. Please report any issues you encounter.
|
|
71
|
+
|
|
65
72
|
- [SUPERVAIZER](#supervaizer)
|
|
66
73
|
- [Description](#description)
|
|
67
74
|
- [Quick Start](#quick-start)
|
|
@@ -70,35 +77,45 @@ A Python toolkit for building, managing, and connecting AI agents with full [Age
|
|
|
70
77
|
- [3. Scaffold the controller](#3-scaffold-the-controller)
|
|
71
78
|
- [(Optional) 4. Configure your Supervaize account \& environment](#optional-4-configure-your-supervaize-account--environment)
|
|
72
79
|
- [5. Start the server 🚀](#5-start-the-server-)
|
|
80
|
+
- [6. Optional parameters](#6-optional-parameters)
|
|
73
81
|
- [What's next?](#whats-next)
|
|
74
82
|
- [Features](#features)
|
|
75
83
|
- [Protocol Support](#protocol-support)
|
|
84
|
+
- [Cloud Deployment](#cloud-deployment)
|
|
85
|
+
- [Quick Start](#quick-start-1)
|
|
86
|
+
- [Deployment Commands](#deployment-commands)
|
|
87
|
+
- [Features](#features-1)
|
|
88
|
+
- [Documentation](#documentation)
|
|
76
89
|
- [Using the CLI](#using-the-cli)
|
|
77
90
|
- [API Documentation \& User Interfaces](#api-documentation--user-interfaces)
|
|
78
91
|
- [Admin Interface (`/admin`)](#admin-interface-admin)
|
|
79
|
-
- [Quick Start](#quick-start-
|
|
92
|
+
- [Quick Start](#quick-start-2)
|
|
80
93
|
- [Calculating costs](#calculating-costs)
|
|
81
|
-
- [Documentation](#documentation)
|
|
94
|
+
- [Documentation](#documentation-1)
|
|
82
95
|
- [Contributing](#contributing)
|
|
83
96
|
- [License](#license)
|
|
84
97
|
|
|
85
98
|
## Description
|
|
86
99
|
|
|
87
|
-
SUPERVAIZER is a toolkit built for the age of AI interoperability. At its core, it implements
|
|
100
|
+
SUPERVAIZER is a toolkit built for the age of AI interoperability. At its core, it implements the Agent-to-Agent (A2A) protocol, enabling seamless discovery and interaction between agents across different systems and platforms.
|
|
88
101
|
|
|
89
|
-
With comprehensive support for the A2A
|
|
102
|
+
With comprehensive support for the A2A protocol specification, SUPERVAIZER allows you to:
|
|
90
103
|
|
|
91
|
-
- Enhance the capabilities of your agents, making them automatically discoverable by other A2A
|
|
104
|
+
- Enhance the capabilities of your agents, making them automatically discoverable by other A2A compatible systems
|
|
92
105
|
- Expose standardized agent capabilities through agent cards
|
|
93
106
|
- Monitor agent health and status through dedicated endpoints
|
|
94
107
|
- Connect your agents to the growing ecosystem of A2A-compatible tools
|
|
95
108
|
|
|
96
109
|
Beyond A2A interoperability, SUPERVAIZER provides a robust API for agent registration, job control, event handling, telemetry, and more, making it a crucial component for building and managing AI agent systems.
|
|
97
110
|
|
|
111
|
+
SUPERVAIZER is the recommended controller to integrate AI Agents into the [supervaize](https://supervaize.com) plateform.
|
|
112
|
+
|
|
98
113
|
## Quick Start
|
|
99
114
|
|
|
100
115
|
Kickstart a **Python** agent with the **Supervaizer Controller** so it's discoverable and operable by Supervaize.
|
|
101
116
|
|
|
117
|
+
See full our full [documentation](https://doc.supervaize.com/docs/category/supervaizer-controller)
|
|
118
|
+
|
|
102
119
|
### What we'll do
|
|
103
120
|
|
|
104
121
|
1. **Install Supervaizer** in that project
|
|
@@ -159,28 +176,82 @@ Once the server is running, you'll have:
|
|
|
159
176
|
- **A2A discovery**: `/.well-known/agents.json`
|
|
160
177
|
- **ACP discovery**: `/agents`
|
|
161
178
|
|
|
179
|
+
### 6. Optional parameters
|
|
180
|
+
|
|
181
|
+
Configure retry behavior for HTTP requests to the Supervaize API:
|
|
182
|
+
|
|
183
|
+
- **`SUPERVAIZE_HTTP_MAX_RETRIES`**: Number of retry attempts for failed HTTP requests (default: `2`). The client will automatically retry requests that fail with status codes 429, 500, 502, 503, or 504.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
export SUPERVAIZE_MAX_HTTP_RETRIES=3 # Will attempt up to 4 times total (1 original + 3 retries)
|
|
187
|
+
```
|
|
188
|
+
|
|
162
189
|
### What's next?
|
|
163
190
|
|
|
164
191
|
- Add more **custom methods** (`chat`, `custom`) to extend control
|
|
165
|
-
- Turn on **A2A
|
|
192
|
+
- Turn on **A2A** discovery for interoperability
|
|
166
193
|
- Hook your controller into Supervaize to **monitor, audit, and operate** the agent
|
|
167
194
|
|
|
168
|
-
For detailed instructions on customizing your controller, see the [Controller Setup Guide](https://doc.supervaize.com/docs/supervaizer-controller/controller-setup
|
|
195
|
+
For detailed instructions on customizing your controller, see the [Controller Setup Guide](https://doc.supervaize.com/docs/supervaizer-controller/controller-setup)
|
|
169
196
|
|
|
170
197
|
## Features
|
|
171
198
|
|
|
172
199
|
- **Agent Management**: Register, update, and control agents
|
|
173
200
|
- **Job Control**: Create, track, and manage jobs
|
|
174
201
|
- **Event Handling**: Process and respond to system events
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
- **ACP Protocol **: Integration with IBM/BeeAI's Agent Communication Protocol for standardized agent discovery and interaction
|
|
202
|
+
- **🚀 Cloud Deployment**: Automated deployment to GCP Cloud Run, AWS App Runner, and DigitalOcean App Platform
|
|
203
|
+
- **A2A Protocol Support**: Full integration with the Agent-to-Agent protocol for standardized agent discovery and interaction
|
|
178
204
|
- **Server Communication**: Interact with SUPERVAIZE servers (see [supervaize.com](https://www.supervaize.com) for more info)
|
|
179
205
|
- **Web Admin Interface**: Easy to use web-based admin dashboard for managing jobs, cases, and system monitoring
|
|
180
206
|
|
|
181
207
|
## Protocol Support
|
|
182
208
|
|
|
183
|
-
SUPERVAIZER provides comprehensive support for
|
|
209
|
+
SUPERVAIZER provides comprehensive support for the A2A agent communication protocol. See [Protocol Documentation](docs/PROTOCOLS.md) for complete details.
|
|
210
|
+
|
|
211
|
+
## Cloud Deployment
|
|
212
|
+
|
|
213
|
+
SUPERVAIZER includes a powerful deployment CLI that automates the entire process of deploying your agents to production cloud platforms.
|
|
214
|
+
|
|
215
|
+
### Quick Start
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Install with deployment dependencies
|
|
219
|
+
pip install supervaizer[deploy]
|
|
220
|
+
|
|
221
|
+
# Test locally with Docker
|
|
222
|
+
supervaizer deploy local --generate-api-key --generate-rsa
|
|
223
|
+
|
|
224
|
+
# Deploy to Google Cloud Run
|
|
225
|
+
supervaizer deploy up --platform cloud-run --region us-central1
|
|
226
|
+
|
|
227
|
+
# Deploy to AWS App Runner
|
|
228
|
+
supervaizer deploy up --platform aws-app-runner --region us-east-1
|
|
229
|
+
|
|
230
|
+
# Deploy to DigitalOcean App Platform
|
|
231
|
+
supervaizer deploy up --platform do-app-platform --region nyc
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Deployment Commands
|
|
235
|
+
|
|
236
|
+
- **`supervaizer deploy plan`** - Preview deployment actions before applying
|
|
237
|
+
- **`supervaizer deploy up`** - Deploy to cloud platform with automated build, push, and verification
|
|
238
|
+
- **`supervaizer deploy down`** - Tear down deployment and clean up resources
|
|
239
|
+
- **`supervaizer deploy status`** - Check deployment status and health
|
|
240
|
+
- **`supervaizer deploy local`** - Local Docker testing with docker-compose
|
|
241
|
+
- **`supervaizer deploy clean`** - Clean up deployment artifacts and state
|
|
242
|
+
|
|
243
|
+
### Features
|
|
244
|
+
|
|
245
|
+
- ✅ **Automated Docker Workflow**: Build → Push → Deploy → Verify
|
|
246
|
+
- ✅ **Secret Management**: Secure handling of API keys and RSA keys
|
|
247
|
+
- ✅ **Health Verification**: Automatic health checks at `/.well-known/health`
|
|
248
|
+
- ✅ **Idempotent Deployments**: Safe create/update operations with rollback on failure
|
|
249
|
+
- ✅ **Local Testing**: Full Docker Compose environment for pre-deployment testing
|
|
250
|
+
|
|
251
|
+
### Documentation
|
|
252
|
+
|
|
253
|
+
- [RFC-001: Cloud Deployment CLI](docs/rfc/001-cloud-deployment-cli.md) - Complete specification
|
|
254
|
+
- [Local Testing Guide](docs/LOCAL_TESTING.md) - Docker testing documentation
|
|
184
255
|
|
|
185
256
|
## Using the CLI
|
|
186
257
|
|