supervaizer 0.10.5__py3-none-any.whl → 0.10.15__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 CHANGED
@@ -13,6 +13,8 @@ from supervaizer.agent import (
13
13
  AgentMethod,
14
14
  AgentMethodParams,
15
15
  AgentMethods,
16
+ AgentMethodField,
17
+ FieldTypeEnum,
16
18
  )
17
19
  from supervaizer.case import (
18
20
  Case,
@@ -50,6 +52,7 @@ __all__ = [
50
52
  "Agent",
51
53
  "AgentCustomMethodParams",
52
54
  "AgentMethod",
55
+ "AgentMethodField",
53
56
  "AgentMethodParams",
54
57
  "AgentMethods",
55
58
  "AgentRegisterEvent",
@@ -57,11 +60,11 @@ __all__ = [
57
60
  "ApiResult",
58
61
  "ApiSuccess",
59
62
  "Case",
60
- "CaseNodeUpdate",
61
- "CaseNodeType",
62
- "Cases",
63
63
  "CaseNode",
64
64
  "CaseNodes",
65
+ "CaseNodeType",
66
+ "CaseNodeUpdate",
67
+ "Cases",
65
68
  "CaseStartEvent",
66
69
  "CaseUpdateEvent",
67
70
  "create_error_response",
@@ -75,6 +78,7 @@ __all__ = [
75
78
  "ErrorType",
76
79
  "Event",
77
80
  "EventType",
81
+ "FieldTypeEnum",
78
82
  "Job",
79
83
  "JobContext",
80
84
  "JobFinishedEvent",
@@ -5,6 +5,6 @@
5
5
  # https://mozilla.org/MPL/2.0/.
6
6
 
7
7
 
8
- VERSION = "0.10.5"
8
+ VERSION = "0.10.15"
9
9
  API_VERSION = "v1"
10
10
  TELEMETRY_VERSION = "v1"
supervaizer/account.py CHANGED
@@ -8,7 +8,7 @@
8
8
  from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union
9
9
 
10
10
  import httpx
11
- from pydantic import Field
11
+ from pydantic import Field, field_validator
12
12
 
13
13
  from supervaizer.__version__ import VERSION
14
14
  from supervaizer.common import ApiError, ApiResult, ApiSuccess, SvBaseModel
@@ -55,6 +55,14 @@ class AccountAbstract(SvBaseModel):
55
55
  description="The URL of the Supervaize SaaS API provided by Supervaize.com"
56
56
  )
57
57
 
58
+ @field_validator("workspace_id", "api_key", "api_url", mode="before")
59
+ @classmethod
60
+ def strip_whitespace(cls, v: Any) -> Any:
61
+ """Strip leading/trailing whitespace (e.g. newlines from env vars)."""
62
+ if isinstance(v, str):
63
+ return v.strip()
64
+ return v
65
+
58
66
  model_config = {
59
67
  "reference_group": "Core",
60
68
  "json_schema_extra": {
@@ -62,7 +70,7 @@ class AccountAbstract(SvBaseModel):
62
70
  {
63
71
  "workspace_id": "ws_1234567890abcdef",
64
72
  "api_key": "sk_1234567890abcdef",
65
- "api_url": "https://api.supervaize.com",
73
+ "api_url": "https://app.supervaize.com",
66
74
  }
67
75
  ]
68
76
  },
@@ -121,7 +129,7 @@ class Account(AccountAbstract):
121
129
  """URL for the Supervaize Control API.
122
130
  Tested in tests/test_account.py
123
131
  """
124
- return f"{self.api_url_w_v1}/ctrl-events/"
132
+ return f"{self.api_url_w_v1}/ctrl-events/".strip()
125
133
 
126
134
  def get_url(self, pattern_name: str, **kwargs: Any) -> str:
127
135
  """Generate a URL using the predefined patterns.
@@ -191,7 +199,13 @@ class Account(AccountAbstract):
191
199
  from supervaizer.event import ServerRegisterEvent
192
200
 
193
201
  event = ServerRegisterEvent(server=server, account=self)
194
- return self.send_event(sender=server, event=event)
202
+ result = self.send_event(sender=server, event=event)
203
+ if isinstance(result, ApiSuccess):
204
+ log.success(result.message)
205
+ # TODO: Update server with the server ID from the response. store this ID in env variable.
206
+ else:
207
+ log.error(result.message)
208
+ return result
195
209
 
196
210
  def _create_api_result(
197
211
  self,
@@ -59,14 +59,17 @@ def send_event(
59
59
 
60
60
  headers = account.api_headers
61
61
  payload = event.payload
62
+ url_event = (
63
+ account.url_event.strip()
64
+ ) # defensive: env vars often have trailing newline
62
65
 
63
66
  # Generate curl equivalent for debugging
64
67
 
65
68
  curl_headers = " ".join([f'-H "{k}: {v}"' for k, v in headers.items()])
66
- curl_cmd = f"curl -X 'GET' '{account.url_event}' {curl_headers}"
69
+ curl_cmd = f"curl -X 'GET' '{url_event}' {curl_headers}"
67
70
 
68
71
  try:
69
- response = _httpx_client.post(account.url_event, headers=headers, json=payload)
72
+ response = _httpx_client.post(url_event, headers=headers, json=payload)
70
73
 
71
74
  # Log response details before raising for status
72
75
 
@@ -76,6 +79,13 @@ def send_event(
76
79
  )
77
80
 
78
81
  log.success(result.log_message)
82
+ except (httpx.ConnectError, httpx.ConnectTimeout) as e:
83
+ log.error(
84
+ f"Supervaize controller is not available at {url_event}. "
85
+ "Connection refused or timed out. Is the controller server running?"
86
+ )
87
+ log.error(f"❌ Error sending event {event.type.name}: {e!s}")
88
+ raise e
79
89
  except httpx.HTTPError as e:
80
90
  # Enhanced error logging
81
91
  log.error("[Send event] HTTP Error occurred")
@@ -83,7 +93,7 @@ def send_event(
83
93
 
84
94
  error_result = ApiError(
85
95
  message=f"Error sending event {event.type.name}",
86
- url=account.url_event,
96
+ url=url_event,
87
97
  payload=event.payload,
88
98
  exception=e,
89
99
  )
@@ -20,8 +20,8 @@ from pathlib import Path
20
20
  from typing import Any, AsyncGenerator, Dict, List, Optional
21
21
 
22
22
  import psutil
23
- from fastapi import APIRouter, HTTPException, Query, Request, Security
24
- from fastapi.responses import HTMLResponse, Response
23
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request, Security
24
+ from fastapi.responses import HTMLResponse, JSONResponse, Response
25
25
  from fastapi.security import APIKeyHeader
26
26
  from fastapi.templating import Jinja2Templates
27
27
  from pydantic import BaseModel
@@ -267,6 +267,7 @@ def create_admin_routes() -> APIRouter:
267
267
  "db_name": "TinyDB",
268
268
  "data_storage_path": str(storage.db_path.absolute()),
269
269
  "api_key": os.getenv("SUPERVAIZER_API_KEY"),
270
+ "server_id": os.getenv("SUPERVAIZER_SERVER_ID"),
270
271
  },
271
272
  )
272
273
  except Exception as e:
@@ -413,6 +414,59 @@ def create_admin_routes() -> APIRouter:
413
414
  log.error(f"Get server status API error: {e}")
414
415
  raise HTTPException(status_code=500, detail=str(e))
415
416
 
417
+ @router.post("/api/server/register")
418
+ async def register_server_with_supervisor(
419
+ request: Request,
420
+ _: bool = Depends(verify_admin_access),
421
+ ) -> JSONResponse:
422
+ """Trigger SERVER_REGISTER to the supervaizer supervisor."""
423
+ try:
424
+ from supervaizer.common import ApiSuccess
425
+ from supervaizer.routes import get_server
426
+
427
+ get_current = request.app.dependency_overrides.get(get_server)
428
+ if get_current is None:
429
+ raise HTTPException(
430
+ status_code=503,
431
+ detail="Server instance not available (admin not running with live server)",
432
+ )
433
+ try:
434
+ server = await get_current()
435
+ except (NotImplementedError, TypeError):
436
+ raise HTTPException(
437
+ status_code=503,
438
+ detail="Server instance not available (admin not running with live server)",
439
+ )
440
+ if not getattr(server, "supervisor_account", None):
441
+ raise HTTPException(
442
+ status_code=503,
443
+ detail="No supervisor account configured",
444
+ )
445
+ result = server.supervisor_account.register_server(server=server)
446
+ if isinstance(result, ApiSuccess):
447
+ return JSONResponse(
448
+ status_code=200,
449
+ content={
450
+ "success": True,
451
+ "message": result.message,
452
+ "detail": result.detail,
453
+ },
454
+ )
455
+ # ApiError
456
+ return JSONResponse(
457
+ status_code=502,
458
+ content={
459
+ "success": False,
460
+ "message": result.message,
461
+ "detail": getattr(result, "detail", None),
462
+ },
463
+ )
464
+ except HTTPException:
465
+ raise
466
+ except Exception as e:
467
+ log.error(f"Server register API error: {e}")
468
+ raise HTTPException(status_code=500, detail=str(e))
469
+
416
470
  @router.get("/api/agents")
417
471
  async def get_agents_api(
418
472
  request: Request,
@@ -872,28 +926,24 @@ def create_admin_routes() -> APIRouter:
872
926
  # Combine and sort by created_at
873
927
  activities = []
874
928
  for job in recent_jobs:
875
- activities.append(
876
- {
877
- "type": "job",
878
- "id": job.get("id"),
879
- "name": job.get("name"),
880
- "status": job.get("status"),
881
- "created_at": job.get("created_at"),
882
- "agent_name": job.get("agent_name"),
883
- }
884
- )
929
+ activities.append({
930
+ "type": "job",
931
+ "id": job.get("id"),
932
+ "name": job.get("name"),
933
+ "status": job.get("status"),
934
+ "created_at": job.get("created_at"),
935
+ "agent_name": job.get("agent_name"),
936
+ })
885
937
 
886
938
  for case in recent_cases:
887
- activities.append(
888
- {
889
- "type": "case",
890
- "id": case.get("id"),
891
- "name": case.get("name"),
892
- "status": case.get("status"),
893
- "created_at": case.get("created_at"),
894
- "job_id": case.get("job_id"),
895
- }
896
- )
939
+ activities.append({
940
+ "type": "case",
941
+ "id": case.get("id"),
942
+ "name": case.get("name"),
943
+ "status": case.get("status"),
944
+ "created_at": case.get("created_at"),
945
+ "job_id": case.get("job_id"),
946
+ })
897
947
 
898
948
  # Sort by created_at descending
899
949
  activities.sort(key=lambda x: str(x.get("created_at", "")), reverse=True)
@@ -1150,23 +1200,23 @@ def get_dashboard_stats(storage: StorageManager) -> AdminStats:
1150
1200
 
1151
1201
  # Calculate job stats
1152
1202
  job_total = len(all_jobs)
1153
- job_running = len(
1154
- [j for j in all_jobs if j.get("status") in ["in_progress", "awaiting"]]
1155
- )
1203
+ job_running = len([
1204
+ j for j in all_jobs if j.get("status") in ["in_progress", "awaiting"]
1205
+ ])
1156
1206
  job_completed = len([j for j in all_jobs if j.get("status") == "completed"])
1157
- job_failed = len(
1158
- [j for j in all_jobs if j.get("status") in ["failed", "cancelled"]]
1159
- )
1207
+ job_failed = len([
1208
+ j for j in all_jobs if j.get("status") in ["failed", "cancelled"]
1209
+ ])
1160
1210
 
1161
1211
  # Calculate case stats
1162
1212
  case_total = len(all_cases)
1163
- case_running = len(
1164
- [c for c in all_cases if c.get("status") in ["in_progress", "awaiting"]]
1165
- )
1213
+ case_running = len([
1214
+ c for c in all_cases if c.get("status") in ["in_progress", "awaiting"]
1215
+ ])
1166
1216
  case_completed = len([c for c in all_cases if c.get("status") == "completed"])
1167
- case_failed = len(
1168
- [c for c in all_cases if c.get("status") in ["failed", "cancelled"]]
1169
- )
1217
+ case_failed = len([
1218
+ c for c in all_cases if c.get("status") in ["failed", "cancelled"]
1219
+ ])
1170
1220
 
1171
1221
  # TinyDB collections count (tables)
1172
1222
  collections_count = len(storage._db.tables())
Binary file
@@ -0,0 +1,63 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Supervaizer API</title>
7
+ <!-- Tailwind CSS -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ </head>
10
+ <body class="bg-gray-50 min-h-screen">
11
+ <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
12
+ <div class="px-4 py-6 sm:px-0">
13
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
14
+ Supervaizer API
15
+ </h1>
16
+ <p class="mt-2 text-sm text-gray-500">
17
+ Controller version {{ version }} · API version {{ api_version }}
18
+ </p>
19
+ <p class="mt-1 text-xs text-gray-400 font-mono">Server ID: {{ server_id }}</p>
20
+
21
+ <div class="mt-8">
22
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Links</h2>
23
+ <ul class="space-y-2">
24
+ <li>
25
+ <a href="{{ base }}/docs" class="text-blue-600 hover:text-blue-800 font-medium">
26
+ Swagger UI
27
+ </a>
28
+ </li>
29
+ <li>
30
+ <a href="{{ base }}/redoc" class="text-blue-600 hover:text-blue-800 font-medium">
31
+ ReDoc
32
+ </a>
33
+ </li>
34
+ <li>
35
+ <a href="{{ base }}/openapi.json" class="text-blue-600 hover:text-blue-800 font-medium">
36
+ OpenAPI
37
+ </a>
38
+ </li>
39
+ <li><strong>BASE:</strong> {{ base }}
40
+ <br>
41
+ <strong>PUBLIC_URL:</strong> {{ public_url }}
42
+ <br>
43
+ <strong>FULL_URL:</strong> {{ full_url }}
44
+ <br>
45
+ </li>
46
+
47
+ {% if show_admin %}
48
+ <li>
49
+ <a href="{{ base }}/admin" class="text-blue-600 hover:text-blue-800 font-medium">
50
+ Admin
51
+ </a>
52
+ </li>
53
+ {% endif %}
54
+ </ul>
55
+ </div>
56
+ </div>
57
+ <hr>
58
+ <div class="mt-8">
59
+ TO REPLACE THIS FILE, create an index.html at the root of the project and it will be served as the home page.
60
+ </div>
61
+ </main>
62
+ </body>
63
+ </html>
@@ -12,7 +12,15 @@
12
12
  </h2>
13
13
  <p class="mt-1 text-sm text-gray-500">Monitor server health and configuration</p>
14
14
  </div>
15
- <div class="mt-4 flex md:mt-0">
15
+ <div class="mt-4 flex md:mt-0 gap-2">
16
+ <button
17
+ id="register-supervisor-btn"
18
+ type="button"
19
+ class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
20
+ >
21
+ <span id="register-indicator" class="htmx-indicator -ml-1 mr-2 h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
22
+ Register with supervisor
23
+ </button>
16
24
  <button
17
25
  hx-get="/admin/api/server/status"
18
26
  hx-target="#server-status-container"
@@ -26,6 +34,7 @@
26
34
  Refresh
27
35
  </button>
28
36
  </div>
37
+ <div id="register-result" class="mt-2 text-sm hidden" role="alert"></div>
29
38
  </div>
30
39
 
31
40
  <!-- Server Status Cards -->
@@ -91,15 +100,52 @@
91
100
  </div>
92
101
 
93
102
  <script>
94
- // Auto-refresh server status every 30 seconds
95
103
  document.addEventListener('DOMContentLoaded', function() {
96
- // Set up auto-refresh interval
104
+ // Auto-refresh server status every 30 seconds
97
105
  setInterval(function() {
98
106
  const refreshButton = document.querySelector('[hx-get="/admin/api/server/status"]');
99
107
  if (refreshButton) {
100
108
  htmx.trigger(refreshButton, 'click');
101
109
  }
102
- }, 30000); // Refresh every 30 seconds
110
+ }, 30000);
111
+
112
+ // Register with supervisor button
113
+ const registerBtn = document.getElementById('register-supervisor-btn');
114
+ const registerIndicator = document.getElementById('register-indicator');
115
+ const registerResult = document.getElementById('register-result');
116
+ if (registerBtn) {
117
+ registerBtn.addEventListener('click', async function() {
118
+ const params = new URLSearchParams(window.location.search);
119
+ const key = params.get('key');
120
+ const url = key ? '/admin/api/server/register?key=' + encodeURIComponent(key) : '/admin/api/server/register';
121
+ registerResult.classList.add('hidden');
122
+ registerIndicator.classList.remove('htmx-indicator');
123
+ registerBtn.disabled = true;
124
+ try {
125
+ const res = await fetch(url, { method: 'POST' });
126
+ const data = await res.json().catch(function() { return {}; });
127
+ registerResult.classList.remove('hidden');
128
+ if (res.ok && data.success) {
129
+ registerResult.className = 'mt-2 text-sm text-green-700';
130
+ registerResult.textContent = data.message || 'Registered successfully.';
131
+ } else {
132
+ registerResult.className = 'mt-2 text-sm text-red-700';
133
+ if (res.status === 403) {
134
+ registerResult.textContent = 'Authentication required. Open this page with ?key=...';
135
+ } else {
136
+ registerResult.textContent = (data.detail && typeof data.detail === 'object' && data.detail.message) ? data.detail.message : (data.message || 'Registration failed.');
137
+ }
138
+ }
139
+ } catch (e) {
140
+ registerResult.classList.remove('hidden');
141
+ registerResult.className = 'mt-2 text-sm text-red-700';
142
+ registerResult.textContent = 'Request failed: ' + (e.message || String(e));
143
+ } finally {
144
+ registerIndicator.classList.add('htmx-indicator');
145
+ registerBtn.disabled = false;
146
+ }
147
+ });
148
+ }
103
149
  });
104
150
  </script>
105
151
  {% endblock %}
supervaizer/agent.py CHANGED
@@ -78,7 +78,7 @@ class AgentMethodField(BaseModel):
78
78
  type: Any = Field(
79
79
  description="Python type of the field for pydantic validation - note , ChoiceField and MultipleChoiceField are a list[str]"
80
80
  )
81
- field_type: FieldTypeEnum = Field(
81
+ field_type: FieldTypeEnum | str = Field(
82
82
  default=FieldTypeEnum.CHAR, description="Field type for persistence"
83
83
  )
84
84
  description: str | None = Field(
@@ -185,6 +185,7 @@ class AgentMethodAbstract(BaseModel):
185
185
  default=None,
186
186
  description="A list of field specifications for generating forms/UI, following the django.forms.fields definition",
187
187
  )
188
+
188
189
  description: str | None = Field(
189
190
  default=None, description="Optional description of what the method does"
190
191
  )
supervaizer/deploy/cli.py CHANGED
@@ -10,6 +10,7 @@ Deployment CLI Commands
10
10
  This module contains the main CLI commands for the deploy subcommand.
11
11
  """
12
12
 
13
+ import importlib.util
13
14
  import typer
14
15
  from pathlib import Path
15
16
  from rich.console import Console
@@ -168,9 +169,7 @@ def plan(
168
169
  ) -> None:
169
170
  """Plan deployment changes without applying them."""
170
171
  # Check if deploy extras are installed (e.g., docker, cloud SDKs)
171
- try:
172
- import docker
173
- except ImportError:
172
+ if importlib.util.find_spec("docker") is None:
174
173
  console.print(
175
174
  "[bold red]Error:[/] 'deploy' extra requirements are not installed. "
176
175
  "Install them with: [bold]pip install supervaizer[deploy][/]"
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Supervaizer API</title>
7
+ <!-- Tailwind CSS -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ </head>
10
+ <body class="bg-gray-50 min-h-screen">
11
+ <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
12
+ <div class="px-4 py-6 sm:px-0">
13
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
14
+ Supervaizer API
15
+ </h1>
16
+ <p class="mt-2 text-sm text-gray-500">
17
+ Controller version {{ version }} · API version {{ api_version }}
18
+ </p>
19
+
20
+ <div class="mt-8">
21
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Links</h2>
22
+ <ul class="space-y-2">
23
+ <li>
24
+ <a href="/docs" class="text-blue-600 hover:text-blue-800 font-medium">
25
+ Swagger UI
26
+ </a>
27
+ </li>
28
+ <li>
29
+ <a href="/redoc" class="text-blue-600 hover:text-blue-800 font-medium">
30
+ ReDoc
31
+ </a>
32
+ </li>
33
+ <li>
34
+ <a href="/openapi.json" class="text-blue-600 hover:text-blue-800 font-medium">
35
+ OpenAPI
36
+ </a>
37
+ </li>
38
+ {% if show_admin %}
39
+ <li>
40
+ <a href="/admin" class="text-blue-600 hover:text-blue-800 font-medium">
41
+ Admin
42
+ </a>
43
+ </li>
44
+ {% endif %}
45
+ <li>
46
+ <a href="/console" class="text-blue-600 hover:text-blue-800 font-medium">
47
+ Console
48
+ </a>
49
+ </li>
50
+ <li> BASE: {{ base }}
51
+ </li>
52
+ </ul>
53
+ </div>
54
+ </div>
55
+ </main>
56
+ </body>
57
+ </html>
@@ -34,28 +34,26 @@ DEV_PUBLIC_URL = "https://myagent-dev.loca.lt"
34
34
  PROD_PUBLIC_URL = "https://myagent.cloud-hosting.net:8001"
35
35
 
36
36
  # Define the parameters and secrets expected by the agent
37
- agent_parameters: ParametersSetup | None = ParametersSetup.from_list(
38
- [
39
- Parameter(
40
- name="OPEN_API_KEY",
41
- description="OpenAPI Key",
42
- is_environment=True,
43
- is_secret=True,
44
- ),
45
- Parameter(
46
- name="SERPER_API",
47
- description="Server API key updated",
48
- is_environment=True,
49
- is_secret=True,
50
- ),
51
- Parameter(
52
- name="COMPETITOR_SUMMARY_URL",
53
- description="Competitor Summary URL",
54
- is_environment=True,
55
- is_secret=False,
56
- ),
57
- ]
58
- )
37
+ agent_parameters: ParametersSetup | None = ParametersSetup.from_list([
38
+ Parameter(
39
+ name="OPEN_API_KEY",
40
+ description="OpenAPI Key",
41
+ is_environment=True,
42
+ is_secret=True,
43
+ ),
44
+ Parameter(
45
+ name="SERPER_API",
46
+ description="Server API key updated",
47
+ is_environment=True,
48
+ is_secret=True,
49
+ ),
50
+ Parameter(
51
+ name="COMPETITOR_SUMMARY_URL",
52
+ description="Competitor Summary URL",
53
+ is_environment=True,
54
+ is_secret=False,
55
+ ),
56
+ ])
59
57
 
60
58
  # Define the method used to start a job
61
59
  job_start_method: AgentMethod = AgentMethod(
@@ -180,7 +178,7 @@ agent: Agent = Agent(
180
178
  account: Account = Account(
181
179
  workspace_id=os.getenv("SUPERVAIZE_WORKSPACE_ID") or "dummy_workspace_id",
182
180
  api_key=os.getenv("SUPERVAIZE_API_KEY") or "dummy_api_key",
183
- api_url=os.getenv("SUPERVAIZE_API_URL") or "https://api.supervaize.com",
181
+ api_url=os.getenv("SUPERVAIZE_API_URL") or "https://app.supervaize.com",
184
182
  )
185
183
 
186
184
  # Define the supervaizer server capabilities
supervaizer/server.py CHANGED
@@ -10,6 +10,7 @@ import sys
10
10
  import time
11
11
  import uuid
12
12
  from datetime import datetime
13
+ from pathlib import Path
13
14
  from typing import Any, ClassVar, Dict, List, Optional, TypeVar
14
15
  from urllib.parse import urlunparse
15
16
 
@@ -19,8 +20,9 @@ from cryptography.hazmat.primitives.asymmetric import rsa
19
20
  from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
20
21
  from fastapi import FastAPI, HTTPException, Request, Security, status
21
22
  from fastapi.exceptions import RequestValidationError
22
- from fastapi.responses import JSONResponse
23
+ from fastapi.responses import HTMLResponse, JSONResponse
23
24
  from fastapi.security import APIKeyHeader
25
+ from fastapi.templating import Jinja2Templates
24
26
  from pydantic import BaseModel, field_validator, Field
25
27
  from rich import inspect
26
28
 
@@ -53,6 +55,16 @@ T = TypeVar("T")
53
55
  # Additional imports for server persistence
54
56
 
55
57
 
58
+ def _get_or_create_server_id() -> str:
59
+ """Use SUPERVAIZER_SERVER_ID from env if set; else create uuid and set env."""
60
+ existing = os.getenv("SUPERVAIZER_SERVER_ID")
61
+ if existing:
62
+ return existing
63
+ new_id = str(uuid.uuid4())
64
+ os.environ["SUPERVAIZER_SERVER_ID"] = new_id
65
+ return new_id
66
+
67
+
56
68
  class ServerInfo(BaseModel):
57
69
  """Complete server information for storage."""
58
70
 
@@ -76,21 +88,20 @@ def save_server_info_to_storage(server_instance: "Server") -> None:
76
88
  agents = []
77
89
  if hasattr(server_instance, "agents") and server_instance.agents:
78
90
  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
- )
91
+ agents.append({
92
+ "name": agent.name,
93
+ "description": agent.description,
94
+ "version": agent.version,
95
+ "api_path": agent.path,
96
+ "slug": agent.slug,
97
+ "instructions_path": agent.instructions_path,
98
+ })
89
99
 
90
100
  # Create server info
91
101
  server_info = ServerInfo(
92
- host=getattr(server_instance, "host", "0.0.0.0"),
93
- port=getattr(server_instance, "port", 8000),
102
+ id=getattr(server_instance, "server_id", "N/A"),
103
+ host=getattr(server_instance, "host", "N/A"),
104
+ port=getattr(server_instance, "port", "N/A"),
94
105
  api_version=API_VERSION,
95
106
  environment=os.getenv("SUPERVAIZER_ENVIRONMENT", "development"),
96
107
  agents=agents,
@@ -148,6 +159,10 @@ class ServerAbstract(SvBaseModel):
148
159
  """
149
160
 
150
161
  supervaizer_VERSION: ClassVar[str] = VERSION
162
+ server_id: str = Field(
163
+ default_factory=_get_or_create_server_id,
164
+ description="Unique server id (SUPERVAIZER_SERVER_ID env or persisted uuid)",
165
+ )
151
166
  scheme: str = Field(description="URL scheme (http or https)")
152
167
  host: str = Field(
153
168
  description="Host to bind the server to (e.g., 0.0.0.0 for all interfaces)"
@@ -354,6 +369,8 @@ class Server(ServerAbstract):
354
369
  **kwargs,
355
370
  )
356
371
 
372
+ log.info(f"[Server launch] Server ID: {self.server_id}")
373
+
357
374
  # Create routes
358
375
  if self.supervisor_account:
359
376
  log.info(
@@ -377,6 +394,26 @@ class Server(ServerAbstract):
377
394
  # Save server info to storage for admin interface
378
395
  save_server_info_to_storage(self)
379
396
 
397
+ # Home page (template in admin/templates)
398
+ _home_templates = Jinja2Templates(
399
+ directory=str(Path(__file__).parent / "admin" / "templates")
400
+ )
401
+
402
+ @self.app.get("/", response_class=HTMLResponse)
403
+ async def home_page(request: Request) -> HTMLResponse:
404
+ base = self.public_url or f"{self.scheme}://{self.host}:{self.port}"
405
+ return _home_templates.TemplateResponse(
406
+ "index.html",
407
+ {
408
+ "request": request,
409
+ "base": base,
410
+ "version": VERSION,
411
+ "api_version": API_VERSION,
412
+ "show_admin": bool(self.api_key and admin_interface),
413
+ "server_id": self.server_id,
414
+ },
415
+ )
416
+
380
417
  # Load running entities from storage into memory
381
418
  try:
382
419
  load_running_entities_on_startup()
@@ -444,6 +481,7 @@ class Server(ServerAbstract):
444
481
  """Get registration info for the server."""
445
482
  assert self.public_key is not None, "Public key not initialized"
446
483
  return {
484
+ "server_id": self.server_id,
447
485
  "url": self.public_url,
448
486
  "uri": self.uri,
449
487
  "api_version": API_VERSION,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervaizer
3
- Version: 0.10.5
3
+ Version: 0.10.15
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
@@ -1,8 +1,8 @@
1
- supervaizer/__init__.py,sha256=eBbGjduMBH-FDjcGlSqeR4Kf4uo60Cf1DrRur8VGkJo,2341
2
- supervaizer/__version__.py,sha256=qlIH8dSecrW9K7OJHX-wf9QuxYRMhfsImO-0ebKqOpM,348
3
- supervaizer/account.py,sha256=-K1pd590Lp2TncZ2jn442kBLxnm482G3WnQavVFxZ1s,11102
4
- supervaizer/account_service.py,sha256=z4lw8qp8XvCrU6Ndf4VHRnQwY_071410ts5_7E8YDAs,3046
5
- supervaizer/agent.py,sha256=Clenvdr_v-lV7_v6YCcevp8dj5JUNvpTWrBZBYM82lg,36509
1
+ supervaizer/__init__.py,sha256=UNFcgJT-2708tLRFXohWr3320LVAzh3_WZZhrU9j1Iw,2427
2
+ supervaizer/__version__.py,sha256=Oe_Vfg1WOw027ZNCTKD35vUMGykGwCa00Szf8CAsyS8,349
3
+ supervaizer/account.py,sha256=POChw9c2ZjBNvesz6JmvBzYmMD40M5oxtz5IupuMqsw,11683
4
+ supervaizer/account_service.py,sha256=ZgZ0fhsbSVA076c-35ZZoYJBrQZsAwfFS7namVeoD3s,3459
5
+ supervaizer/agent.py,sha256=EtmqBH9fsO4k8YcHnjj55_4uOmb5URZwLlIIJReWg7A,36516
6
6
  supervaizer/case.py,sha256=dOgRujyf4MFcZ-937zxJbqLIPduKg6ZspHuhnWiq13Q,14385
7
7
  supervaizer/cli.py,sha256=3KH4gZXTiJ9GtY375kGm5HaKCGV2V8WEbcWdF_sWWEc,13936
8
8
  supervaizer/common.py,sha256=6ygNS1YxliP-e6OEVX7vDuC8YAaT2rYhjqQ_8txnWlg,10066
@@ -13,11 +13,12 @@ supervaizer/job_service.py,sha256=22Qe7Z5_u3R28tcNH_21YMIYciWFtJaM7I-MXtIhBMU,46
13
13
  supervaizer/lifecycle.py,sha256=5CunJN7MsM5blyNiFMJMLFDUBmTmmAsPE24QC-gSbYA,13958
14
14
  supervaizer/parameter.py,sha256=sYDuGof_w6mlix0oxjB6odV0sO0QSBL1KwFZa3Y2cOA,8157
15
15
  supervaizer/routes.py,sha256=o3u7pGGLE0MQzWtQNdd5xk1M0G9Y_BR_JiLVmCFtdC4,34319
16
- supervaizer/server.py,sha256=cXwB2XfIBQJ6zFaUde4OTCPgHpb_tpsUy9cRkkXqA-Q,20805
16
+ supervaizer/server.py,sha256=JLavzhd8PVTDtqfupaHGnshnY5GCi5rxTC4ThGcml9M,22270
17
17
  supervaizer/server_utils.py,sha256=FMglpADQBJynkR2v-pfwANu6obsaPvR9j0BQc5Otpac,1590
18
18
  supervaizer/storage.py,sha256=WLX8ggwt1AGF07DrfD1K6PyP2-N45c_Ep4CL0iPLVbI,15332
19
19
  supervaizer/telemetry.py,sha256=XSYw8ZwoY8W6C7mhwHU67t7trJzWd7CBkkSNdsDT_HA,2596
20
- supervaizer/admin/routes.py,sha256=r9hucWtNiTGQHgv9DsRkVpwnLbyLFaqrbse1orl-oTA,45627
20
+ supervaizer/admin/routes.py,sha256=utXv4Agn9Cz9V6IG8XCKSJnRPK4qi7Ge7Ti9ZUlSB4w,47694
21
+ supervaizer/admin/static/favicon.ico,sha256=wFyXw96AplZoEcW45dJBeC1pHTcPSH07HGWbtrc49mI,5558
21
22
  supervaizer/admin/static/js/job-start-form.js,sha256=s--AGVYzgc9mE20POYeM0BNm0Wy41aBZVv99tc0cSPU,11938
22
23
  supervaizer/admin/templates/agent_detail.html,sha256=DFOGfjuQNC39FOLYUW_jD0A01WpBY1umatGCslyJ0_c,7581
23
24
  supervaizer/admin/templates/agents.html,sha256=orB_z1iMFE2MKhFm9XejwZjmzMC1PVT69oB-F9YZPHQ,12678
@@ -28,17 +29,18 @@ supervaizer/admin/templates/cases_list.html,sha256=UV4SfULzxNiOpG8aNddablpwf6hVN
28
29
  supervaizer/admin/templates/cases_table.html,sha256=VyL5mEF003FTNHym1UYBD8JkvhA9wR328ciTKNKxdb8,6619
29
30
  supervaizer/admin/templates/console.html,sha256=tLGGf8vHjGK77Il6SYlgparoU_xz3nbvNpGVQRkVNCc,13966
30
31
  supervaizer/admin/templates/dashboard.html,sha256=3Pu5bR78QUcPNp5MyXkyibfp-wxYdJyyf77v3O_f_hU,7873
32
+ supervaizer/admin/templates/index.html,sha256=3MFAQ53gptvUgBBfzQoCLOFLAd7JYOV5Ak4NEPsU1f4,2500
31
33
  supervaizer/admin/templates/job_detail.html,sha256=LDTMWLURyVCp7SMTxQ4M8AFqNpbbUVUx253negp9JNA,10790
32
34
  supervaizer/admin/templates/job_start_test.html,sha256=eAogAit0JfuMU4N5YR7N03w0nD_Bi9mtiX8uJ7dHLPg,4270
33
35
  supervaizer/admin/templates/jobs_list.html,sha256=VJ2VYe62dHXvjQQgUAVyKcn58rO5919UuP3VKgxLVhM,9136
34
36
  supervaizer/admin/templates/jobs_table.html,sha256=BCOI_7QlxJ5XOra7WKou48a2lNwQYASCMFTtgzHNotw,5974
35
37
  supervaizer/admin/templates/navigation.html,sha256=Ci_CMLqBuIKRt3JCFtn9Vjz1AAkqC75WMT6IQUiByFI,11724
36
38
  supervaizer/admin/templates/recent_activity.html,sha256=hL06GXF1a-C_tkj3pRLrDTTDqG0KplcWMZFengFMuEc,4843
37
- supervaizer/admin/templates/server.html,sha256=m3qIQsEojogXQKTSD6ljKj4_lrwaLJTpTbMpfnWZHxU,5650
39
+ supervaizer/admin/templates/server.html,sha256=CScvNTNdc0Wi2entmzZHSnqrBjqIOZUsbQrWD58pBYM,8527
38
40
  supervaizer/admin/templates/server_status_cards.html,sha256=yJ36hkfgQpscYkiaodFDQPnmJWeb2W7gey09Z3T6JsY,7882
39
41
  supervaizer/admin/templates/supervaize_instructions.html,sha256=LTLla1xgIeLpFf7bond_lxH5qdQQ2ak52Fd7hqChi1I,10225
40
42
  supervaizer/deploy/__init__.py,sha256=DvngGQu8tS_Yz5FU4kKCvPpod11IGCtZWkUNeB5aVHI,557
41
- supervaizer/deploy/cli.py,sha256=SeG2rIT5cKl-I5s4qvpLLl9OXxmzelqKRDKcfH1GsSM,10183
43
+ supervaizer/deploy/cli.py,sha256=6najlZXh_3kSY_sfJdilf-iW5BqaudI2bKKrhzXN09A,10201
42
44
  supervaizer/deploy/docker.py,sha256=FLMKHOnbnjMFTcULdRdgwKJwT-q8JtVwbN1iSemh21w,13810
43
45
  supervaizer/deploy/driver_factory.py,sha256=Qm6DYVUfV3mlRHUglk5YlslGg6JYZ754uKeoiyxXw10,1487
44
46
  supervaizer/deploy/health.py,sha256=vh4SMOxy43QXi1fanQjWfWqoGTy_z1VXwwfy4Fq2bHg,13764
@@ -61,7 +63,8 @@ supervaizer/deploy/templates/debug_env.py,sha256=WFlxfiCAlkxM1NybNvtmmG5zJnoSvjW
61
63
  supervaizer/deploy/templates/docker-compose.yml.template,sha256=ZcW8WyhmqMElaxBpokuZG12n0tFJL8BY7k7Tvdz6tdw,1100
62
64
  supervaizer/deploy/templates/dockerignore.template,sha256=bYFRn6QGsjtRDH-Y7vzWk3-u3jIp90FajV2Ed94ah5M,515
63
65
  supervaizer/deploy/templates/entrypoint.sh,sha256=z079VUotu6DJX92fJiBB3eVwCEgcP6B9D9Fvwv_FL9w,463
64
- supervaizer/examples/controller_template.py,sha256=gAmA3GnDTdYkDk3NpVe-6Adbsl4Oxs-iZKN6mrO3WEg,6419
66
+ supervaizer/deploy/templates/index.html,sha256=z2ocEOYbWbl1Rz3Toc5JZkN7OAoW_GFV1yKKhdyC3vw,2160
67
+ supervaizer/examples/controller_template.py,sha256=bQRtZdq9Z8Pg0b3bRy4oVy6YIIM_e5DzhXVPOr1gFvk,6337
65
68
  supervaizer/protocol/__init__.py,sha256=00GHbUsLNsf0a1rQrUPpVN2Uy-7rDz72Ps6TUVD91tE,389
66
69
  supervaizer/protocol/a2a/__init__.py,sha256=1ACfPGLzS6XdZPiFxn1nVamvbasqtJJ7U1BBbSmT-nI,625
67
70
  supervaizer/protocol/a2a/model.py,sha256=sWzatlA_fcva_fdtM8Yh4cioRq0KbwV5AXpNCnsOtQo,7198
@@ -69,8 +72,8 @@ supervaizer/protocol/a2a/routes.py,sha256=rkQTNBD1NTYimKCb8iOk4bVf9ldDP1LqHfOsyh
69
72
  supervaizer/utils/__init__.py,sha256=fd0NFwN_cen3QPms2SOnuz4jcetay3f_31dit2As7EA,458
70
73
  supervaizer/utils/version_check.py,sha256=-tsOURpHVh0LNTbpQsyJDJENKszC-NzXDSO_EToEQPE,1893
71
74
  supervaizer/py.typed,sha256=bHhvLx7c6MqrzXVPbdK3qAOcSxzp4wDtTx4QifMC2EY,74
72
- supervaizer-0.10.5.dist-info/METADATA,sha256=PanDorKt110d-c25DnRY995GBEp0upFeCAELBXOL1BE,12647
73
- supervaizer-0.10.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
74
- supervaizer-0.10.5.dist-info/entry_points.txt,sha256=vL_IBR_AeEI2u2-6YL4PY9k2Mar4-gprG8-UxERWjmg,52
75
- supervaizer-0.10.5.dist-info/licenses/LICENSE.md,sha256=dmdnt1vfpxNPr8Lt0BnxKE5uzUwK3CWTthTUStgOXjY,15327
76
- supervaizer-0.10.5.dist-info/RECORD,,
75
+ supervaizer-0.10.15.dist-info/METADATA,sha256=-c3Opx2tY7d8zsKm14IRFJFE3ELjquXaPEFGYrGHhtQ,12648
76
+ supervaizer-0.10.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
77
+ supervaizer-0.10.15.dist-info/entry_points.txt,sha256=vL_IBR_AeEI2u2-6YL4PY9k2Mar4-gprG8-UxERWjmg,52
78
+ supervaizer-0.10.15.dist-info/licenses/LICENSE.md,sha256=dmdnt1vfpxNPr8Lt0BnxKE5uzUwK3CWTthTUStgOXjY,15327
79
+ supervaizer-0.10.15.dist-info/RECORD,,