supervaizer 0.9.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. supervaizer/__init__.py +88 -0
  2. supervaizer/__version__.py +10 -0
  3. supervaizer/account.py +304 -0
  4. supervaizer/account_service.py +87 -0
  5. supervaizer/admin/routes.py +1254 -0
  6. supervaizer/admin/templates/agent_detail.html +145 -0
  7. supervaizer/admin/templates/agents.html +175 -0
  8. supervaizer/admin/templates/agents_grid.html +80 -0
  9. supervaizer/admin/templates/base.html +233 -0
  10. supervaizer/admin/templates/case_detail.html +230 -0
  11. supervaizer/admin/templates/cases_list.html +182 -0
  12. supervaizer/admin/templates/cases_table.html +134 -0
  13. supervaizer/admin/templates/console.html +389 -0
  14. supervaizer/admin/templates/dashboard.html +153 -0
  15. supervaizer/admin/templates/job_detail.html +192 -0
  16. supervaizer/admin/templates/jobs_list.html +180 -0
  17. supervaizer/admin/templates/jobs_table.html +122 -0
  18. supervaizer/admin/templates/navigation.html +153 -0
  19. supervaizer/admin/templates/recent_activity.html +81 -0
  20. supervaizer/admin/templates/server.html +105 -0
  21. supervaizer/admin/templates/server_status_cards.html +121 -0
  22. supervaizer/agent.py +816 -0
  23. supervaizer/case.py +400 -0
  24. supervaizer/cli.py +135 -0
  25. supervaizer/common.py +283 -0
  26. supervaizer/event.py +181 -0
  27. supervaizer/examples/controller-template.py +195 -0
  28. supervaizer/instructions.py +145 -0
  29. supervaizer/job.py +379 -0
  30. supervaizer/job_service.py +155 -0
  31. supervaizer/lifecycle.py +417 -0
  32. supervaizer/parameter.py +173 -0
  33. supervaizer/protocol/__init__.py +11 -0
  34. supervaizer/protocol/a2a/__init__.py +21 -0
  35. supervaizer/protocol/a2a/model.py +227 -0
  36. supervaizer/protocol/a2a/routes.py +99 -0
  37. supervaizer/protocol/acp/__init__.py +21 -0
  38. supervaizer/protocol/acp/model.py +198 -0
  39. supervaizer/protocol/acp/routes.py +74 -0
  40. supervaizer/py.typed +1 -0
  41. supervaizer/routes.py +667 -0
  42. supervaizer/server.py +554 -0
  43. supervaizer/server_utils.py +54 -0
  44. supervaizer/storage.py +436 -0
  45. supervaizer/telemetry.py +81 -0
  46. supervaizer-0.9.6.dist-info/METADATA +245 -0
  47. supervaizer-0.9.6.dist-info/RECORD +50 -0
  48. supervaizer-0.9.6.dist-info/WHEEL +4 -0
  49. supervaizer-0.9.6.dist-info/entry_points.txt +2 -0
  50. supervaizer-0.9.6.dist-info/licenses/LICENSE.md +346 -0
