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
supervaizer/common.py ADDED
@@ -0,0 +1,283 @@
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 base64
9
+ import json
10
+ import os
11
+ import traceback
12
+ from typing import Any, Callable, Dict, Optional, TypeVar
13
+
14
+ import demjson3
15
+ from cryptography.hazmat.primitives import hashes
16
+ from cryptography.hazmat.primitives.asymmetric import padding, rsa
17
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
18
+ from loguru import logger
19
+ from pydantic import BaseModel
20
+
21
+ log = logger.bind(module="supervaize")
22
+
23
+ T = TypeVar("T")
24
+
25
+
26
+ class SvBaseModel(BaseModel):
27
+ """
28
+ Base model for all Supervaize models.
29
+ """
30
+
31
+ @property
32
+ def to_dict(self) -> Dict[str, Any]:
33
+ """
34
+ Convert the model to a dictionary.
35
+
36
+ Note: Using mode="json" to handle datetime serialization.
37
+ Tested in tests/test_common.test_sv_base_model_json_conversion
38
+ """
39
+ return self.model_dump(mode="json")
40
+
41
+ @property
42
+ def to_json(self) -> str:
43
+ return self.model_dump_json()
44
+
45
+
46
+ class ApiResult:
47
+ def __init__(self, message: str, detail: Optional[Dict[str, Any]], code: str):
48
+ self.message = message
49
+ self.code = str(code)
50
+ self.detail = detail
51
+
52
+ def __str__(self) -> str:
53
+ return f"{self.json_return}"
54
+
55
+ def __repr__(self) -> str:
56
+ return f"{self.__class__.__name__} ({self.message})"
57
+
58
+ @property
59
+ def dict(self) -> Dict[str, Any]:
60
+ return {key: value for key, value in self.__dict__.items()}
61
+
62
+ @property
63
+ def json_return(self) -> str:
64
+ return json.dumps(self.dict)
65
+
66
+
67
+ class ApiSuccess(ApiResult):
68
+ """
69
+ ApiSuccess is a class that extends ApiResult.
70
+ It is used to return a success response from the API.
71
+
72
+ If the detail is a string, it is decoded as a JSON object: Expects a JSON object with a
73
+ key "object" and a value of the JSON object to return.
74
+ If the detail is a dictionary, it is used as is.
75
+
76
+
77
+ Tested in tests/test_common.py
78
+ """
79
+
80
+ def __init__(
81
+ self, message: str, detail: Optional[Dict[str, Any] | str], code: int = 200
82
+ ):
83
+ log_message = "✅ "
84
+ if isinstance(detail, str):
85
+ result = demjson3.decode(detail, return_errors=True)
86
+ detail = {"object": result.object}
87
+ id = result.object.get("id") or None
88
+ if id is not None:
89
+ log_message += f"{message} : {id}"
90
+ else:
91
+ log_message += f"{message}"
92
+ else:
93
+ id = None
94
+ detail = detail
95
+ log_message += f"{message}"
96
+
97
+ super().__init__(
98
+ message=message,
99
+ detail=detail,
100
+ code=str(code),
101
+ )
102
+ self.id: Optional[str] = id
103
+ self.log_message = log_message
104
+ log.debug(f"[API Success] {self.log_message}")
105
+
106
+
107
+ class ApiError(ApiResult):
108
+ """
109
+ ApiError is a class that extends ApiResult.
110
+ It can be used to return an error response from the API.
111
+ Note : not really useful for the moment, as API errors raise exception.
112
+
113
+ Tested in tests/test_common.py
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ message: str,
119
+ code: str = "",
120
+ detail: Optional[Dict[str, Any]] = None,
121
+ exception: Optional[Exception] = None,
122
+ url: str = "",
123
+ payload: Optional[Dict[str, Any]] = None,
124
+ ):
125
+ super().__init__(message, detail, code)
126
+ self.exception = exception
127
+ self.url = url
128
+ self.payload = payload
129
+ self.log_message = f"❌ {message} : {self.exception}"
130
+
131
+ @property
132
+ def dict(self) -> Dict[str, Any]:
133
+ if self.exception:
134
+ exception_dict: Dict[str, Any] = {
135
+ "type": type(self.exception).__name__,
136
+ "message": str(self.exception),
137
+ "traceback": traceback.format_exc(),
138
+ "attributes": {},
139
+ }
140
+ if (
141
+ response := hasattr(self.exception, "response")
142
+ and self.exception.response
143
+ ):
144
+ self.code = str(response.status_code) or ""
145
+
146
+ try:
147
+ response_text = self.exception.response.text
148
+ exception_dict["response"] = json.loads(response_text)
149
+ except json.JSONDecodeError:
150
+ pass
151
+ for attr in dir(self.exception):
152
+ try:
153
+ if (
154
+ not attr.startswith("__")
155
+ and not callable(attribute := getattr(self.exception, attr))
156
+ and getattr(self.exception, attr)
157
+ ):
158
+ try:
159
+ exception_dict["attributes"][attr] = json.loads(
160
+ str(attribute)
161
+ )
162
+ except json.JSONDecodeError:
163
+ pass
164
+ except Exception:
165
+ pass
166
+
167
+ result: Dict[str, Any] = {
168
+ "message": self.message,
169
+ "code": self.code,
170
+ "url": self.url,
171
+ "payload": self.payload,
172
+ "detail": self.detail,
173
+ }
174
+ if self.exception:
175
+ result["exception"] = exception_dict
176
+ return result
177
+
178
+
179
+ def singleton(cls: type[T]) -> Callable[..., T]:
180
+ """Decorator to create a singleton class
181
+ Tested in tests/test_common.py
182
+ """
183
+ instances: Dict[type[T], T] = {}
184
+
185
+ def get_instance(*args: Any, **kwargs: Any) -> T:
186
+ if cls not in instances:
187
+ instances[cls] = cls(*args, **kwargs)
188
+ return instances[cls]
189
+
190
+ return get_instance
191
+
192
+
193
+ def encrypt_value(value_to_encrypt: str, public_key: rsa.RSAPublicKey) -> str:
194
+ """Encrypt using hybrid RSA+AES encryption to handle messages of any size.
195
+
196
+ Args:
197
+ value_to_encrypt (str): Value to encrypt
198
+ public_key (rsa.RSAPublicKey): RSA public key
199
+
200
+ Returns:
201
+ str: Base64 encoded encrypted value containing both the encrypted AES key and encrypted data
202
+
203
+ Raises:
204
+ ValueError: If encryption fails
205
+ """
206
+
207
+ # Generate random AES key and IV
208
+ aes_key = os.urandom(32) # 256-bit key
209
+ iv = os.urandom(16)
210
+
211
+ # Encrypt the AES key with RSA
212
+ encrypted_key = public_key.encrypt(
213
+ aes_key,
214
+ padding.OAEP(
215
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
216
+ algorithm=hashes.SHA256(),
217
+ label=None,
218
+ ),
219
+ )
220
+
221
+ # Create AES cipher
222
+ cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
223
+ encryptor = cipher.encryptor()
224
+
225
+ # Pad data to block size
226
+ value_bytes = value_to_encrypt.encode("utf-8")
227
+ pad_length = 16 - (len(value_bytes) % 16)
228
+ value_bytes += bytes([pad_length]) * pad_length
229
+
230
+ # Encrypt data with AES
231
+ encrypted_data = encryptor.update(value_bytes) + encryptor.finalize()
232
+
233
+ # Combine encrypted key, IV and data
234
+ combined = encrypted_key + iv + encrypted_data
235
+
236
+ # Return base64 encoded result
237
+ return base64.b64encode(combined).decode("utf-8")
238
+
239
+
240
+ def decrypt_value(encrypted_value: str, private_key: rsa.RSAPrivateKey) -> str:
241
+ """Decrypt using hybrid RSA+AES decryption.
242
+
243
+ Args:
244
+ encrypted_value (str): Base64 encoded encrypted value
245
+ private_key (rsa.RSAPrivateKey): RSA private key
246
+
247
+ Returns:
248
+ str: Decrypted value as string
249
+
250
+ Raises:
251
+ ValueError: If decryption fails
252
+ """
253
+
254
+ # Decode base64
255
+ combined = base64.b64decode(encrypted_value)
256
+
257
+ # Extract components - first 256 bytes are RSA encrypted key
258
+ encrypted_key = combined[:256] # RSA-2048 output is 256 bytes
259
+ iv = combined[256:272] # 16 bytes IV
260
+ encrypted_data = combined[272:] # Rest is encrypted data
261
+
262
+ # Decrypt AES key
263
+ aes_key = private_key.decrypt(
264
+ encrypted_key,
265
+ padding.OAEP(
266
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
267
+ algorithm=hashes.SHA256(),
268
+ label=None,
269
+ ),
270
+ )
271
+
272
+ # Create AES cipher
273
+ cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
274
+ decryptor = cipher.decryptor()
275
+
276
+ # Decrypt data
277
+ decrypted_padded = decryptor.update(encrypted_data) + decryptor.finalize()
278
+
279
+ # Remove padding
280
+ pad_length = decrypted_padded[-1]
281
+ decrypted = decrypted_padded[:-pad_length]
282
+
283
+ return decrypted.decode("utf-8")
supervaizer/event.py ADDED
@@ -0,0 +1,181 @@
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
+ from enum import Enum
8
+ from typing import TYPE_CHECKING, Any, ClassVar, Dict
9
+
10
+ from supervaizer.__version__ import VERSION
11
+ from supervaizer.common import SvBaseModel
12
+ from supervaizer.lifecycle import EntityStatus
13
+
14
+ if TYPE_CHECKING:
15
+ from supervaizer.agent import Agent
16
+ from supervaizer.case import Case, CaseNodeUpdate
17
+ from supervaizer.job import Job
18
+ from supervaizer.server import Server
19
+
20
+
21
+ class EventType(str, Enum):
22
+ AGENT_REGISTER = "agent.register"
23
+ SERVER_REGISTER = "server.register"
24
+ AGENT_WAKEUP = "agent.wakeup"
25
+ AGENT_SEND_ANOMALY = "agent.anomaly"
26
+ INTERMEDIARY = "agent.intermediary"
27
+ JOB_START_CONFIRMATION = "agent.job.start.confirmation"
28
+ JOB_END = "agent.job.end"
29
+ JOB_STATUS = "agent.job.status"
30
+ JOB_RESULT = "agent.job.result"
31
+ JOB_ERROR = "agent.job.error"
32
+ CASE_START = "agent.case.start"
33
+ CASE_END = "agent.case.end"
34
+ CASE_STATUS = "agent.case.status"
35
+ CASE_RESULT = "agent.case.result"
36
+ CASE_UPDATE = "agent.case.update"
37
+
38
+
39
+ class AbstractEvent(SvBaseModel):
40
+ supervaizer_VERSION: ClassVar[str] = VERSION
41
+ source: Dict[str, Any]
42
+ account: Any # Use Any to avoid Pydantic type resolution issues
43
+ type: EventType
44
+ object_type: str
45
+ details: Dict[str, Any]
46
+
47
+
48
+ class Event(AbstractEvent):
49
+ """Base class for all events in the Supervaize Control system.
50
+
51
+ Events represent messages sent from agents to the control system to communicate
52
+ status, anomalies, deliverables and other information.
53
+
54
+ Inherits from AbstractEvent which defines the core event attributes:
55
+ - source: The source/origin of the event (e.g. agent/server URI)
56
+ - type: The EventType enum indicating the event category
57
+ - account: The account that the event belongs to
58
+ - details: A dictionary containing event-specific details
59
+
60
+ Tests in tests/test_event.py
61
+ """
62
+
63
+ def __init__(self, **kwargs: Any) -> None:
64
+ super().__init__(**kwargs)
65
+
66
+ @property
67
+ def payload(self) -> Dict[str, Any]:
68
+ """
69
+ Returns the payload for the event.
70
+ This must be a dictionary that can be serialized to JSON to be sent in the request body.
71
+ """
72
+ return {
73
+ "source": self.source,
74
+ "workspace": f"{self.account.workspace_id}",
75
+ "event_type": f"{self.type.value}",
76
+ "object_type": self.object_type,
77
+ "details": self.details,
78
+ }
79
+
80
+
81
+ class AgentRegisterEvent(Event):
82
+ """Event sent when an agent registers with the control system.
83
+
84
+ Test in tests/test_agent_register_event.py
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ agent: "Agent",
90
+ account: Any, # Use Any to avoid type resolution issues
91
+ polling: bool = True,
92
+ ) -> None:
93
+ super().__init__(
94
+ type=EventType.AGENT_REGISTER,
95
+ account=account,
96
+ source={"agent": agent.slug},
97
+ object_type="agent",
98
+ details=agent.registration_info | {"polling": polling},
99
+ )
100
+
101
+
102
+ class ServerRegisterEvent(Event):
103
+ def __init__(
104
+ self,
105
+ account: Any, # Use Any to avoid type resolution issues
106
+ server: "Server",
107
+ ) -> None:
108
+ super().__init__(
109
+ type=EventType.SERVER_REGISTER,
110
+ source={"server": server.uri},
111
+ account=account,
112
+ object_type="server",
113
+ details=server.registration_info,
114
+ )
115
+
116
+
117
+ class JobStartConfirmationEvent(Event):
118
+ def __init__(
119
+ self,
120
+ job: "Job",
121
+ account: Any, # Use Any to avoid type resolution issues
122
+ ) -> None:
123
+ super().__init__(
124
+ type=EventType.JOB_START_CONFIRMATION,
125
+ account=account,
126
+ source={"job": job.id},
127
+ object_type="job",
128
+ details=job.registration_info,
129
+ )
130
+
131
+
132
+ class JobFinishedEvent(Event):
133
+ def __init__(self, job: "Job", account: Any) -> None:
134
+ # Check if job has responses, otherwise use the job's current status
135
+ if job.responses:
136
+ details = job.responses[-1].status
137
+ else:
138
+ details = job.status
139
+
140
+ event_type = (
141
+ EventType.JOB_END
142
+ if details == EntityStatus.COMPLETED
143
+ else EventType.JOB_ERROR
144
+ )
145
+
146
+ super().__init__(
147
+ type=event_type,
148
+ account=account,
149
+ source={"job": job.id},
150
+ object_type="job",
151
+ details=job.registration_info,
152
+ )
153
+
154
+
155
+ class CaseStartEvent(Event):
156
+ def __init__(
157
+ self, case: "Case", account: Any
158
+ ) -> None: # Use Any to avoid type resolution issues
159
+ super().__init__(
160
+ type=EventType.CASE_START,
161
+ account=account,
162
+ source={"job": case.job_id, "case": case.id},
163
+ object_type="case",
164
+ details=case.registration_info,
165
+ )
166
+
167
+
168
+ class CaseUpdateEvent(Event):
169
+ def __init__(
170
+ self,
171
+ case: "Case",
172
+ account: Any,
173
+ update: "CaseNodeUpdate",
174
+ ) -> None:
175
+ super().__init__(
176
+ type=EventType.CASE_UPDATE,
177
+ account=account,
178
+ source={"job": case.job_id, "case": case.id},
179
+ object_type="case",
180
+ details=update.registration_info,
181
+ )
@@ -0,0 +1,195 @@
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 is an example file.
8
+ # It must be copied / renamed to supervaizer_control.py
9
+ # and edited to configure your agent(s)
10
+
11
+ import os
12
+ import shortuuid
13
+ from rich.console import Console
14
+
15
+ from supervaizer import (
16
+ Agent,
17
+ AgentMethod,
18
+ AgentMethods,
19
+ Parameter,
20
+ ParametersSetup,
21
+ Server,
22
+ )
23
+ from supervaizer.account import Account
24
+
25
+ # Create a console with default style set to yellow
26
+ console = Console(style="yellow")
27
+
28
+ # Public url of your hosted agent (including port if needed)
29
+ # Use loca.lt or ngrok to get a public url during development.
30
+ # This can be setup from environment variables.
31
+ # SUPERVAIZER_HOST and SUPERVAIZER_PORT
32
+ DEV_PUBLIC_URL = "https://myagent-dev.loca.lt"
33
+ # Public url of your hosted agent
34
+ PROD_PUBLIC_URL = "https://myagent.cloud-hosting.net:8001"
35
+
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
+ )
59
+
60
+ # Define the method used to start a job
61
+ job_start_method: AgentMethod = AgentMethod(
62
+ name="start", # This is required
63
+ method="example_agent.example_synchronous_job_start", # Path to the main function in dotted notation.
64
+ is_async=False, # Only use sync methods for the moment
65
+ params={"action": "start"}, # If default parameters must be passed to the function.
66
+ fields=[
67
+ {
68
+ "name": "Company to research", # Field name - displayed in the UI
69
+ "type": str, # Python type of the field for pydantic validation - note , ChoiceField and MultipleChoiceField are a list[str]
70
+ "field_type": "CharField", # Field type for persistence.
71
+ "description": "Company to research", # Optional - Description of the field - displayed in the UI
72
+ "default": "Google", # Optional - Default value for the field
73
+ "required": True, # Whether the field is required
74
+ },
75
+ {
76
+ "name": "Max number of results",
77
+ "type": int,
78
+ "field_type": "IntegerField",
79
+ "required": True,
80
+ },
81
+ {
82
+ "name": "Subscribe to updates",
83
+ "type": bool,
84
+ "field_type": "BooleanField",
85
+ "required": False,
86
+ },
87
+ {
88
+ "name": "Type of research",
89
+ "type": str,
90
+ "field_type": "ChoiceField",
91
+ "choices": [["A", "Advanced"], ["R", "Restricted"]],
92
+ "widget": "RadioSelect",
93
+ "required": True,
94
+ },
95
+ {
96
+ "name": "Details of research",
97
+ "type": str,
98
+ "field_type": "CharField",
99
+ "widget": "Textarea",
100
+ "required": False,
101
+ },
102
+ {
103
+ "name": "List of countries",
104
+ "type": list[str],
105
+ "field_type": "MultipleChoiceField",
106
+ "choices": [
107
+ ["PA", "Panama"],
108
+ ["PG", "Papua New Guinea"],
109
+ ["PY", "Paraguay"],
110
+ ["PE", "Peru"],
111
+ ["PH", "Philippines"],
112
+ ["PN", "Pitcairn"],
113
+ ["PL", "Poland"],
114
+ ],
115
+ "required": True,
116
+ },
117
+ {
118
+ "name": "languages",
119
+ "type": list[str],
120
+ "field_type": "MultipleChoiceField",
121
+ "choices": [["en", "English"], ["fr", "French"], ["es", "Spanish"]],
122
+ "required": False,
123
+ },
124
+ ],
125
+ description="Start the collection of new competitor summary",
126
+ )
127
+
128
+ job_stop_method: AgentMethod = AgentMethod(
129
+ name="stop",
130
+ method="control.stop",
131
+ params={"action": "stop"},
132
+ description="Stop the agent",
133
+ )
134
+ job_status_method: AgentMethod = AgentMethod(
135
+ name="status",
136
+ method="hello.mystatus",
137
+ params={"status": "statusvalue"},
138
+ description="Get the status of the agent",
139
+ )
140
+ custom_method: AgentMethod = AgentMethod(
141
+ name="custom",
142
+ method="control.custom",
143
+ params={"action": "custom"},
144
+ description="Custom method",
145
+ )
146
+
147
+ custom_method2: AgentMethod = AgentMethod(
148
+ name="custom2",
149
+ method="control.custom2",
150
+ params={"action": "custom2"},
151
+ description="Custom method",
152
+ )
153
+
154
+
155
+ agent_name = "competitor_summary"
156
+
157
+ # Define the Agent
158
+ agent: Agent = Agent(
159
+ name=agent_name,
160
+ id=shortuuid.uuid(f"{agent_name}"),
161
+ author="John Doe", # Author of the agent
162
+ developer="Developer", # Developer of the controller integration
163
+ maintainer="Ive Maintained", # Maintainer of the integration
164
+ editor="DevAiExperts", # Editor (usually a company)
165
+ version="1.3", # Version string
166
+ description="This is a test agent",
167
+ tags=["testtag", "testtag2"],
168
+ methods=AgentMethods(
169
+ job_start=job_start_method,
170
+ job_stop=job_stop_method,
171
+ job_status=job_status_method,
172
+ chat=None,
173
+ custom={"custom1": custom_method, "custom2": custom_method2},
174
+ ),
175
+ parameters_setup=agent_parameters,
176
+ )
177
+
178
+ account: Account = Account(
179
+ workspace_id=os.getenv("SUPERVAIZE_WORKSPACE_ID"), # From supervaize.com
180
+ api_key=os.getenv("SUPERVAIZE_API_KEY"), # From supervaize
181
+ api_url=os.getenv("SUPERVAIZE_API_URL"), # From supervaize
182
+ )
183
+
184
+ # Define the supervaizer server capabilities
185
+ sv_server: Server = Server(
186
+ agents=[agent],
187
+ a2a_endpoints=True, # Enable A2A endpoints
188
+ acp_endpoints=True, # Enable ACP endpoints
189
+ supervisor_account=account, # Account of the supervisor from Supervaize
190
+ )
191
+
192
+
193
+ if __name__ == "__main__":
194
+ # Start the supervaize server
195
+ sv_server.launch(log_level="DEBUG")