digitalkin 0.3.1.dev2__py3-none-any.whl → 0.3.2a3__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.
- base_server/server_async_insecure.py +6 -5
- base_server/server_async_secure.py +6 -5
- base_server/server_sync_insecure.py +5 -4
- base_server/server_sync_secure.py +5 -4
- digitalkin/__version__.py +1 -1
- digitalkin/core/job_manager/base_job_manager.py +1 -1
- digitalkin/core/job_manager/single_job_manager.py +78 -36
- digitalkin/core/job_manager/taskiq_broker.py +7 -6
- digitalkin/core/job_manager/taskiq_job_manager.py +9 -5
- digitalkin/core/task_manager/base_task_manager.py +3 -1
- digitalkin/core/task_manager/surrealdb_repository.py +29 -7
- digitalkin/core/task_manager/task_executor.py +46 -12
- digitalkin/core/task_manager/task_session.py +132 -102
- digitalkin/grpc_servers/module_server.py +95 -171
- digitalkin/grpc_servers/module_servicer.py +121 -19
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +36 -10
- digitalkin/grpc_servers/utils/utility_schema_extender.py +106 -0
- digitalkin/models/__init__.py +1 -1
- digitalkin/models/core/job_manager_models.py +0 -8
- digitalkin/models/core/task_monitor.py +23 -1
- digitalkin/models/grpc_servers/models.py +95 -8
- digitalkin/models/module/__init__.py +26 -13
- digitalkin/models/module/base_types.py +61 -0
- digitalkin/models/module/module_context.py +279 -13
- digitalkin/models/module/module_types.py +28 -392
- digitalkin/models/module/setup_types.py +547 -0
- digitalkin/models/module/tool_cache.py +230 -0
- digitalkin/models/module/tool_reference.py +160 -0
- digitalkin/models/module/utility.py +167 -0
- digitalkin/models/services/cost.py +22 -1
- digitalkin/models/services/registry.py +77 -0
- digitalkin/modules/__init__.py +5 -1
- digitalkin/modules/_base_module.py +188 -63
- digitalkin/modules/archetype_module.py +6 -1
- digitalkin/modules/tool_module.py +6 -1
- digitalkin/modules/triggers/__init__.py +8 -0
- digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
- digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
- digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
- digitalkin/services/__init__.py +4 -0
- digitalkin/services/communication/__init__.py +7 -0
- digitalkin/services/communication/communication_strategy.py +87 -0
- digitalkin/services/communication/default_communication.py +104 -0
- digitalkin/services/communication/grpc_communication.py +264 -0
- digitalkin/services/cost/cost_strategy.py +36 -14
- digitalkin/services/cost/default_cost.py +61 -1
- digitalkin/services/cost/grpc_cost.py +98 -2
- digitalkin/services/filesystem/grpc_filesystem.py +9 -2
- digitalkin/services/registry/__init__.py +22 -1
- digitalkin/services/registry/default_registry.py +156 -4
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +382 -0
- digitalkin/services/registry/registry_models.py +15 -0
- digitalkin/services/registry/registry_strategy.py +106 -4
- digitalkin/services/services_config.py +25 -3
- digitalkin/services/services_models.py +5 -1
- digitalkin/services/setup/default_setup.py +1 -1
- digitalkin/services/setup/grpc_setup.py +1 -1
- digitalkin/services/storage/grpc_storage.py +1 -1
- digitalkin/services/user_profile/__init__.py +11 -0
- digitalkin/services/user_profile/grpc_user_profile.py +2 -2
- digitalkin/services/user_profile/user_profile_strategy.py +0 -15
- digitalkin/utils/__init__.py +15 -3
- digitalkin/utils/conditional_schema.py +260 -0
- digitalkin/utils/dynamic_schema.py +4 -0
- digitalkin/utils/schema_splitter.py +290 -0
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.dist-info}/METADATA +12 -12
- digitalkin-0.3.2a3.dist-info/RECORD +144 -0
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.dist-info}/WHEEL +1 -1
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.dist-info}/top_level.txt +1 -0
- modules/archetype_with_tools_module.py +232 -0
- modules/cpu_intensive_module.py +1 -1
- modules/dynamic_setup_module.py +5 -29
- modules/minimal_llm_module.py +1 -1
- modules/text_transform_module.py +1 -1
- monitoring/digitalkin_observability/__init__.py +46 -0
- monitoring/digitalkin_observability/http_server.py +150 -0
- monitoring/digitalkin_observability/interceptors.py +176 -0
- monitoring/digitalkin_observability/metrics.py +201 -0
- monitoring/digitalkin_observability/prometheus.py +137 -0
- monitoring/tests/test_metrics.py +172 -0
- services/filesystem_module.py +7 -5
- services/storage_module.py +4 -2
- digitalkin/grpc_servers/registry_server.py +0 -65
- digitalkin/grpc_servers/registry_servicer.py +0 -456
- digitalkin-0.3.1.dev2.dist-info/RECORD +0 -119
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""gRPC Registry client implementation.
|
|
2
|
+
|
|
3
|
+
This module provides a gRPC-based registry client that communicates with
|
|
4
|
+
the Service Provider's Registry service.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from agentic_mesh_protocol.registry.v1 import (
|
|
10
|
+
registry_enums_pb2,
|
|
11
|
+
registry_models_pb2,
|
|
12
|
+
registry_requests_pb2,
|
|
13
|
+
registry_service_pb2_grpc,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from digitalkin.grpc_servers.utils.exceptions import ServerError
|
|
17
|
+
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
18
|
+
from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
|
|
19
|
+
from digitalkin.logger import logger
|
|
20
|
+
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
21
|
+
from digitalkin.models.services.registry import (
|
|
22
|
+
ModuleInfo,
|
|
23
|
+
RegistryModuleStatus,
|
|
24
|
+
RegistryModuleType,
|
|
25
|
+
RegistrySetupStatus,
|
|
26
|
+
RegistryVisibility,
|
|
27
|
+
SetupInfo,
|
|
28
|
+
)
|
|
29
|
+
from digitalkin.services.registry.exceptions import (
|
|
30
|
+
RegistryModuleNotFoundError,
|
|
31
|
+
RegistryServiceError,
|
|
32
|
+
)
|
|
33
|
+
from digitalkin.services.registry.registry_models import ModuleStatusInfo
|
|
34
|
+
from digitalkin.services.registry.registry_strategy import RegistryStrategy
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GrpcRegistry(RegistryStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
|
|
38
|
+
"""gRPC-based registry client.
|
|
39
|
+
|
|
40
|
+
This client communicates with the Service Provider's Registry service
|
|
41
|
+
to perform module discovery, registration, and status management operations.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
mission_id: str,
|
|
47
|
+
setup_id: str,
|
|
48
|
+
setup_version_id: str,
|
|
49
|
+
client_config: ClientConfig,
|
|
50
|
+
config: dict[str, Any] | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Initialize the gRPC registry client."""
|
|
53
|
+
RegistryStrategy.__init__(self, mission_id, setup_id, setup_version_id, config)
|
|
54
|
+
self.service_name = "RegistryService"
|
|
55
|
+
self.stub = registry_service_pb2_grpc.RegistryServiceStub(self._init_channel(client_config))
|
|
56
|
+
logger.debug("Channel client 'Registry' initialized successfully")
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def _proto_to_module_info(
|
|
60
|
+
descriptor: registry_models_pb2.ModuleDescriptor,
|
|
61
|
+
) -> ModuleInfo:
|
|
62
|
+
"""Convert proto ModuleDescriptor to ModuleInfo.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
descriptor: Proto ModuleDescriptor message.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
ModuleInfo with mapped fields.
|
|
69
|
+
"""
|
|
70
|
+
type_name = registry_enums_pb2.ModuleType.Name(descriptor.module_type).removeprefix("MODULE_TYPE_")
|
|
71
|
+
return ModuleInfo(
|
|
72
|
+
module_id=descriptor.id,
|
|
73
|
+
module_type=RegistryModuleType[type_name],
|
|
74
|
+
address=descriptor.address,
|
|
75
|
+
port=descriptor.port,
|
|
76
|
+
version=descriptor.version,
|
|
77
|
+
module_name=descriptor.name,
|
|
78
|
+
documentation=descriptor.documentation or None,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _proto_to_setup_info(descriptor: registry_models_pb2.SetupDescriptor) -> SetupInfo | None:
|
|
83
|
+
"""Convert proto SetupDescriptor to SetupInfo.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
descriptor: Proto SetupDescriptor message.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
SetupInfo with mapped fields, or None if descriptor is empty.
|
|
90
|
+
"""
|
|
91
|
+
if not descriptor.id:
|
|
92
|
+
return None
|
|
93
|
+
status_name = registry_enums_pb2.SetupStatus.Name(descriptor.status).removeprefix("SETUP_STATUS_")
|
|
94
|
+
visibility_name = registry_enums_pb2.Visibility.Name(descriptor.visibility).removeprefix("VISIBILITY_")
|
|
95
|
+
return SetupInfo(
|
|
96
|
+
setup_id=descriptor.id,
|
|
97
|
+
name=descriptor.name,
|
|
98
|
+
documentation=descriptor.documentation or None,
|
|
99
|
+
status=RegistrySetupStatus[status_name],
|
|
100
|
+
visibility=RegistryVisibility[visibility_name],
|
|
101
|
+
organization_id=descriptor.organization_id or None,
|
|
102
|
+
owner_id=descriptor.owner_id or None,
|
|
103
|
+
card_id=descriptor.card_id or None,
|
|
104
|
+
module_id=descriptor.module_id or None,
|
|
105
|
+
setup_version_id=descriptor.setup_version_id or None,
|
|
106
|
+
setup_version=descriptor.setup_version or None,
|
|
107
|
+
config=dict(descriptor.config) if descriptor.config else None,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def discover_by_id(self, module_id: str) -> ModuleInfo:
|
|
111
|
+
"""Get module info by ID.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
module_id: The module identifier.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
ModuleInfo with module details.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
RegistryModuleNotFoundError: If module not found.
|
|
121
|
+
RegistryServiceError: If gRPC call fails.
|
|
122
|
+
"""
|
|
123
|
+
logger.debug("Discovering module by ID", extra={"module_id": module_id})
|
|
124
|
+
|
|
125
|
+
with self.handle_grpc_errors("GetModule", RegistryServiceError):
|
|
126
|
+
try:
|
|
127
|
+
response = self.exec_grpc_query(
|
|
128
|
+
"GetModule",
|
|
129
|
+
registry_requests_pb2.GetModuleRequest(module_id=module_id),
|
|
130
|
+
)
|
|
131
|
+
except ServerError as e:
|
|
132
|
+
msg = f"Failed to discover module '{module_id}': {e}"
|
|
133
|
+
logger.error(msg)
|
|
134
|
+
raise RegistryServiceError(msg) from e
|
|
135
|
+
|
|
136
|
+
if not response.id:
|
|
137
|
+
logger.warning("Module not found in registry", extra={"module_id": module_id})
|
|
138
|
+
raise RegistryModuleNotFoundError(module_id)
|
|
139
|
+
|
|
140
|
+
logger.debug(
|
|
141
|
+
"Module discovered",
|
|
142
|
+
extra={
|
|
143
|
+
"module_id": response.id,
|
|
144
|
+
"address": response.address,
|
|
145
|
+
"port": response.port,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
return self._proto_to_module_info(response)
|
|
149
|
+
|
|
150
|
+
def search(
|
|
151
|
+
self,
|
|
152
|
+
name: str | None = None,
|
|
153
|
+
module_type: str | None = None,
|
|
154
|
+
organization_id: str | None = None,
|
|
155
|
+
) -> list[ModuleInfo]:
|
|
156
|
+
"""Search for modules by criteria.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
name: Filter by name (partial match via query).
|
|
160
|
+
module_type: Filter by type (archetype, tool).
|
|
161
|
+
organization_id: Filter by organization.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List of matching modules.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
RegistryServiceError: If gRPC call fails.
|
|
168
|
+
"""
|
|
169
|
+
logger.debug(
|
|
170
|
+
"Searching modules",
|
|
171
|
+
extra={
|
|
172
|
+
"name": name,
|
|
173
|
+
"module_type": module_type,
|
|
174
|
+
"organization_id": organization_id,
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
with self.handle_grpc_errors("DiscoverModules", RegistryServiceError):
|
|
179
|
+
module_types = []
|
|
180
|
+
if module_type:
|
|
181
|
+
enum_val = RegistryModuleType[module_type.upper()]
|
|
182
|
+
module_types.append(getattr(registry_enums_pb2, f"MODULE_TYPE_{enum_val.name}"))
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
response = self.exec_grpc_query(
|
|
186
|
+
"DiscoverModules",
|
|
187
|
+
registry_requests_pb2.DiscoverModulesRequest(
|
|
188
|
+
query=name or "",
|
|
189
|
+
organization_id=organization_id or "",
|
|
190
|
+
module_types=module_types,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
except ServerError as e:
|
|
194
|
+
msg = f"Failed to search modules: {e}"
|
|
195
|
+
logger.error(msg)
|
|
196
|
+
raise RegistryServiceError(msg) from e
|
|
197
|
+
|
|
198
|
+
logger.debug("Search returned %d modules", len(response.modules))
|
|
199
|
+
return [self._proto_to_module_info(m) for m in response.modules]
|
|
200
|
+
|
|
201
|
+
def get_status(self, module_id: str) -> ModuleStatusInfo:
|
|
202
|
+
"""Get module status by fetching the module.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
module_id: The module identifier.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
ModuleStatusInfo with current status.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
RegistryModuleNotFoundError: If module not found.
|
|
212
|
+
RegistryServiceError: If gRPC call fails.
|
|
213
|
+
"""
|
|
214
|
+
logger.debug("Getting module status", extra={"module_id": module_id})
|
|
215
|
+
|
|
216
|
+
with self.handle_grpc_errors("GetModule", RegistryServiceError):
|
|
217
|
+
try:
|
|
218
|
+
response = self.exec_grpc_query(
|
|
219
|
+
"GetModule",
|
|
220
|
+
registry_requests_pb2.GetModuleRequest(module_id=module_id),
|
|
221
|
+
)
|
|
222
|
+
except ServerError as e:
|
|
223
|
+
msg = f"Failed to get module status for '{module_id}': {e}"
|
|
224
|
+
logger.error(msg)
|
|
225
|
+
raise RegistryServiceError(msg) from e
|
|
226
|
+
|
|
227
|
+
if not response.id:
|
|
228
|
+
logger.warning("Module not found in registry", extra={"module_id": module_id})
|
|
229
|
+
raise RegistryModuleNotFoundError(module_id)
|
|
230
|
+
|
|
231
|
+
status_name = registry_enums_pb2.ModuleStatus.Name(response.status).removeprefix("MODULE_STATUS_")
|
|
232
|
+
logger.debug(
|
|
233
|
+
"Module status retrieved",
|
|
234
|
+
extra={"module_id": response.id, "status": status_name},
|
|
235
|
+
)
|
|
236
|
+
return ModuleStatusInfo(
|
|
237
|
+
module_id=response.id,
|
|
238
|
+
status=RegistryModuleStatus[status_name],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def register(
|
|
242
|
+
self,
|
|
243
|
+
module_id: str,
|
|
244
|
+
address: str,
|
|
245
|
+
port: int,
|
|
246
|
+
version: str,
|
|
247
|
+
) -> ModuleInfo | None:
|
|
248
|
+
"""Register a module with the registry.
|
|
249
|
+
|
|
250
|
+
Note: The new proto only updates address/port/version for an existing module.
|
|
251
|
+
The module must already exist in the registry database.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
module_id: Unique module identifier.
|
|
255
|
+
address: Network address.
|
|
256
|
+
port: Network port.
|
|
257
|
+
version: Module version.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
ModuleInfo if successful, None if module not found.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
RegistryServiceError: If gRPC call fails.
|
|
264
|
+
"""
|
|
265
|
+
logger.info(
|
|
266
|
+
"Registering module with registry",
|
|
267
|
+
extra={
|
|
268
|
+
"module_id": module_id,
|
|
269
|
+
"address": address,
|
|
270
|
+
"port": port,
|
|
271
|
+
"version": version,
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
with self.handle_grpc_errors("RegisterModule", RegistryServiceError):
|
|
276
|
+
try:
|
|
277
|
+
response = self.exec_grpc_query(
|
|
278
|
+
"RegisterModule",
|
|
279
|
+
registry_requests_pb2.RegisterModuleRequest(
|
|
280
|
+
module_id=module_id,
|
|
281
|
+
address=address,
|
|
282
|
+
port=port,
|
|
283
|
+
version=version,
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
except ServerError as e:
|
|
287
|
+
msg = f"Failed to register module '{module_id}': {e}"
|
|
288
|
+
logger.error(msg)
|
|
289
|
+
raise RegistryServiceError(msg) from e
|
|
290
|
+
|
|
291
|
+
if not response.module or not response.module.id:
|
|
292
|
+
logger.warning(
|
|
293
|
+
"Registry returned empty response for module registration",
|
|
294
|
+
extra={"module_id": module_id},
|
|
295
|
+
)
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
logger.info(
|
|
299
|
+
"Module registered successfully",
|
|
300
|
+
extra={
|
|
301
|
+
"module_id": response.module.id,
|
|
302
|
+
"address": response.module.address,
|
|
303
|
+
"port": response.module.port,
|
|
304
|
+
},
|
|
305
|
+
)
|
|
306
|
+
return self._proto_to_module_info(response.module)
|
|
307
|
+
|
|
308
|
+
def heartbeat(self, module_id: str) -> RegistryModuleStatus:
|
|
309
|
+
"""Send heartbeat to keep module active.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
module_id: The module identifier.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Current module status after heartbeat.
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
RegistryServiceError: If gRPC call fails.
|
|
319
|
+
"""
|
|
320
|
+
logger.debug("Sending heartbeat", extra={"module_id": module_id})
|
|
321
|
+
|
|
322
|
+
with self.handle_grpc_errors("Heartbeat", RegistryServiceError):
|
|
323
|
+
try:
|
|
324
|
+
response = self.exec_grpc_query(
|
|
325
|
+
"Heartbeat",
|
|
326
|
+
registry_requests_pb2.HeartbeatRequest(module_id=module_id),
|
|
327
|
+
)
|
|
328
|
+
except ServerError as e:
|
|
329
|
+
msg = f"Failed to send heartbeat for '{module_id}': {e}"
|
|
330
|
+
logger.error(msg)
|
|
331
|
+
raise RegistryServiceError(msg) from e
|
|
332
|
+
|
|
333
|
+
status_name = registry_enums_pb2.ModuleStatus.Name(response.status).removeprefix("MODULE_STATUS_")
|
|
334
|
+
logger.debug(
|
|
335
|
+
"Heartbeat response",
|
|
336
|
+
extra={"module_id": module_id, "status": status_name},
|
|
337
|
+
)
|
|
338
|
+
return RegistryModuleStatus[status_name]
|
|
339
|
+
|
|
340
|
+
def get_setup(self, setup_id: str) -> SetupInfo | None:
|
|
341
|
+
"""Get setup info.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
setup_id: The setup identifier.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
SetupInfo if successful, None otherwise.
|
|
348
|
+
|
|
349
|
+
Raises:
|
|
350
|
+
RegistryServiceError: If gRPC call fails.
|
|
351
|
+
"""
|
|
352
|
+
logger.debug("Getting setup", extra={"setup_id": setup_id})
|
|
353
|
+
with self.handle_grpc_errors("GetSetup", RegistryServiceError):
|
|
354
|
+
try:
|
|
355
|
+
response = self.exec_grpc_query(
|
|
356
|
+
"GetSetup",
|
|
357
|
+
registry_requests_pb2.GetSetupRequest(setup_id=setup_id),
|
|
358
|
+
)
|
|
359
|
+
except ServerError as e:
|
|
360
|
+
msg = f"Failed to get setup '{setup_id}': {e}"
|
|
361
|
+
logger.error(msg)
|
|
362
|
+
raise RegistryServiceError(msg) from e
|
|
363
|
+
return self._proto_to_setup_info(response)
|
|
364
|
+
|
|
365
|
+
async def deregister(self, module_id: str) -> bool: # noqa: PLR6301
|
|
366
|
+
"""Deregister a module from the registry.
|
|
367
|
+
|
|
368
|
+
Note: The registry protocol uses heartbeat expiration for deregistration.
|
|
369
|
+
When a module stops sending heartbeats, it becomes inactive. This method
|
|
370
|
+
logs the deregistration intent for observability.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
module_id: The module identifier to deregister.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
True always (heartbeat expiration handles actual deregistration).
|
|
377
|
+
"""
|
|
378
|
+
logger.info(
|
|
379
|
+
"Module deregistration initiated (will become inactive via heartbeat expiration)",
|
|
380
|
+
extra={"module_id": module_id},
|
|
381
|
+
)
|
|
382
|
+
return True
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Registry data models.
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for registry service data structures.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from digitalkin.models.services.registry import RegistryModuleStatus
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ModuleStatusInfo(BaseModel):
|
|
12
|
+
"""Module status response."""
|
|
13
|
+
|
|
14
|
+
module_id: str
|
|
15
|
+
status: RegistryModuleStatus
|
|
@@ -1,14 +1,116 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Abstract base class for registry strategies."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
6
|
+
from digitalkin.models.services.registry import (
|
|
7
|
+
ModuleInfo,
|
|
8
|
+
RegistryModuleStatus,
|
|
9
|
+
SetupInfo,
|
|
10
|
+
)
|
|
5
11
|
from digitalkin.services.base_strategy import BaseStrategy
|
|
12
|
+
from digitalkin.services.registry.registry_models import ModuleStatusInfo
|
|
6
13
|
|
|
7
14
|
|
|
8
15
|
class RegistryStrategy(BaseStrategy, ABC):
|
|
9
|
-
"""Abstract base class for registry strategies.
|
|
16
|
+
"""Abstract base class for registry strategies.
|
|
17
|
+
|
|
18
|
+
Defines the interface for registry operations including module discovery,
|
|
19
|
+
registration, and status management.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
mission_id: str,
|
|
25
|
+
setup_id: str,
|
|
26
|
+
setup_version_id: str,
|
|
27
|
+
config: dict[str, Any] | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Initialize the strategy."""
|
|
30
|
+
super().__init__(mission_id, setup_id, setup_version_id)
|
|
31
|
+
self.config = config
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def discover_by_id(self, module_id: str) -> ModuleInfo:
|
|
35
|
+
"""Get module info by ID."""
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def search(
|
|
40
|
+
self,
|
|
41
|
+
name: str | None = None,
|
|
42
|
+
module_type: str | None = None,
|
|
43
|
+
organization_id: str | None = None,
|
|
44
|
+
) -> list[ModuleInfo]:
|
|
45
|
+
"""Search for modules by criteria.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
name: Filter by name (partial match via query).
|
|
49
|
+
module_type: Filter by type (archetype, tool).
|
|
50
|
+
organization_id: Filter by organization.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of matching modules.
|
|
54
|
+
"""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def get_status(self, module_id: str) -> ModuleStatusInfo:
|
|
59
|
+
"""Get module status."""
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def register(
|
|
64
|
+
self,
|
|
65
|
+
module_id: str,
|
|
66
|
+
address: str,
|
|
67
|
+
port: int,
|
|
68
|
+
version: str,
|
|
69
|
+
) -> ModuleInfo | None:
|
|
70
|
+
"""Register a module with the registry.
|
|
71
|
+
|
|
72
|
+
Note: The new proto only updates address/port/version for an existing module.
|
|
73
|
+
The module must already exist in the registry database.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
module_id: Unique module identifier.
|
|
77
|
+
address: Network address.
|
|
78
|
+
port: Network port.
|
|
79
|
+
version: Module version.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
ModuleInfo if successful, None otherwise.
|
|
83
|
+
"""
|
|
84
|
+
raise NotImplementedError
|
|
10
85
|
|
|
11
86
|
@abstractmethod
|
|
12
|
-
def
|
|
13
|
-
"""
|
|
87
|
+
def heartbeat(self, module_id: str) -> RegistryModuleStatus:
|
|
88
|
+
"""Send heartbeat to keep module active.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
module_id: The module identifier.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Current module status after heartbeat.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
RegistryModuleNotFoundError: If module not found.
|
|
98
|
+
"""
|
|
99
|
+
raise NotImplementedError
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def get_setup(self, setup_id: str) -> SetupInfo | None:
|
|
103
|
+
"""Get setup info."""
|
|
104
|
+
raise NotImplementedError
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def deregister(self, module_id: str) -> bool:
|
|
108
|
+
"""Deregister a module from the registry.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
module_id: The module identifier to deregister.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
True if deregistration was successful, False otherwise.
|
|
115
|
+
"""
|
|
14
116
|
raise NotImplementedError
|
|
@@ -5,13 +5,15 @@ from typing import Any, ClassVar
|
|
|
5
5
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
6
6
|
|
|
7
7
|
from digitalkin.services.agent import AgentStrategy, DefaultAgent
|
|
8
|
+
from digitalkin.services.communication import CommunicationStrategy, DefaultCommunication, GrpcCommunication
|
|
8
9
|
from digitalkin.services.cost import CostStrategy, DefaultCost, GrpcCost
|
|
9
10
|
from digitalkin.services.filesystem import DefaultFilesystem, FilesystemStrategy, GrpcFilesystem
|
|
10
11
|
from digitalkin.services.identity import DefaultIdentity, IdentityStrategy
|
|
11
|
-
from digitalkin.services.registry import DefaultRegistry, RegistryStrategy
|
|
12
|
+
from digitalkin.services.registry import DefaultRegistry, GrpcRegistry, RegistryStrategy
|
|
12
13
|
from digitalkin.services.services_models import ServicesMode, ServicesStrategy
|
|
13
14
|
from digitalkin.services.snapshot import DefaultSnapshot, SnapshotStrategy
|
|
14
15
|
from digitalkin.services.storage import DefaultStorage, GrpcStorage, StorageStrategy
|
|
16
|
+
from digitalkin.services.user_profile import DefaultUserProfile, GrpcUserProfile, UserProfileStrategy
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class ServicesConfig(BaseModel):
|
|
@@ -38,9 +40,9 @@ class ServicesConfig(BaseModel):
|
|
|
38
40
|
)
|
|
39
41
|
_config_snapshot: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
40
42
|
_registry: ServicesStrategy[RegistryStrategy] = PrivateAttr(
|
|
41
|
-
default_factory=lambda: ServicesStrategy(local=DefaultRegistry, remote=
|
|
43
|
+
default_factory=lambda: ServicesStrategy(local=DefaultRegistry, remote=GrpcRegistry)
|
|
42
44
|
)
|
|
43
|
-
|
|
45
|
+
_config_registry: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
44
46
|
_filesystem: ServicesStrategy[FilesystemStrategy] = PrivateAttr(
|
|
45
47
|
default_factory=lambda: ServicesStrategy(local=DefaultFilesystem, remote=GrpcFilesystem)
|
|
46
48
|
)
|
|
@@ -53,6 +55,14 @@ class ServicesConfig(BaseModel):
|
|
|
53
55
|
default_factory=lambda: ServicesStrategy(local=DefaultIdentity, remote=DefaultIdentity)
|
|
54
56
|
)
|
|
55
57
|
_config_identity: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
58
|
+
_communication: ServicesStrategy[CommunicationStrategy] = PrivateAttr(
|
|
59
|
+
default_factory=lambda: ServicesStrategy(local=DefaultCommunication, remote=GrpcCommunication)
|
|
60
|
+
)
|
|
61
|
+
_config_communication: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
62
|
+
_user_profile: ServicesStrategy[UserProfileStrategy] = PrivateAttr(
|
|
63
|
+
default_factory=lambda: ServicesStrategy(local=DefaultUserProfile, remote=GrpcUserProfile)
|
|
64
|
+
)
|
|
65
|
+
_config_user_profile: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
56
66
|
|
|
57
67
|
# List of valid strategy names for validation
|
|
58
68
|
_valid_strategy_names: ClassVar[set[str]] = {
|
|
@@ -63,6 +73,8 @@ class ServicesConfig(BaseModel):
|
|
|
63
73
|
"filesystem",
|
|
64
74
|
"agent",
|
|
65
75
|
"identity",
|
|
76
|
+
"communication",
|
|
77
|
+
"user_profile",
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
def __init__(
|
|
@@ -169,6 +181,16 @@ class ServicesConfig(BaseModel):
|
|
|
169
181
|
"""Get the identity service strategy class based on the current mode."""
|
|
170
182
|
return self._identity[self.mode.value]
|
|
171
183
|
|
|
184
|
+
@property
|
|
185
|
+
def communication(self) -> type[CommunicationStrategy]:
|
|
186
|
+
"""Get the communication service strategy class based on the current mode."""
|
|
187
|
+
return self._communication[self.mode.value]
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def user_profile(self) -> type[UserProfileStrategy]:
|
|
191
|
+
"""Get the user_profile service strategy class based on the current mode."""
|
|
192
|
+
return self._user_profile[self.mode.value]
|
|
193
|
+
|
|
172
194
|
def update_mode(self, mode: ServicesMode) -> None:
|
|
173
195
|
"""Update the strategy mode.
|
|
174
196
|
|
|
@@ -7,23 +7,27 @@ from pydantic import BaseModel
|
|
|
7
7
|
|
|
8
8
|
from digitalkin.logger import logger
|
|
9
9
|
from digitalkin.services.agent import AgentStrategy
|
|
10
|
+
from digitalkin.services.communication import CommunicationStrategy
|
|
10
11
|
from digitalkin.services.cost import CostStrategy
|
|
11
12
|
from digitalkin.services.filesystem import FilesystemStrategy
|
|
12
13
|
from digitalkin.services.identity import IdentityStrategy
|
|
13
14
|
from digitalkin.services.registry import RegistryStrategy
|
|
14
15
|
from digitalkin.services.snapshot import SnapshotStrategy
|
|
15
16
|
from digitalkin.services.storage import StorageStrategy
|
|
17
|
+
from digitalkin.services.user_profile import UserProfileStrategy
|
|
16
18
|
|
|
17
19
|
# Define type variables
|
|
18
20
|
T = TypeVar(
|
|
19
21
|
"T",
|
|
20
22
|
bound=AgentStrategy
|
|
23
|
+
| CommunicationStrategy
|
|
21
24
|
| CostStrategy
|
|
22
25
|
| FilesystemStrategy
|
|
23
26
|
| IdentityStrategy
|
|
24
27
|
| RegistryStrategy
|
|
25
28
|
| SnapshotStrategy
|
|
26
|
-
| StorageStrategy
|
|
29
|
+
| StorageStrategy
|
|
30
|
+
| UserProfileStrategy,
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
|
|
@@ -173,7 +173,7 @@ class DefaultSetup(SetupStrategy):
|
|
|
173
173
|
return [
|
|
174
174
|
value
|
|
175
175
|
for value in self.setup_versions[setup_version_dict["setup_id"]].values()
|
|
176
|
-
if setup_version_dict["query_versions"] in value.version
|
|
176
|
+
if setup_version_dict["query_versions"] in value.version
|
|
177
177
|
]
|
|
178
178
|
|
|
179
179
|
def update_setup_version(self, setup_version_dict: dict[str, Any]) -> bool:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""This module implements the default storage strategy."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from agentic_mesh_protocol.storage.v1 import data_pb2, storage_service_pb2_grpc
|
|
4
4
|
from google.protobuf import json_format
|
|
5
5
|
from google.protobuf.struct_pb2 import Struct
|
|
6
6
|
from pydantic import BaseModel
|
|
@@ -1 +1,12 @@
|
|
|
1
1
|
"""UserProfile service package."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.user_profile.default_user_profile import DefaultUserProfile
|
|
4
|
+
from digitalkin.services.user_profile.grpc_user_profile import GrpcUserProfile
|
|
5
|
+
from digitalkin.services.user_profile.user_profile_strategy import UserProfileServiceError, UserProfileStrategy
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"DefaultUserProfile",
|
|
9
|
+
"GrpcUserProfile",
|
|
10
|
+
"UserProfileServiceError",
|
|
11
|
+
"UserProfileStrategy",
|
|
12
|
+
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from agentic_mesh_protocol.user_profile.v1 import (
|
|
6
6
|
user_profile_pb2,
|
|
7
7
|
user_profile_service_pb2_grpc,
|
|
8
8
|
)
|
|
@@ -49,7 +49,7 @@ class GrpcUserProfile(UserProfileStrategy, GrpcClientWrapper, GrpcErrorHandlerMi
|
|
|
49
49
|
ServerError: If gRPC operation fails
|
|
50
50
|
"""
|
|
51
51
|
with self.handle_grpc_errors("GetUserProfile", UserProfileServiceError):
|
|
52
|
-
# mission_id
|
|
52
|
+
# mission_id typically contains user context
|
|
53
53
|
request = user_profile_pb2.GetUserProfileRequest(mission_id=self.mission_id)
|
|
54
54
|
response = self.exec_grpc_query("GetUserProfile", request)
|
|
55
55
|
|