@@ -0,0 +1,88 @@
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 import protocol
9
+ from supervaizer.account import Account
10
+ from supervaizer.agent import (
11
+ Agent,
12
+ AgentCustomMethodParams,
13
+ AgentMethod,
14
+ AgentMethodParams,
15
+ AgentMethods,
16
+ )
17
+ from supervaizer.case import Case, CaseNodeUpdate, CaseNoteType, Cases
18
+ from supervaizer.common import ApiError, ApiResult, ApiSuccess
19
+ from supervaizer.event import (
20
+ AgentRegisterEvent,
21
+ CaseStartEvent,
22
+ CaseUpdateEvent,
23
+ Event,
24
+ EventType,
25
+ JobFinishedEvent,
26
+ JobStartConfirmationEvent,
27
+ ServerRegisterEvent,
28
+ )
29
+ from supervaizer.job import Job, JobContext, JobInstructions, JobResponse, Jobs
30
+ from supervaizer.lifecycle import EntityEvents, EntityLifecycle, EntityStatus
31
+ from supervaizer.parameter import Parameter, ParametersSetup
32
+ from supervaizer.server import Server
33
+ from supervaizer.server_utils import ErrorResponse, ErrorType, create_error_response
34
+ from supervaizer.telemetry import (
35
+ Telemetry,
36
+ TelemetryCategory,
37
+ TelemetrySeverity,
38
+ TelemetryType,
39
+ )
40
+
41
+ __all__ = [
42
+ "Account",
43
+ "Agent",
44
+ "AgentCustomMethodParams",
45
+ "AgentMethod",
46
+ "AgentMethodParams",
47
+ "AgentMethods",
48
+ "AgentRegisterEvent",
49
+ "ApiError",
50
+ "ApiResult",
51
+ "ApiSuccess",
52
+ "Case",
53
+ "CaseNodeUpdate",
54
+ "CaseNoteType",
55
+ "Cases",
56
+ "CaseStartEvent",
57
+ "CaseUpdateEvent",
58
+ "create_error_response",
59
+ "EntityEvents",
60
+ "EntityEvents",
61
+ "EntityLifecycle",
62
+ "EntityLifecycle",
63
+ "EntityStatus",
64
+ "EntityStatus",
65
+ "ErrorResponse",
66
+ "ErrorType",
67
+ "Event",
68
+ "EventType",
69
+ "Job",
70
+ "JobContext",
71
+ "JobFinishedEvent",
72
+ "JobInstructions",
73
+ "JobResponse",
74
+ "Jobs",
75
+ "JobStartConfirmationEvent",
76
+ "Parameter",
77
+ "ParametersSetup",
78
+ "protocol",
79
+ "Server",
80
+ "ServerRegisterEvent",
81
+ "Telemetry",
82
+ "TelemetryCategory",
83
+ "TelemetrySeverity",
84
+ "TelemetryType",
85
+ ]
86
+
87
+ # Rebuild models to resolve forward references after all imports are done
88
+ Case.model_rebuild()
@@ -0,0 +1,10 @@
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
+ VERSION = "0.9.6"
9
+ API_VERSION = "v1"
10
+ TELEMETRY_VERSION = "v1"
supervaizer/account.py ADDED
@@ -0,0 +1,304 @@
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 typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union
9
+
10
+ import httpx
11
+ from pydantic import Field
12
+
13
+ from supervaizer.__version__ import VERSION
14
+ from supervaizer.common import ApiError, ApiResult, ApiSuccess, SvBaseModel
15
+ from supervaizer.telemetry import Telemetry
16
+
17
+ if TYPE_CHECKING:
18
+ from supervaizer.agent import Agent
19
+ from supervaizer.case import Case, CaseNodeUpdate
20
+ from supervaizer.event import Event
21
+ from supervaizer.job import Job
22
+ from supervaizer.server import Server
23
+
24
+
25
+ class AccountAbstract(SvBaseModel):
26
+ """
27
+ Account model for the Supervaize Control API.
28
+
29
+ This represents an account that can be used to authenticate and communicate
30
+ with the Supervaize SaaS API. Account parameters are provided by Supervaize.com.
31
+ Create an account at [https://supervaize.com/](https://supervaize.com/)
32
+
33
+ The account ID is generated by Supervaize.com. The account API key is provided
34
+ by Supervaize.com and is used to authenticate requests to the Supervaize Control API.
35
+ The API URL is provided by Supervaize.com and is the URL of the Supervaize SaaS API.
36
+
37
+ The account provides methods for registering servers and agents, sending events,
38
+ and communicating with the Supervaize platform.
39
+
40
+
41
+
42
+ Attributes:
43
+ workspace_id (str): The workspace ID provided by Supervaize.com
44
+ api_key (str): The API key provided by Supervaize.com for authentication
45
+ api_url (str): The URL of the Supervaize SaaS API provided by Supervaize.com
46
+ """
47
+
48
+ supervaizer_VERSION: ClassVar[str] = VERSION
49
+ workspace_id: str = Field(description="The workspace ID provided by Supervaize.com")
50
+ api_key: str = Field(
51
+ description="The API key provided by Supervaize.com for authentication"
52
+ )
53
+ api_url: str = Field(
54
+ description="The URL of the Supervaize SaaS API provided by Supervaize.com"
55
+ )
56
+
57
+ model_config = {
58
+ "reference_group": "Core",
59
+ "json_schema_extra": {
60
+ "examples": [
61
+ {
62
+ "workspace_id": "ws_1234567890abcdef",
63
+ "api_key": "sk_1234567890abcdef",
64
+ "api_url": "https://api.supervaize.com",
65
+ }
66
+ ]
67
+ },
68
+ }
69
+
70
+
71
+ class Account(AccountAbstract):
72
+ # URL patterns dictionary
73
+ _URL_PATTERNS = {
74
+ "team": "{api_url}/w/{workspace_id}",
75
+ "event": "{api_url}/w/{workspace_id}/api/v1/ctrl-events/",
76
+ "agent_by_id": "{api_url}/w/{workspace_id}/api/v1/agents/{agent_id}",
77
+ "agent_by_slug": "{api_url}/w/{workspace_id}/api/v1/agents/by-slug/{agent_slug}",
78
+ "telemetry": "{api_url}/{telemetry_version}/telemetry",
79
+ }
80
+
81
+ def __init__(self, **kwargs: Any) -> None:
82
+ """Account parameters are provided by Supervaize.com.
83
+ The account ID is generated by Supervaize.com. The account API key is provided by Supervaize.com and is used
84
+ to authenticate requests to the Supervaize Control API.
85
+ The API URL is provided by Supervaize.com and is the URL of the Supervaize
86
+ SaaS API.
87
+
88
+ Raises:
89
+ ValueError: If the account ID does not match the account name
90
+
91
+ Tested in tests/test_account.py
92
+ """
93
+ super().__init__(**kwargs)
94
+
95
+ @property
96
+ def api_url_w_v1(self) -> str:
97
+ """URL for the Supervaize Control API.
98
+ Tested in tests/test_account.py
99
+ """
100
+ return f"{self.api_url}/w/{self.workspace_id}/api/v1"
101
+
102
+ @property
103
+ def api_headers(self) -> Dict[str, str]:
104
+ """Headers for the Supervaize Control API.
105
+ Tested in tests/test_account.py
106
+ """
107
+ return {
108
+ "Authorization": f"Api-Key {self.api_key}",
109
+ "accept": "application/json",
110
+ "workspace": self.workspace_id,
111
+ }
112
+
113
+ @property
114
+ def api_url_team(self) -> str:
115
+ """URL for the Supervaize workspace team."""
116
+ return self.get_url("team")
117
+
118
+ @property
119
+ def url_event(self) -> str:
120
+ """URL for the Supervaize Control API.
121
+ Tested in tests/test_account.py
122
+ """
123
+ return f"{self.api_url_w_v1}/ctrl-events/"
124
+
125
+ def get_url(self, pattern_name: str, **kwargs: Any) -> str:
126
+ """Generate a URL using the predefined patterns.
127
+
128
+ Args:
129
+ pattern_name (str): The name of the URL pattern to use
130
+ **kwargs: Additional parameters for URL formatting
131
+
132
+ Returns:
133
+ str: The formatted URL
134
+
135
+ Raises:
136
+ KeyError: If the pattern_name is not found in _URL_PATTERNS
137
+ """
138
+ if pattern_name not in self._URL_PATTERNS:
139
+ raise KeyError(f"URL pattern '{pattern_name}' not found")
140
+
141
+ pattern = self._URL_PATTERNS[pattern_name]
142
+
143
+ # Default values from the account
144
+ url_params = {
145
+ "api_url": self.api_url,
146
+ "workspace_id": self.workspace_id,
147
+ "telemetry_version": "v1", # Default telemetry version
148
+ }
149
+
150
+ # Override with any provided kwargs
151
+ url_params.update(kwargs)
152
+
153
+ return pattern.format(**url_params)
154
+
155
+ def send_event(
156
+ self,
157
+ sender: Union["Agent", "Job", "Server", "Case", "CaseNodeUpdate"],
158
+ event: "Event",
159
+ ) -> ApiResult:
160
+ """Send an event to the Supervaize SaaS API.
161
+
162
+ Args:
163
+ sender (Union[Agent, Server, Case, CaseNodeUpdate]): The sender of the event
164
+ event (Event): The event to be sent
165
+
166
+ Returns:
167
+ ApiResult: ApiSuccess with response details if successful,
168
+ Raises:
169
+ Request exception if the request fails.
170
+ """
171
+ # Import here to avoid circular imports
172
+ from supervaizer.account_service import send_event as service_send_event
173
+
174
+ return service_send_event(self, sender, event)
175
+
176
+ def register_server(self, server: "Server") -> ApiResult:
177
+ """Register a server with the Supervaize Control API.
178
+
179
+ Args:
180
+ server (Server): The server to register.
181
+
182
+ Returns:
183
+ ApiResult: ApiSuccess with response details if successful,
184
+ ApiError with error details if request fails
185
+
186
+ Side effects:
187
+ - Sends a ServerRegisterEvent to the Supervaize Control API
188
+ """
189
+ # Import here to avoid circular imports
190
+ from supervaizer.event import ServerRegisterEvent
191
+
192
+ event = ServerRegisterEvent(server=server, account=self)
193
+ return self.send_event(sender=server, event=event)
194
+
195
+ def _create_api_result(
196
+ self,
197
+ success: bool,
198
+ message: str,
199
+ detail: Any = None,
200
+ url: str = "",
201
+ exception: Optional[Exception] = None,
202
+ ) -> ApiResult:
203
+ if success:
204
+ return ApiSuccess(message=message, detail=detail)
205
+ return ApiError(message=message, url=url, payload=None, exception=exception)
206
+
207
+ def get_agent_by(
208
+ self, agent_id: str | None = None, agent_slug: str | None = None
209
+ ) -> ApiSuccess | ApiError:
210
+ """Get an agent from the Supervaize Control API.
211
+
212
+ Args:
213
+ agent_id (str): The ID of the agent to get.
214
+ agent_slut (str): The name of the agent to get. (unique per workspace)
215
+
216
+ Returns:
217
+ ApiResult: ApiSuccess with response details if successful,ApiError with error details if request fails
218
+
219
+ """
220
+ url = ""
221
+ if agent_id:
222
+ url = f"{self.api_url_w_v1}/agents/{agent_id}"
223
+ elif agent_slug:
224
+ url = f"{self.api_url_w_v1}/agents/by-slug/{agent_slug}"
225
+ else:
226
+ raise ValueError("No agent ID or slug provided")
227
+
228
+ headers = self.api_headers
229
+
230
+ try:
231
+ response = httpx.get(url, headers=headers, follow_redirects=True)
232
+ response.raise_for_status()
233
+ return ApiSuccess(
234
+ message=f"GET Agent <{agent_slug or agent_id}>",
235
+ detail=response.json(),
236
+ )
237
+ except httpx.HTTPError as e:
238
+ return ApiError(
239
+ message=f"Error GET Agent <{agent_slug or agent_id}>",
240
+ url=url,
241
+ payload=None,
242
+ exception=e,
243
+ )
244
+
245
+ def register_agent(self, agent: "Agent", polling: bool = True) -> ApiResult:
246
+ """Send a registration event to the Supervaize Control API.
247
+ This will be used for polling, when the agent is registered without a server.
248
+ Args:
249
+ agent (Agent): The agent sending the registration event
250
+ polling (bool): If server is not defined, polling will be used.
251
+
252
+ Returns:
253
+ ApiResult: ApiSuccess with response details if successful,
254
+ ApiError with error details if request fails
255
+ """
256
+ # Import here to avoid circular imports
257
+ from supervaizer.event import AgentRegisterEvent
258
+
259
+ event = AgentRegisterEvent(agent=agent, account=self, polling=polling)
260
+ return self.send_event(agent, event)
261
+
262
+ def send_start_case(self, case: "Case") -> ApiResult:
263
+ # Import here to avoid circular imports
264
+ from supervaizer.event import CaseStartEvent
265
+
266
+ event = CaseStartEvent(case=case, account=self)
267
+ return self.send_event(case, event)
268
+
269
+ def send_update_case(self, case: "Case", update: "CaseNodeUpdate") -> ApiResult:
270
+ # Import here to avoid circular imports
271
+ from supervaizer.event import CaseUpdateEvent
272
+
273
+ event = CaseUpdateEvent(case=case, update=update, account=self)
274
+ return self.send_event(update, event)
275
+
276
+ def send_telemetry(self, telemetry: Telemetry) -> ApiResult:
277
+ """Send telemetry data to the Supervaize Control API.
278
+
279
+ Args:
280
+ telemetry (Telemetry): The telemetry object to be sent
281
+
282
+ Returns:
283
+ ApiResult: ApiSuccess with response details if successful,
284
+ ApiError with error details if request fails
285
+ """
286
+ url = self.get_url("telemetry")
287
+ headers = self.api_headers
288
+ payload = {"workspace_id": self.workspace_id} | telemetry.payload
289
+ result: ApiSuccess | ApiError
290
+ try:
291
+ response = httpx.post(url, headers=headers, json=payload)
292
+ response.raise_for_status()
293
+ result = ApiSuccess(
294
+ message=f"Telemetry sent {telemetry.type.name}", detail=response.text
295
+ )
296
+ except httpx.HTTPError as e:
297
+ result = ApiError(
298
+ message=f"Error sending telemetry {telemetry.type.name}",
299
+ url=url,
300
+ payload=payload,
301
+ exception=e,
302
+ )
303
+
304
+ return result
@@ -0,0 +1,87 @@
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
+ # This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
8
+ # If a copy of the MPL was not distributed with this file, you can obtain one at
9
+ # https://mozilla.org/MPL/2.0/.
10
+
11
+ import logging
12
+ from typing import TYPE_CHECKING, Union
13
+
14
+ import httpx
15
+
16
+ from supervaizer.common import ApiError, ApiResult, ApiSuccess, log
17
+
18
+ logger = logging.getLogger("httpx")
19
+ # Enable httpx debug logging (optional - uncomment for transport-level debugging)
20
+ logger.setLevel(logging.DEBUG)
21
+
22
+ if TYPE_CHECKING:
23
+ from supervaizer.account import Account
24
+ from supervaizer.agent import Agent
25
+ from supervaizer.case import Case, CaseNodeUpdate
26
+ from supervaizer.event import Event
27
+ from supervaizer.job import Job
28
+ from supervaizer.server import Server
29
+
30
+
31
+ def send_event(
32
+ account: "Account",
33
+ sender: Union["Agent", "Server", "Job", "Case", "CaseNodeUpdate"],
34
+ event: "Event",
35
+ ) -> ApiResult:
36
+ """Send an event to the Supervaize SaaS API.
37
+
38
+ Args:
39
+ account (Account): The account used to authenticate the request
40
+ sender (Union[Agent, Server, Case, CaseNodeUpdate]): The sender of the event
41
+ event (Event): The event to be sent
42
+
43
+ Returns:
44
+ ApiResult: ApiSuccess with response details if successful,
45
+ Raises:
46
+ Request exception if the request fails.
47
+
48
+ Side effects:
49
+ - Sends an event to the Supervaize Control API
50
+
51
+ Tested in tests/test_account_service.py
52
+ """
53
+
54
+ headers = account.api_headers
55
+ payload = event.payload
56
+
57
+ # Generate curl equivalent for debugging
58
+
59
+ curl_headers = " ".join([f'-H "{k}: {v}"' for k, v in headers.items()])
60
+ curl_cmd = f"curl -X 'GET' '{account.url_event}' {curl_headers}"
61
+
62
+ try:
63
+ response = httpx.post(account.url_event, headers=headers, json=payload)
64
+
65
+ # Log response details before raising for status
66
+
67
+ response.raise_for_status()
68
+ result = ApiSuccess(
69
+ message=f"POST Event {event.type.name} sent", detail=response.text
70
+ )
71
+
72
+ log.success(result.log_message)
73
+ except httpx.HTTPError as e:
74
+ # Enhanced error logging
75
+ log.error("[Send event] HTTP Error occurred")
76
+ log.warning(f"⚠️ Try to connect via curl:\n{curl_cmd}")
77
+
78
+ error_result = ApiError(
79
+ message=f"Error sending event {event.type.name}",
80
+ url=account.url_event,
81
+ payload=event.payload,
82
+ exception=e,
83
+ )
84
+ log.error(f"[Send event] Error details: {error_result.dict}")
85
+ log.error(error_result.log_message)
86
+ raise e
87
+ return result