supervaizer 0.9.7__py3-none-any.whl → 0.10.0__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 +46 -7
- supervaizer/admin/static/js/job-start-form.js +373 -0
- supervaizer/admin/templates/agents.html +74 -0
- supervaizer/admin/templates/agents_grid.html +5 -3
- supervaizer/admin/templates/job_start_test.html +109 -0
- supervaizer/admin/templates/navigation.html +11 -1
- supervaizer/admin/templates/supervaize_instructions.html +212 -0
- supervaizer/agent.py +165 -25
- supervaizer/case.py +46 -14
- supervaizer/cli.py +248 -8
- 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 +370 -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 +41 -0
- supervaizer/examples/{controller-template.py → controller_template.py} +5 -4
- supervaizer/job.py +18 -5
- supervaizer/job_service.py +6 -5
- supervaizer/parameter.py +61 -1
- supervaizer/protocol/__init__.py +2 -2
- supervaizer/protocol/a2a/routes.py +1 -1
- supervaizer/routes.py +262 -12
- supervaizer/server.py +5 -11
- supervaizer/utils/__init__.py +16 -0
- supervaizer/utils/version_check.py +56 -0
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.dist-info}/METADATA +105 -34
- supervaizer-0.10.0.dist-info/RECORD +76 -0
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.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.7.dist-info/RECORD +0 -50
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.dist-info}/entry_points.txt +0 -0
- {supervaizer-0.9.7.dist-info → supervaizer-0.10.0.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,238 @@ 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
|
+
|
|
437
|
+
@router.post(
|
|
438
|
+
"/validate-agent-parameters",
|
|
439
|
+
summary=f"Validate agent parameters for agent: {agent.name}",
|
|
440
|
+
description="Validate agent configuration parameters (secrets, API keys, etc.) before starting a job",
|
|
441
|
+
response_model=Dict[str, Any],
|
|
442
|
+
responses={
|
|
443
|
+
http_status.HTTP_200_OK: {"model": Dict[str, Any]},
|
|
444
|
+
http_status.HTTP_400_BAD_REQUEST: {"model": Dict[str, Any]},
|
|
445
|
+
http_status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponse},
|
|
446
|
+
},
|
|
447
|
+
dependencies=[Security(server.verify_api_key)],
|
|
448
|
+
)
|
|
449
|
+
@handle_route_errors()
|
|
450
|
+
async def validate_agent_parameters(
|
|
451
|
+
body_params: Any = Body(...),
|
|
452
|
+
agent: Agent = Depends(get_agent),
|
|
453
|
+
) -> Dict[str, Any]:
|
|
454
|
+
"""Validate agent parameters for this agent"""
|
|
455
|
+
log.info(
|
|
456
|
+
f"📥 POST /validate-agent-parameters [Validate agent parameters] {agent.name}"
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
if not agent.parameters_setup:
|
|
460
|
+
result = {
|
|
461
|
+
"valid": True,
|
|
462
|
+
"message": "Agent has no parameter setup defined",
|
|
463
|
+
"errors": [],
|
|
464
|
+
"invalid_parameters": {},
|
|
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 = {}
|
|
471
|
+
|
|
472
|
+
encrypted_agent_parameters = body_params.get("encrypted_agent_parameters")
|
|
473
|
+
|
|
474
|
+
agent_parameters: Dict[str, Any] = {}
|
|
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
|
+
|
|
481
|
+
try:
|
|
482
|
+
import json
|
|
483
|
+
|
|
484
|
+
from supervaizer.common import decrypt_value
|
|
485
|
+
|
|
486
|
+
agent_parameters_str = decrypt_value(
|
|
487
|
+
encrypted_agent_parameters, server.private_key
|
|
488
|
+
)
|
|
489
|
+
agent_parameters = (
|
|
490
|
+
json.loads(agent_parameters_str) if agent_parameters_str else {}
|
|
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
|
+
|
|
513
|
+
except Exception as e:
|
|
514
|
+
log.error(f"❌ Decryption failed: {type(e).__name__}: {str(e)}")
|
|
515
|
+
result = {
|
|
516
|
+
"valid": False,
|
|
517
|
+
"message": f"Failed to decrypt agent parameters: {str(e)}",
|
|
518
|
+
"errors": [f"Decryption failed: {str(e)}"],
|
|
519
|
+
"invalid_parameters": {
|
|
520
|
+
"encrypted_agent_parameters": f"Decryption failed: {str(e)}"
|
|
521
|
+
},
|
|
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
|
+
)
|
|
530
|
+
|
|
531
|
+
# Validate agent parameters
|
|
532
|
+
validation_result = agent.parameters_setup.validate_parameters(agent_parameters)
|
|
533
|
+
|
|
534
|
+
result = {
|
|
535
|
+
"valid": validation_result["valid"],
|
|
536
|
+
"message": "Agent parameters validated successfully"
|
|
537
|
+
if validation_result["valid"]
|
|
538
|
+
else "Agent parameter validation failed",
|
|
539
|
+
"errors": validation_result["errors"],
|
|
540
|
+
"invalid_parameters": validation_result["invalid_parameters"],
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
log.info(f"📤 Agent {agent.name}: Validation result → {result}")
|
|
544
|
+
return result
|
|
545
|
+
|
|
546
|
+
@router.post(
|
|
547
|
+
"/validate-method-fields",
|
|
548
|
+
summary=f"Validate method fields for agent: {agent.name}",
|
|
549
|
+
description="Validate job input fields against the method's field definitions before starting a job",
|
|
550
|
+
response_model=Dict[str, Any],
|
|
551
|
+
responses={
|
|
552
|
+
http_status.HTTP_200_OK: {"model": Dict[str, Any]},
|
|
553
|
+
http_status.HTTP_400_BAD_REQUEST: {"model": Dict[str, Any]},
|
|
554
|
+
http_status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponse},
|
|
555
|
+
},
|
|
556
|
+
dependencies=[Security(server.verify_api_key)],
|
|
557
|
+
)
|
|
558
|
+
@handle_route_errors()
|
|
559
|
+
async def validate_method_fields(
|
|
560
|
+
body_params: Any = Body(...),
|
|
561
|
+
agent: Agent = Depends(get_agent),
|
|
562
|
+
) -> Dict[str, Any]:
|
|
563
|
+
"""Validate method fields for this agent"""
|
|
564
|
+
log.info(
|
|
565
|
+
f"📥 POST /validate-method-fields [Validate method fields] {agent.name}"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
if body_params is None:
|
|
569
|
+
body_params = {}
|
|
570
|
+
|
|
571
|
+
method_name = body_params.get("method_name", "job_start")
|
|
572
|
+
job_fields = body_params.get("job_fields", {})
|
|
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
|
+
|
|
579
|
+
# Get the method to validate against
|
|
580
|
+
if not agent.methods:
|
|
581
|
+
result = {
|
|
582
|
+
"valid": False,
|
|
583
|
+
"message": "Agent has no methods defined",
|
|
584
|
+
"errors": ["Agent has no methods defined"],
|
|
585
|
+
"invalid_fields": {},
|
|
586
|
+
}
|
|
587
|
+
log.info(f"📤 Agent {agent.name}: No methods defined → {result}")
|
|
588
|
+
return result
|
|
589
|
+
|
|
590
|
+
if method_name == "job_start":
|
|
591
|
+
method = agent.methods.job_start
|
|
592
|
+
elif agent.methods.custom and method_name in agent.methods.custom:
|
|
593
|
+
method = agent.methods.custom[method_name]
|
|
594
|
+
else:
|
|
595
|
+
result = {
|
|
596
|
+
"valid": False,
|
|
597
|
+
"message": f"Method '{method_name}' not found",
|
|
598
|
+
"errors": [f"Method '{method_name}' not found"],
|
|
599
|
+
"invalid_fields": {},
|
|
600
|
+
}
|
|
601
|
+
log.info(
|
|
602
|
+
f"📤 Agent {agent.name}: Method '{method_name}' not found → {result}"
|
|
603
|
+
)
|
|
604
|
+
return result
|
|
605
|
+
|
|
606
|
+
# Validate method fields
|
|
607
|
+
validation_result = method.validate_method_fields(job_fields)
|
|
608
|
+
|
|
609
|
+
result = {
|
|
610
|
+
"valid": validation_result["valid"],
|
|
611
|
+
"message": validation_result["message"],
|
|
612
|
+
"errors": validation_result["errors"],
|
|
613
|
+
"invalid_fields": validation_result["invalid_fields"],
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
log.info(
|
|
617
|
+
f"📤 Agent {agent.name}: Method '{method_name}' validation result → {result}"
|
|
618
|
+
)
|
|
619
|
+
return result
|
|
620
|
+
|
|
386
621
|
if not agent.methods:
|
|
387
622
|
raise ValueError(f"Agent {agent.name} has no methods defined")
|
|
388
623
|
|
|
@@ -400,6 +635,7 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
400
635
|
description=f"{agent.methods.job_start.description}",
|
|
401
636
|
responses={
|
|
402
637
|
http_status.HTTP_202_ACCEPTED: {"model": Job},
|
|
638
|
+
http_status.HTTP_400_BAD_REQUEST: {"model": Dict[str, Any]},
|
|
403
639
|
http_status.HTTP_409_CONFLICT: {"model": ErrorResponse},
|
|
404
640
|
http_status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponse},
|
|
405
641
|
},
|
|
@@ -415,13 +651,19 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
415
651
|
) -> Union[Job, JSONResponse]:
|
|
416
652
|
"""Start a new job for this agent"""
|
|
417
653
|
log.info(f"📥 POST /jobs [Start job] {agent.name} with params {body_params}")
|
|
418
|
-
|
|
419
|
-
|
|
654
|
+
|
|
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", {})
|
|
420
664
|
|
|
421
665
|
# Get job encrypted parameters if available
|
|
422
|
-
encrypted_agent_parameters =
|
|
423
|
-
body_params, "encrypted_agent_parameters", None
|
|
424
|
-
)
|
|
666
|
+
encrypted_agent_parameters = body_params.get("encrypted_agent_parameters")
|
|
425
667
|
|
|
426
668
|
# Delegate job creation and scheduling to the service
|
|
427
669
|
new_job = await service_job_start(
|
|
@@ -526,7 +768,9 @@ def create_agent_route(server: "Server", agent: Agent) -> APIRouter:
|
|
|
526
768
|
agent: Agent = Depends(get_agent),
|
|
527
769
|
) -> AgentResponse:
|
|
528
770
|
log.info(f"📥 POST /stop [Stop agent] {agent.name} with params {params}")
|
|
529
|
-
|
|
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})
|
|
530
774
|
res_info = result.registration_info if result else {}
|
|
531
775
|
return AgentResponse(
|
|
532
776
|
name=agent.name,
|
|
@@ -623,6 +867,7 @@ def create_agent_custom_routes(server: "Server", agent: Agent) -> APIRouter:
|
|
|
623
867
|
response_model=JobResponse,
|
|
624
868
|
responses={
|
|
625
869
|
http_status.HTTP_202_ACCEPTED: {"model": JobResponse},
|
|
870
|
+
http_status.HTTP_400_BAD_REQUEST: {"model": Dict[str, Any]},
|
|
626
871
|
http_status.HTTP_405_METHOD_NOT_ALLOWED: {"model": ErrorResponse},
|
|
627
872
|
},
|
|
628
873
|
dependencies=[Security(server.verify_api_key)],
|
|
@@ -637,6 +882,11 @@ def create_agent_custom_routes(server: "Server", agent: Agent) -> APIRouter:
|
|
|
637
882
|
log.info(
|
|
638
883
|
f"📥 POST /custom/{method_name} [custom job] {agent.name} with params {body_params}"
|
|
639
884
|
)
|
|
885
|
+
log.info(f"body_params: {body_params}")
|
|
886
|
+
|
|
887
|
+
if body_params is None:
|
|
888
|
+
raise ValueError("body_params cannot be None")
|
|
889
|
+
|
|
640
890
|
sv_context: JobContext = body_params.job_context
|
|
641
891
|
job_fields = body_params.job_fields.to_dict()
|
|
642
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
|