digitalkin 0.2.23__py3-none-any.whl → 0.3.1.dev2__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 (78) hide show
  1. digitalkin/__version__.py +1 -1
  2. digitalkin/core/__init__.py +1 -0
  3. digitalkin/core/common/__init__.py +9 -0
  4. digitalkin/core/common/factories.py +156 -0
  5. digitalkin/core/job_manager/__init__.py +1 -0
  6. digitalkin/{modules → core}/job_manager/base_job_manager.py +137 -31
  7. digitalkin/core/job_manager/single_job_manager.py +354 -0
  8. digitalkin/{modules → core}/job_manager/taskiq_broker.py +116 -22
  9. digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
  10. digitalkin/core/task_manager/__init__.py +1 -0
  11. digitalkin/core/task_manager/base_task_manager.py +539 -0
  12. digitalkin/core/task_manager/local_task_manager.py +108 -0
  13. digitalkin/core/task_manager/remote_task_manager.py +87 -0
  14. digitalkin/core/task_manager/surrealdb_repository.py +266 -0
  15. digitalkin/core/task_manager/task_executor.py +249 -0
  16. digitalkin/core/task_manager/task_session.py +406 -0
  17. digitalkin/grpc_servers/__init__.py +1 -19
  18. digitalkin/grpc_servers/_base_server.py +3 -3
  19. digitalkin/grpc_servers/module_server.py +27 -43
  20. digitalkin/grpc_servers/module_servicer.py +51 -36
  21. digitalkin/grpc_servers/registry_server.py +2 -2
  22. digitalkin/grpc_servers/registry_servicer.py +4 -4
  23. digitalkin/grpc_servers/utils/__init__.py +1 -0
  24. digitalkin/grpc_servers/utils/exceptions.py +0 -8
  25. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +4 -4
  26. digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
  27. digitalkin/logger.py +73 -24
  28. digitalkin/mixins/__init__.py +19 -0
  29. digitalkin/mixins/base_mixin.py +10 -0
  30. digitalkin/mixins/callback_mixin.py +24 -0
  31. digitalkin/mixins/chat_history_mixin.py +110 -0
  32. digitalkin/mixins/cost_mixin.py +76 -0
  33. digitalkin/mixins/file_history_mixin.py +93 -0
  34. digitalkin/mixins/filesystem_mixin.py +46 -0
  35. digitalkin/mixins/logger_mixin.py +51 -0
  36. digitalkin/mixins/storage_mixin.py +79 -0
  37. digitalkin/models/core/__init__.py +1 -0
  38. digitalkin/{modules/job_manager → models/core}/job_manager_models.py +3 -3
  39. digitalkin/models/core/task_monitor.py +70 -0
  40. digitalkin/models/grpc_servers/__init__.py +1 -0
  41. digitalkin/{grpc_servers/utils → models/grpc_servers}/models.py +5 -5
  42. digitalkin/models/module/__init__.py +2 -0
  43. digitalkin/models/module/module.py +9 -1
  44. digitalkin/models/module/module_context.py +122 -6
  45. digitalkin/models/module/module_types.py +307 -19
  46. digitalkin/models/services/__init__.py +9 -0
  47. digitalkin/models/services/cost.py +1 -0
  48. digitalkin/models/services/storage.py +39 -5
  49. digitalkin/modules/_base_module.py +123 -118
  50. digitalkin/modules/tool_module.py +10 -2
  51. digitalkin/modules/trigger_handler.py +7 -6
  52. digitalkin/services/cost/__init__.py +9 -2
  53. digitalkin/services/cost/grpc_cost.py +9 -42
  54. digitalkin/services/filesystem/default_filesystem.py +0 -2
  55. digitalkin/services/filesystem/grpc_filesystem.py +10 -39
  56. digitalkin/services/setup/default_setup.py +5 -6
  57. digitalkin/services/setup/grpc_setup.py +52 -15
  58. digitalkin/services/storage/grpc_storage.py +4 -4
  59. digitalkin/services/user_profile/__init__.py +1 -0
  60. digitalkin/services/user_profile/default_user_profile.py +55 -0
  61. digitalkin/services/user_profile/grpc_user_profile.py +69 -0
  62. digitalkin/services/user_profile/user_profile_strategy.py +40 -0
  63. digitalkin/utils/__init__.py +28 -0
  64. digitalkin/utils/arg_parser.py +1 -1
  65. digitalkin/utils/development_mode_action.py +2 -2
  66. digitalkin/utils/dynamic_schema.py +483 -0
  67. digitalkin/utils/package_discover.py +1 -2
  68. {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/METADATA +11 -30
  69. digitalkin-0.3.1.dev2.dist-info/RECORD +119 -0
  70. modules/dynamic_setup_module.py +362 -0
  71. digitalkin/grpc_servers/utils/factory.py +0 -180
  72. digitalkin/modules/job_manager/single_job_manager.py +0 -294
  73. digitalkin/modules/job_manager/taskiq_job_manager.py +0 -290
  74. digitalkin-0.2.23.dist-info/RECORD +0 -89
  75. /digitalkin/{grpc_servers/utils → models/grpc_servers}/types.py +0 -0
  76. {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/WHEEL +0 -0
  77. {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/licenses/LICENSE +0 -0
  78. {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,11 @@
1
1
  """BaseModule is the abstract base for all modules in the DigitalKin SDK."""
2
2
 
3
3
  import asyncio
4
- import contextlib
5
4
  import json
6
5
  from abc import ABC, abstractmethod
7
6
  from collections.abc import Callable, Coroutine
8
7
  from typing import Any, ClassVar, Generic
9
8
 
10
- from pydantic import BaseModel
11
-
12
9
  from digitalkin.logger import logger
13
10
  from digitalkin.models.module import (
14
11
  InputModelT,
@@ -17,28 +14,14 @@ from digitalkin.models.module import (
17
14
  SecretModelT,
18
15
  SetupModelT,
19
16
  )
17
+ from digitalkin.models.module.module import ModuleCodeModel
20
18
  from digitalkin.models.module.module_context import ModuleContext
21
19
  from digitalkin.modules.trigger_handler import TriggerHandler
22
- from digitalkin.services.agent.agent_strategy import AgentStrategy
23
- from digitalkin.services.cost.cost_strategy import CostStrategy
24
- from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
25
- from digitalkin.services.identity.identity_strategy import IdentityStrategy
26
- from digitalkin.services.registry.registry_strategy import RegistryStrategy
27
20
  from digitalkin.services.services_config import ServicesConfig, ServicesStrategy
28
- from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
29
- from digitalkin.services.storage.storage_strategy import StorageStrategy
30
21
  from digitalkin.utils.llm_ready_schema import llm_ready_schema
31
22
  from digitalkin.utils.package_discover import ModuleDiscoverer
32
23
 
33
24
 
34
- class ModuleCodeModel(BaseModel):
35
- """typed error/code model."""
36
-
37
- code: str
38
- message: str
39
- short_description: str
40
-
41
-
42
25
  class BaseModule( # noqa: PLR0904
43
26
  ABC,
44
27
  Generic[
@@ -67,33 +50,29 @@ class BaseModule( # noqa: PLR0904
67
50
  services_config_params: ClassVar[dict[str, dict[str, Any | None] | None]]
68
51
  services_config: ServicesConfig
69
52
 
70
- # services list
71
- agent: AgentStrategy
72
- cost: CostStrategy
73
- filesystem: FilesystemStrategy
74
- identity: IdentityStrategy
75
- registry: RegistryStrategy
76
- snapshot: SnapshotStrategy
77
- storage: StorageStrategy
78
-
79
- # runtime params
80
- job_id: str
81
- mission_id: str
82
- setup_id: str
83
- setup_version_id: str
84
- _status: ModuleStatus
85
- _task: asyncio.Task | None
86
-
87
- def _init_strategies(self) -> None:
88
- """Initialize the services configuration."""
89
- for service_name in self.services_config.valid_strategy_names():
90
- service = self.services_config.init_strategy(
53
+ def _init_strategies(self, mission_id: str, setup_id: str, setup_version_id: str) -> dict[str, Any]:
54
+ """Initialize the services configuration.
55
+
56
+ Returns:
57
+ dict of services with name: Strategy
58
+ agent: AgentStrategy
59
+ cost: CostStrategy
60
+ filesystem: FilesystemStrategy
61
+ identity: IdentityStrategy
62
+ registry: RegistryStrategy
63
+ snapshot: SnapshotStrategy
64
+ storage: StorageStrategy
65
+ """
66
+ logger.debug("Service initialisation: %s", self.services_config_strategies.keys())
67
+ return {
68
+ service_name: self.services_config.init_strategy(
91
69
  service_name,
92
- self.mission_id,
93
- self.setup_id,
94
- self.setup_version_id,
70
+ mission_id,
71
+ setup_id,
72
+ setup_version_id,
95
73
  )
96
- setattr(self, service_name, service)
74
+ for service_name in self.services_config.valid_strategy_names()
75
+ }
97
76
 
98
77
  def __init__(
99
78
  self,
@@ -103,22 +82,19 @@ class BaseModule( # noqa: PLR0904
103
82
  setup_version_id: str,
104
83
  ) -> None:
105
84
  """Initialize the module."""
106
- self.job_id: str = job_id
107
- self.mission_id: str = mission_id
108
- # Setup reference needed for the overall Kin scope as the filesystem context
109
- self.setup_id: str = setup_id
110
- # SetupVersion reference needed for the precise Kin scope as the cost
111
- self.setup_version_id: str = setup_version_id
112
85
  self._status = ModuleStatus.CREATED
113
- self._task: asyncio.Task | None = None
114
- # Initialize services configuration
115
- self._init_strategies()
116
86
 
117
87
  # Initialize minimum context
118
88
  self.context = ModuleContext(
119
- storage=self.storage,
120
- cost=self.cost,
121
- filesystem=self.filesystem,
89
+ # Initialize services configuration
90
+ **self._init_strategies(mission_id, setup_id, setup_version_id),
91
+ session={
92
+ "setup_id": setup_id,
93
+ "mission_id": mission_id,
94
+ "setup_version_id": setup_version_id,
95
+ "job_id": job_id,
96
+ },
97
+ callbacks={"logger": logger},
122
98
  )
123
99
 
124
100
  @property
@@ -131,14 +107,18 @@ class BaseModule( # noqa: PLR0904
131
107
  return self._status
132
108
 
133
109
  @classmethod
134
- def get_secret_format(cls, *, llm_format: bool) -> str:
110
+ async def get_secret_format(cls, *, llm_format: bool) -> str:
135
111
  """Get the JSON schema of the secret format model.
136
112
 
137
- Raises:
138
- NotImplementedError: If the `secret_format` is not defined.
113
+ Args:
114
+ llm_format: If True, return LLM-optimized schema format with inlined
115
+ references and simplified structure.
139
116
 
140
117
  Returns:
141
- The JSON schema of the secret format as a string.
118
+ The JSON schema of the secret format as a JSON string.
119
+
120
+ Raises:
121
+ NotImplementedError: If the `secret_format` class attribute is not defined.
142
122
  """
143
123
  if cls.secret_format is not None:
144
124
  if llm_format:
@@ -148,14 +128,18 @@ class BaseModule( # noqa: PLR0904
148
128
  raise NotImplementedError(msg)
149
129
 
150
130
  @classmethod
151
- def get_input_format(cls, *, llm_format: bool) -> str:
131
+ async def get_input_format(cls, *, llm_format: bool) -> str:
152
132
  """Get the JSON schema of the input format model.
153
133
 
154
- Raises:
155
- NotImplementedError: If the `input_format` is not defined.
134
+ Args:
135
+ llm_format: If True, return LLM-optimized schema format with inlined
136
+ references and simplified structure.
156
137
 
157
138
  Returns:
158
- The JSON schema of the input format as a string.
139
+ The JSON schema of the input format as a JSON string.
140
+
141
+ Raises:
142
+ NotImplementedError: If the `input_format` class attribute is not defined.
159
143
  """
160
144
  if cls.input_format is not None:
161
145
  if llm_format:
@@ -165,14 +149,18 @@ class BaseModule( # noqa: PLR0904
165
149
  raise NotImplementedError(msg)
166
150
 
167
151
  @classmethod
168
- def get_output_format(cls, *, llm_format: bool) -> str:
152
+ async def get_output_format(cls, *, llm_format: bool) -> str:
169
153
  """Get the JSON schema of the output format model.
170
154
 
171
- Raises:
172
- NotImplementedError: If the `output_format` is not defined.
155
+ Args:
156
+ llm_format: If True, return LLM-optimized schema format with inlined
157
+ references and simplified structure.
173
158
 
174
159
  Returns:
175
- The JSON schema of the output format as a string.
160
+ The JSON schema of the output format as a JSON string.
161
+
162
+ Raises:
163
+ NotImplementedError: If the `output_format` class attribute is not defined.
176
164
  """
177
165
  if cls.output_format is not None:
178
166
  if llm_format:
@@ -182,20 +170,29 @@ class BaseModule( # noqa: PLR0904
182
170
  raise NotImplementedError(msg)
183
171
 
184
172
  @classmethod
185
- def get_config_setup_format(cls, *, llm_format: bool) -> str:
173
+ async def get_config_setup_format(cls, *, llm_format: bool) -> str:
186
174
  """Gets the JSON schema of the config setup format model.
187
175
 
188
- The config setup format is used only to initialize the module with configuration data.
189
- The setup format is used to initialize an run the module with setup data.
176
+ The config setup format is used only to initialize the module with configuration
177
+ data. It includes fields marked with `json_schema_extra={"config": True}` and
178
+ excludes hidden runtime fields.
190
179
 
191
- Raises:
192
- NotImplementedError: If the `setup_format` is not defined.
180
+ Dynamic schema fields are always resolved when generating the schema, as this
181
+ method is typically called during module discovery or schema generation where
182
+ fresh values are needed.
183
+
184
+ Args:
185
+ llm_format: If True, return LLM-optimized schema format with inlined
186
+ references and simplified structure.
193
187
 
194
188
  Returns:
195
- The JSON schema of the config setup format as a string.
189
+ The JSON schema of the config setup format as a JSON string.
190
+
191
+ Raises:
192
+ NotImplementedError: If the `setup_format` class attribute is not defined.
196
193
  """
197
194
  if cls.setup_format is not None:
198
- setup_format = cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False)
195
+ setup_format = await cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False, force=True)
199
196
  if llm_format:
200
197
  return json.dumps(llm_ready_schema(setup_format), indent=2)
201
198
  return json.dumps(setup_format.model_json_schema(), indent=2)
@@ -203,17 +200,28 @@ class BaseModule( # noqa: PLR0904
203
200
  raise NotImplementedError(msg)
204
201
 
205
202
  @classmethod
206
- def get_setup_format(cls, *, llm_format: bool) -> str:
203
+ async def get_setup_format(cls, *, llm_format: bool) -> str:
207
204
  """Gets the JSON schema of the setup format model.
208
205
 
209
- Raises:
210
- NotImplementedError: If the `setup_format` is not defined.
206
+ The setup format is used at runtime and includes hidden fields but excludes
207
+ config-only fields. This is the schema used when running the module.
208
+
209
+ Dynamic schema fields are always resolved when generating the schema, as this
210
+ method is typically called during module discovery or schema generation where
211
+ fresh values are needed.
212
+
213
+ Args:
214
+ llm_format: If True, return LLM-optimized schema format with inlined
215
+ references and simplified structure.
211
216
 
212
217
  Returns:
213
- The JSON schema of the setup format as a string.
218
+ The JSON schema of the setup format as a JSON string.
219
+
220
+ Raises:
221
+ NotImplementedError: If the `setup_format` class attribute is not defined.
214
222
  """
215
223
  if cls.setup_format is not None:
216
- setup_format = cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True)
224
+ setup_format = await cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True, force=True)
217
225
  if llm_format:
218
226
  return json.dumps(llm_ready_schema(setup_format), indent=2)
219
227
  return json.dumps(setup_format.model_json_schema(), indent=2)
@@ -245,17 +253,22 @@ class BaseModule( # noqa: PLR0904
245
253
  return cls.input_format(**input_data)
246
254
 
247
255
  @classmethod
248
- def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
256
+ async def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
249
257
  """Create the setup model from the setup data.
250
258
 
259
+ Creates a filtered setup model instance based on the provided data.
260
+ Uses `get_clean_model()` internally to get the appropriate model class
261
+ with field filtering applied.
262
+
251
263
  Args:
252
264
  setup_data: The setup data to create the model from.
253
265
  config_fields: If True, include only fields with json_schema_extra["config"] == True.
254
266
 
255
267
  Returns:
256
- The setup model.
268
+ An instance of the setup model with the provided data.
257
269
  """
258
- return cls.setup_format.get_clean_model(config_fields=config_fields, hidden_fields=True)(**setup_data)
270
+ model_cls = await cls.setup_format.get_clean_model(config_fields=config_fields, hidden_fields=True)
271
+ return model_cls(**setup_data)
259
272
 
260
273
  @classmethod
261
274
  def create_secret_model(cls, secret_data: dict[str, Any]) -> SecretModelT:
@@ -307,7 +320,11 @@ class BaseModule( # noqa: PLR0904
307
320
  """
308
321
  return cls.triggers_discoverer.register_trigger(handler_cls)
309
322
 
310
- async def run_config_setup(self, config_setup_data: SetupModelT) -> SetupModelT: # noqa: PLR6301
323
+ async def run_config_setup( # noqa: PLR6301
324
+ self,
325
+ context: ModuleContext, # noqa: ARG002
326
+ config_setup_data: SetupModelT,
327
+ ) -> SetupModelT:
311
328
  """Run config setup the module.
312
329
 
313
330
  The config setup is used to initialize the setup with configuration data.
@@ -323,7 +340,7 @@ class BaseModule( # noqa: PLR0904
323
340
  return config_setup_data
324
341
 
325
342
  @abstractmethod
326
- async def initialize(self, setup_data: SetupModelT) -> None:
343
+ async def initialize(self, context: ModuleContext, setup_data: SetupModelT) -> None:
327
344
  """Initialize the module."""
328
345
  raise NotImplementedError
329
346
 
@@ -331,7 +348,6 @@ class BaseModule( # noqa: PLR0904
331
348
  self,
332
349
  input_data: InputModelT,
333
350
  setup_data: SetupModelT,
334
- callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
335
351
  ) -> None:
336
352
  """Run the module with the given input and setup data.
337
353
 
@@ -346,7 +362,6 @@ class BaseModule( # noqa: PLR0904
346
362
  Args:
347
363
  input_data (InputModelT): The input data to be processed by the module.
348
364
  setup_data (SetupModelT): The setup or configuration data required for the module.
349
- callback (Callable[[OutputModelT], Coroutine[Any, Any, None]]): callback to be invoked to stream any result.
350
365
 
351
366
  Raises:
352
367
  ValueError: If no handler for the protocol is found.
@@ -360,7 +375,6 @@ class BaseModule( # noqa: PLR0904
360
375
  await handler_instance.handle(
361
376
  input_instance.root,
362
377
  setup_data,
363
- callback,
364
378
  self.context,
365
379
  )
366
380
 
@@ -373,7 +387,6 @@ class BaseModule( # noqa: PLR0904
373
387
  self,
374
388
  input_data: InputModelT,
375
389
  setup_data: SetupModelT,
376
- callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
377
390
  ) -> None:
378
391
  """Run the module lifecycle.
379
392
 
@@ -381,19 +394,17 @@ class BaseModule( # noqa: PLR0904
381
394
  asyncio.CancelledError: If the module is cancelled
382
395
  """
383
396
  try:
384
- logger.info("Starting module %s", self.name)
385
- await self.run(input_data, setup_data, callback)
386
- logger.info("Module %s finished", self.name)
397
+ logger.info("Starting module %s", self.name, extra=self.context.session.current_ids())
398
+ await self.run(input_data, setup_data)
399
+ logger.info("Module %s finished", self.name, extra=self.context.session.current_ids())
387
400
  except asyncio.CancelledError:
388
401
  self._status = ModuleStatus.CANCELLED
389
- logger.error(f"Module {self.name} cancelled")
402
+ logger.error("Module %s cancelled", self.name, extra=self.context.session.current_ids())
390
403
  except Exception:
391
404
  self._status = ModuleStatus.FAILED
392
- logger.exception("Error inside module %s", self.name)
405
+ logger.exception("Error inside module %s", self.name, extra=self.context.session.current_ids())
393
406
  else:
394
407
  self._status = ModuleStatus.STOPPING
395
- finally:
396
- await self.stop()
397
408
 
398
409
  async def start(
399
410
  self,
@@ -404,15 +415,16 @@ class BaseModule( # noqa: PLR0904
404
415
  ) -> None:
405
416
  """Start the module."""
406
417
  try:
407
- logger.debug("Inititalize module")
408
- await self.initialize(setup_data=setup_data)
418
+ self.context.callbacks.send_message = callback
419
+ logger.info(f"Inititalize module {self.context.session.job_id}")
420
+ await self.initialize(self.context, setup_data)
409
421
  except Exception as e:
410
422
  self._status = ModuleStatus.FAILED
411
423
  short_description = "Error initializing module"
412
424
  logger.exception("%s: %s", short_description, e)
413
425
  await callback(
414
426
  ModuleCodeModel(
415
- code=str(self._status),
427
+ code="Error",
416
428
  short_description=short_description,
417
429
  message=str(e),
418
430
  )
@@ -425,32 +437,22 @@ class BaseModule( # noqa: PLR0904
425
437
  try:
426
438
  logger.debug("Init the discovered input handlers.")
427
439
  self.triggers_discoverer.init_handlers(self.context)
428
- logger.debug("Run lifecycle")
429
- self._status = ModuleStatus.RUNNING
430
- self._task = asyncio.create_task(
431
- self._run_lifecycle(input_data, setup_data, callback),
432
- name="module_lifecycle",
433
- )
434
- if done_callback is not None:
435
- self._task.add_done_callback(done_callback)
440
+ logger.debug(f"Run lifecycle {self.context.session.job_id}")
441
+ await self._run_lifecycle(input_data, setup_data)
436
442
  except Exception:
437
443
  self._status = ModuleStatus.FAILED
438
444
  logger.exception("Error during module lifecyle")
445
+ finally:
446
+ await self.stop()
439
447
 
440
448
  async def stop(self) -> None:
441
449
  """Stop the module."""
442
- logger.info("Stopping module %s with status %s", self.name, self._status)
443
- if self._status not in {ModuleStatus.RUNNING, ModuleStatus.STOPPING}:
444
- return
445
-
450
+ logger.info("Stopping module %s | job_id=%s", self.name, self.context.session.job_id)
446
451
  try:
447
452
  self._status = ModuleStatus.STOPPING
448
- if self._task and not self._task.done():
449
- self._task.cancel()
450
- with contextlib.suppress(asyncio.CancelledError):
451
- await self._task
452
453
  logger.debug("Module %s stopped", self.name)
453
454
  await self.cleanup()
455
+ await self.context.callbacks.send_message(ModuleCodeModel(code="__END_OF_STREAM__"))
454
456
  self._status = ModuleStatus.STOPPED
455
457
  logger.debug("Module %s cleaned", self.name)
456
458
  except Exception:
@@ -464,14 +466,17 @@ class BaseModule( # noqa: PLR0904
464
466
  ) -> None:
465
467
  """Start the module."""
466
468
  try:
467
- logger.info("Run Config Setup lifecycle")
469
+ logger.info("Run Config Setup lifecycle", extra=self.context.session.current_ids())
468
470
  self._status = ModuleStatus.RUNNING
469
- content = await self.run_config_setup(config_setup_data)
471
+ self.context.callbacks.set_config_setup = callback
472
+ content = await self.run_config_setup(self.context, config_setup_data)
470
473
 
471
474
  wrapper = config_setup_data.model_dump()
472
475
  wrapper["content"] = content.model_dump()
473
- await callback(self.create_setup_model(wrapper))
476
+ setup_model = await self.create_setup_model(wrapper)
477
+ await callback(setup_model)
474
478
  self._status = ModuleStatus.STOPPING
475
479
  except Exception:
480
+ logger.error("Error during module lifecyle")
476
481
  self._status = ModuleStatus.FAILED
477
- logger.exception("Error during module lifecyle")
482
+ logger.exception("Error during module lifecyle", extra=self.context.session.current_ids())
@@ -3,8 +3,16 @@
3
3
  from abc import ABC
4
4
 
5
5
  from digitalkin.models.module import InputModelT, OutputModelT, SecretModelT, SetupModelT
6
- from digitalkin.modules._base_module import BaseModule # type: ignore
6
+ from digitalkin.modules._base_module import BaseModule # type: ignore
7
7
 
8
8
 
9
- class ToolModule(BaseModule[InputModelT, OutputModelT, SetupModelT, SecretModelT,], ABC):
9
+ class ToolModule(
10
+ BaseModule[
11
+ InputModelT,
12
+ OutputModelT,
13
+ SetupModelT,
14
+ SecretModelT,
15
+ ],
16
+ ABC,
17
+ ):
10
18
  """ToolModule extends BaseModule to implement specific module types."""
@@ -1,14 +1,14 @@
1
1
  """Definition of the Trigger type."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from collections.abc import Callable, Coroutine
5
- from typing import Any, ClassVar, Generic
4
+ from typing import ClassVar, Generic
6
5
 
6
+ from digitalkin.mixins import BaseMixin
7
+ from digitalkin.models.module.module_context import ModuleContext
7
8
  from digitalkin.models.module.module_types import InputModelT, OutputModelT, SetupModelT
8
- from digitalkin.modules._base_module import ModuleContext
9
9
 
10
10
 
11
- class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
11
+ class TriggerHandler(ABC, BaseMixin, Generic[InputModelT, SetupModelT, OutputModelT]):
12
12
  """Base class for all input-trigger handlers.
13
13
 
14
14
  Each handler declares:
@@ -28,7 +28,6 @@ class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
28
28
  self,
29
29
  input_data: InputModelT,
30
30
  setup_data: SetupModelT,
31
- callback: Callable[[Any], Coroutine[Any, Any, None]],
32
31
  context: ModuleContext,
33
32
  ) -> None:
34
33
  """Asynchronously processes the input data specific to Handler and streams results via the provided callback.
@@ -36,12 +35,14 @@ class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
36
35
  Args:
37
36
  input_data (InputModelT): The input data to be processed by the handler.
38
37
  setup_data (SetupModelT): The setup or configuration data required for processing.
39
- callback (Callable[[Any], Coroutine[Any, Any, None]]): callback that stream results.
40
38
  context (ModuleContext): The context object containing module-specific information and resources.
41
39
 
42
40
  Returns:
43
41
  Any: The result of the processing, if applicable.
44
42
 
45
43
  Note:
44
+ self.send_message: : callback used to stream results.
45
+ (Callable[[OutputMdodelT], Coroutine[Any, Any, None]])
46
+
46
47
  The callback must be awaited to ensure results are streamed correctly during processing.
47
48
  """
@@ -1,7 +1,14 @@
1
1
  """This module is responsible for handling the cost services."""
2
2
 
3
- from digitalkin.services.cost.cost_strategy import CostStrategy
3
+ from digitalkin.services.cost.cost_strategy import CostConfig, CostData, CostStrategy, CostType
4
4
  from digitalkin.services.cost.default_cost import DefaultCost
5
5
  from digitalkin.services.cost.grpc_cost import GrpcCost
6
6
 
7
- __all__ = ["CostStrategy", "DefaultCost", "GrpcCost"]
7
+ __all__ = [
8
+ "CostConfig",
9
+ "CostData",
10
+ "CostStrategy",
11
+ "CostType",
12
+ "DefaultCost",
13
+ "GrpcCost",
14
+ ]
@@ -1,16 +1,14 @@
1
1
  """This module implements the gRPC Cost strategy."""
2
2
 
3
- from collections.abc import Generator
4
- from contextlib import contextmanager
5
- from typing import Any, Literal
3
+ from typing import Literal
6
4
 
7
- from digitalkin_proto.digitalkin.cost.v1 import cost_pb2, cost_service_pb2_grpc
5
+ from digitalkin_proto.agentic_mesh_protocol.cost.v1 import cost_pb2, cost_service_pb2_grpc
8
6
  from google.protobuf import json_format
9
7
 
10
- from digitalkin.grpc_servers.utils.exceptions import ServerError
11
8
  from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
12
- from digitalkin.grpc_servers.utils.models import ClientConfig
9
+ from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
13
10
  from digitalkin.logger import logger
11
+ from digitalkin.models.grpc_servers.models import ClientConfig
14
12
  from digitalkin.services.cost.cost_strategy import (
15
13
  CostConfig,
16
14
  CostData,
@@ -20,40 +18,9 @@ from digitalkin.services.cost.cost_strategy import (
20
18
  )
21
19
 
22
20
 
23
- class GrpcCost(CostStrategy, GrpcClientWrapper):
21
+ class GrpcCost(CostStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
24
22
  """This class implements the default Cost strategy."""
25
23
 
26
- @staticmethod
27
- @contextmanager
28
- def _handle_grpc_errors(operation: str) -> Generator[Any, Any, Any]:
29
- """Context manager for consistent gRPC error handling.
30
-
31
- Yields:
32
- Allow error handling in context.
33
-
34
- Args:
35
- operation: Description of the operation being performed.
36
-
37
- Raises:
38
- ValueError: Error with the model validation.
39
- ServerError: from gRPC Client.
40
- CostServiceError: Unexpected error.
41
- """
42
- try:
43
- yield
44
- except CostServiceError as e:
45
- msg = f"CostServiceError in {operation}: {e}"
46
- logger.exception(msg)
47
- raise CostServiceError(msg) from e
48
- except ServerError as e:
49
- msg = f"gRPC {operation} failed: {e}"
50
- logger.exception(msg)
51
- raise ServerError(msg) from e
52
- except Exception as e:
53
- msg = f"Unexpected error in {operation}"
54
- logger.exception(msg)
55
- raise CostServiceError(msg) from e
56
-
57
24
  def __init__(
58
25
  self,
59
26
  mission_id: str,
@@ -66,7 +33,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
66
33
  super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
67
34
  channel = self._init_channel(client_config)
68
35
  self.stub = cost_service_pb2_grpc.CostServiceStub(channel)
69
- logger.debug("Channel client 'Cost' initialized succesfully")
36
+ logger.debug("Channel client 'Cost' initialized successfully")
70
37
 
71
38
  def add(
72
39
  self,
@@ -84,7 +51,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
84
51
  Raises:
85
52
  CostServiceError: If the cost config is invalid
86
53
  """
87
- with self._handle_grpc_errors("AddCost"):
54
+ with self.handle_grpc_errors("AddCost", CostServiceError):
88
55
  cost_config = self.config.get(cost_config_name)
89
56
  if cost_config is None:
90
57
  msg = f"Cost config {cost_config_name} not found in the configuration."
@@ -122,7 +89,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
122
89
  Returns:
123
90
  CostData: The cost data
124
91
  """
125
- with self._handle_grpc_errors("GetCost"):
92
+ with self.handle_grpc_errors("GetCost", CostServiceError):
126
93
  request = cost_pb2.GetCostRequest(name=name, mission_id=self.mission_id)
127
94
  response: cost_pb2.GetCostResponse = self.exec_grpc_query("GetCost", request)
128
95
  cost_data_list = [
@@ -150,7 +117,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
150
117
  Returns:
151
118
  list[CostData]: The cost data
152
119
  """
153
- with self._handle_grpc_errors("GetCosts"):
120
+ with self.handle_grpc_errors("GetCosts", CostServiceError):
154
121
  request = cost_pb2.GetCostsRequest(
155
122
  mission_id=self.mission_id,
156
123
  filter=cost_pb2.CostFilter(
@@ -198,8 +198,6 @@ class DefaultFilesystem(FilesystemStrategy):
198
198
  end_idx = start_idx + list_size
199
199
  paginated_files = filtered_files[start_idx:end_idx]
200
200
 
201
- logger.critical(f"{filters=} | {paginated_files=}")
202
-
203
201
  if include_content:
204
202
  for file in paginated_files:
205
203
  file.content = Path(file.storage_uri).read_bytes()