digitalkin 0.2.25rc1__py3-none-any.whl → 0.3.0__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 (39) hide show
  1. digitalkin/__version__.py +1 -1
  2. digitalkin/grpc_servers/_base_server.py +1 -1
  3. digitalkin/grpc_servers/module_server.py +26 -42
  4. digitalkin/grpc_servers/module_servicer.py +30 -24
  5. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +3 -3
  6. digitalkin/grpc_servers/utils/models.py +1 -1
  7. digitalkin/logger.py +60 -23
  8. digitalkin/mixins/__init__.py +19 -0
  9. digitalkin/mixins/base_mixin.py +10 -0
  10. digitalkin/mixins/callback_mixin.py +24 -0
  11. digitalkin/mixins/chat_history_mixin.py +108 -0
  12. digitalkin/mixins/cost_mixin.py +76 -0
  13. digitalkin/mixins/file_history_mixin.py +99 -0
  14. digitalkin/mixins/filesystem_mixin.py +47 -0
  15. digitalkin/mixins/logger_mixin.py +59 -0
  16. digitalkin/mixins/storage_mixin.py +79 -0
  17. digitalkin/models/module/__init__.py +2 -0
  18. digitalkin/models/module/module.py +9 -1
  19. digitalkin/models/module/module_context.py +90 -6
  20. digitalkin/models/module/module_types.py +5 -5
  21. digitalkin/models/module/task_monitor.py +51 -0
  22. digitalkin/models/services/__init__.py +9 -0
  23. digitalkin/models/services/storage.py +39 -5
  24. digitalkin/modules/_base_module.py +105 -74
  25. digitalkin/modules/job_manager/base_job_manager.py +12 -8
  26. digitalkin/modules/job_manager/single_job_manager.py +84 -78
  27. digitalkin/modules/job_manager/surrealdb_repository.py +225 -0
  28. digitalkin/modules/job_manager/task_manager.py +391 -0
  29. digitalkin/modules/job_manager/task_session.py +276 -0
  30. digitalkin/modules/job_manager/taskiq_job_manager.py +2 -2
  31. digitalkin/modules/tool_module.py +10 -2
  32. digitalkin/modules/trigger_handler.py +7 -6
  33. digitalkin/services/cost/__init__.py +9 -2
  34. digitalkin/services/storage/grpc_storage.py +1 -1
  35. {digitalkin-0.2.25rc1.dist-info → digitalkin-0.3.0.dist-info}/METADATA +18 -18
  36. {digitalkin-0.2.25rc1.dist-info → digitalkin-0.3.0.dist-info}/RECORD +39 -26
  37. {digitalkin-0.2.25rc1.dist-info → digitalkin-0.3.0.dist-info}/WHEEL +0 -0
  38. {digitalkin-0.2.25rc1.dist-info → digitalkin-0.3.0.dist-info}/licenses/LICENSE +0 -0
  39. {digitalkin-0.2.25rc1.dist-info → digitalkin-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,99 @@
1
+ """Context mixins providing ergonomic access to service strategies.
2
+
3
+ This module provides mixins that wrap service strategy calls with cleaner APIs,
4
+ following Django/FastAPI patterns where context is passed explicitly to each method.
5
+ """
6
+
7
+ from digitalkin.mixins.logger_mixin import LoggerMixin
8
+ from digitalkin.mixins.storage_mixin import StorageMixin
9
+ from digitalkin.models.module.module_context import ModuleContext
10
+ from digitalkin.models.services.storage import FileHistory, FileModel
11
+
12
+
13
+ class FileHistoryMixin(StorageMixin, LoggerMixin):
14
+ """Mixin providing File history operations through storage strategy.
15
+
16
+ This mixin provides a higher-level API for managing File history,
17
+ using the storage strategy as the underlying persistence mechanism.
18
+ """
19
+
20
+ file_history_front: FileHistory = FileHistory(files=[])
21
+ FILE_HISTORY_COLLECTION = "file_history"
22
+ FILE_HISTORY_RECORD_ID = "full_file_history"
23
+
24
+ def _get_history_key(self, context: ModuleContext) -> str:
25
+ """Get session-specific history key.
26
+
27
+ Args:
28
+ context: Module context containing session information
29
+
30
+ Returns:
31
+ Unique history key for the current session
32
+ """
33
+ mission_id = getattr(context.session, "mission_id", None) or "default"
34
+ return f"{self.FILE_HISTORY_RECORD_ID}_{mission_id}"
35
+
36
+ def load_file_history(self, context: ModuleContext) -> FileHistory:
37
+ """Load File history for the current session.
38
+
39
+ Args:
40
+ context: Module context containing storage strategy
41
+
42
+ Returns:
43
+ File history object, empty if none exists or loading fails
44
+ """
45
+ history_key = self._get_history_key(context)
46
+
47
+ if self.file_history_front is None:
48
+ try:
49
+ record = self.read_storage(
50
+ context,
51
+ self.FILE_HISTORY_COLLECTION,
52
+ history_key,
53
+ )
54
+ if record and record.data:
55
+ return FileHistory.model_validate(record.data)
56
+ except Exception as e:
57
+ self.log_warning(context, f"Failed to load File history: {e}")
58
+ return self.file_history_front
59
+
60
+ def append_files_history(self, context: ModuleContext, files: list[FileModel]) -> None:
61
+ """Append a message to File history.
62
+
63
+ Args:
64
+ context: Module context containing storage strategy
65
+ role: Message role (user, assistant, system)
66
+ files: list of files model
67
+
68
+ Raises:
69
+ StorageServiceError: If history update fails
70
+ """
71
+ history_key = self._get_history_key(context)
72
+
73
+ try:
74
+ if not self.file_history_front.files:
75
+ self.file_history_front = self.load_file_history(context)
76
+ self.file_history_front.files.extend(files)
77
+ except Exception as e:
78
+ self.log_error(context, f"Failed to append message to File history: {e}")
79
+
80
+ try:
81
+ self.log_debug(context, f"Updating File history for session: {history_key}")
82
+ self.update_storage(
83
+ context,
84
+ self.FILE_HISTORY_COLLECTION,
85
+ history_key,
86
+ self.file_history_front.model_dump(),
87
+ )
88
+ except Exception as e:
89
+ self.log_error(context, f"Updating File history for session: {history_key} with error: {e}")
90
+
91
+ # Create new record
92
+ self.log_debug(context, f"Creating new File history for session: {history_key}")
93
+ self.store_storage(
94
+ context,
95
+ self.FILE_HISTORY_COLLECTION,
96
+ history_key,
97
+ self.file_history_front.model_dump(),
98
+ data_type="OUTPUT",
99
+ )
@@ -0,0 +1,47 @@
1
+ """Filesystem Mixin to ease filesystem use."""
2
+
3
+ from typing import Any
4
+
5
+ from digitalkin.models.module.module_context import ModuleContext
6
+ from digitalkin.services.filesystem.filesystem_strategy import FilesystemRecord
7
+
8
+
9
+ class FilesystemMixin:
10
+ """Mixin providing filesystem operations through the filesystem strategy.
11
+
12
+ This mixin wraps filesystem strategy calls to provide a cleaner API
13
+ for file operations in trigger handlers.
14
+ """
15
+
16
+ @staticmethod
17
+ def upload_files(context: ModuleContext, files: list[Any]) -> tuple[list[FilesystemRecord], int, int]:
18
+ """Upload files using the filesystem strategy.
19
+
20
+ Args:
21
+ context: Module context containing the filesystem strategy
22
+ files: List of files to upload
23
+
24
+ Returns:
25
+ Tuple of (all_files, succeeded_files, failed_files)
26
+
27
+ Raises:
28
+ FilesystemServiceError: If upload operation fails
29
+ """
30
+ return context.filesystem.upload_files(files)
31
+
32
+ @staticmethod
33
+ def get_file(context: ModuleContext, file_id: str) -> tuple[FilesystemRecord, bytes | None]:
34
+ """Retrieve a file by ID with the content.
35
+
36
+ Args:
37
+ context: Module context containing the filesystem strategy
38
+ file_id: Unique identifier for the file
39
+ include_content: Whether to include file content in response
40
+
41
+ Returns:
42
+ File object with metadata and optionally content
43
+
44
+ Raises:
45
+ FilesystemServiceError: If file retrieval fails
46
+ """
47
+ return context.filesystem.get_file(file_id, include_content=True)
@@ -0,0 +1,59 @@
1
+ """Logger Mixin to ease and merge every logs."""
2
+
3
+ from digitalkin.models.module.module_context import ModuleContext
4
+
5
+
6
+ class LoggerMixin:
7
+ """Mixin providing callback operations through the callbacks strategy.
8
+
9
+ This mixin wraps callback strategy calls to provide a cleaner API
10
+ for logging and messaging in trigger handlers.
11
+ """
12
+
13
+ @staticmethod
14
+ def log_debug(context: ModuleContext, message: str) -> None:
15
+ """Log debug message using the callbacks strategy.
16
+
17
+ Args:
18
+ context: Module context containing the callbacks strategy
19
+ message: Debug message to log
20
+ *args: Positional arguments for message formatting
21
+ **kwargs: Keyword arguments for logger
22
+ """
23
+ return context.callbacks.logger.debug(message)
24
+
25
+ @staticmethod
26
+ def log_info(context: ModuleContext, message: str) -> None:
27
+ """Log info message using the callbacks strategy.
28
+
29
+ Args:
30
+ context: Module context containing the callbacks strategy
31
+ message: Info message to log
32
+ *args: Positional arguments for message formatting
33
+ **kwargs: Keyword arguments for logger
34
+ """
35
+ return context.callbacks.logger.info(message)
36
+
37
+ @staticmethod
38
+ def log_warning(context: ModuleContext, message: str) -> None:
39
+ """Log warning message using the callbacks strategy.
40
+
41
+ Args:
42
+ context: Module context containing the callbacks strategy
43
+ message: Warning message to log
44
+ *args: Positional arguments for message formatting
45
+ **kwargs: Keyword arguments for logger
46
+ """
47
+ return context.callbacks.logger.warning(message)
48
+
49
+ @staticmethod
50
+ def log_error(context: ModuleContext, message: str) -> None:
51
+ """Log error message using the callbacks strategy.
52
+
53
+ Args:
54
+ context: Module context containing the callbacks strategy
55
+ message: Error message to log
56
+ *args: Positional arguments for message formatting
57
+ **kwargs: Keyword arguments for logger
58
+ """
59
+ return context.callbacks.logger.error(message)
@@ -0,0 +1,79 @@
1
+ """Storage Mixin to ease storage access in Triggers."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from digitalkin.models.module.module_context import ModuleContext
6
+ from digitalkin.services.storage.storage_strategy import StorageRecord
7
+
8
+
9
+ class StorageMixin:
10
+ """Mixin providing storage operations through the storage strategy.
11
+
12
+ This mixin wraps storage strategy calls to provide a cleaner API
13
+ for trigger handlers.
14
+ """
15
+
16
+ @staticmethod
17
+ def store_storage(
18
+ context: ModuleContext,
19
+ collection: str,
20
+ record_id: str | None,
21
+ data: dict[str, Any],
22
+ data_type: Literal["OUTPUT", "VIEW", "LOGS", "OTHER"] = "OUTPUT",
23
+ ) -> StorageRecord:
24
+ """Store data using the storage strategy.
25
+
26
+ Args:
27
+ context: Module context containing the storage strategy
28
+ collection: Collection name for the data
29
+ record_id: Optional record identifier
30
+ data: Data to store
31
+ data_type: Type of data being stored
32
+
33
+ Returns:
34
+ Result from the storage strategy
35
+
36
+ Raises:
37
+ StorageServiceError: If storage operation fails
38
+ """
39
+ return context.storage.store(collection, record_id, data, data_type=data_type)
40
+
41
+ @staticmethod
42
+ def read_storage(context: ModuleContext, collection: str, record_id: str) -> StorageRecord | None:
43
+ """Read data from storage.
44
+
45
+ Args:
46
+ context: Module context containing the storage strategy
47
+ collection: Collection name
48
+ record_id: Record identifier
49
+
50
+ Returns:
51
+ Retrieved data
52
+
53
+ Raises:
54
+ StorageServiceError: If read operation fails
55
+ """
56
+ return context.storage.read(collection, record_id)
57
+
58
+ @staticmethod
59
+ def update_storage(
60
+ context: ModuleContext,
61
+ collection: str,
62
+ record_id: str,
63
+ data: dict[str, Any],
64
+ ) -> StorageRecord | None:
65
+ """Update existing data in storage.
66
+
67
+ Args:
68
+ context: Module context containing the storage strategy
69
+ collection: Collection name
70
+ record_id: Record identifier
71
+ data: Updated data
72
+
73
+ Returns:
74
+ Result from the storage strategy
75
+
76
+ Raises:
77
+ StorageServiceError: If update operation fails
78
+ """
79
+ return context.storage.update(collection, record_id, data)
@@ -5,6 +5,7 @@ from digitalkin.models.module.module_context import ModuleContext
5
5
  from digitalkin.models.module.module_types import (
6
6
  DataModel,
7
7
  DataTrigger,
8
+ DataTriggerT,
8
9
  InputModelT,
9
10
  OutputModelT,
10
11
  SecretModelT,
@@ -15,6 +16,7 @@ from digitalkin.models.module.module_types import (
15
16
  __all__ = [
16
17
  "DataModel",
17
18
  "DataTrigger",
19
+ "DataTriggerT",
18
20
  "InputModelT",
19
21
  "Module",
20
22
  "ModuleContext",
@@ -2,7 +2,15 @@
2
2
 
3
3
  from enum import Enum, auto
4
4
 
5
- from pydantic import BaseModel
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ModuleCodeModel(BaseModel):
9
+ """typed error/code model."""
10
+
11
+ code: str = Field(...)
12
+ message: str | None = Field(default=None)
13
+ short_description: str | None = Field(default=None)
6
14
 
7
15
 
8
16
  class ModuleStatus(Enum):
@@ -1,24 +1,108 @@
1
1
  """Define the module context used in the triggers."""
2
2
 
3
3
  from types import SimpleNamespace
4
+ from typing import Any
4
5
 
6
+ from digitalkin.services.agent.agent_strategy import AgentStrategy
5
7
  from digitalkin.services.cost.cost_strategy import CostStrategy
6
8
  from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
9
+ from digitalkin.services.identity.identity_strategy import IdentityStrategy
10
+ from digitalkin.services.registry.registry_strategy import RegistryStrategy
11
+ from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
7
12
  from digitalkin.services.storage.storage_strategy import StorageStrategy
8
13
 
9
14
 
10
- class ModuleContext(SimpleNamespace):
11
- """ModuleContext provides a container for strategies and resources used by a module.
15
+ class Session(SimpleNamespace):
16
+ """Session data container with mandatory setup_id and mission_id."""
17
+
18
+ mission_id: str
19
+ setup_version_id: str
20
+
21
+ def __init__(self, mission_id: str, setup_version_id: str, **kwargs: dict[str, Any]) -> None:
22
+ """Init Module Session.
23
+
24
+ Args:
25
+ mission_id: current mission_id.
26
+ setup_version_id: used setup config.
27
+ kwargs: user defined session variables.
28
+
29
+ Raises:
30
+ ValueError: If mandatory args are missing
31
+ """
32
+ if not setup_version_id:
33
+ msg = "setup_version_id is mandatory and cannot be empty"
34
+ raise ValueError(msg)
35
+ if not mission_id:
36
+ msg = "mission_id is mandatory and cannot be empty"
37
+ raise ValueError(msg)
38
+
39
+ self.mission_id = mission_id
40
+ self.setup_version_id = setup_version_id
41
+
42
+ super().__init__(**kwargs)
12
43
 
13
- Attributes:
14
- cost (CostStrategy): The strategy used to calculate or manage costs within the module.
15
- filesystem (FilesystemStrategy): The strategy for interacting with the filesystem.
16
- storage (StorageStrategy): The strategy for handling storage operations.
44
+
45
+ class ModuleContext:
46
+ """ModuleContext provides a container for strategies and resources used by a module.
17
47
 
18
48
  This context object is designed to be passed to module components, providing them with
19
49
  access to shared strategies and resources. Additional attributes may be set dynamically.
20
50
  """
21
51
 
52
+ # services list
53
+ agent: AgentStrategy
22
54
  cost: CostStrategy
23
55
  filesystem: FilesystemStrategy
56
+ identity: IdentityStrategy
57
+ registry: RegistryStrategy
58
+ snapshot: SnapshotStrategy
24
59
  storage: StorageStrategy
60
+
61
+ session: Session
62
+ callbacks: SimpleNamespace
63
+ metadata: SimpleNamespace
64
+ helpers: SimpleNamespace
65
+ state: SimpleNamespace = SimpleNamespace()
66
+
67
+ def __init__( # noqa: PLR0913, PLR0917
68
+ self,
69
+ agent: AgentStrategy,
70
+ cost: CostStrategy,
71
+ filesystem: FilesystemStrategy,
72
+ identity: IdentityStrategy,
73
+ registry: RegistryStrategy,
74
+ snapshot: SnapshotStrategy,
75
+ storage: StorageStrategy,
76
+ session: dict[str, Any],
77
+ metadata: dict[str, Any] = {},
78
+ helpers: dict[str, Any] = {},
79
+ callbacks: dict[str, Any] = {},
80
+ ) -> None:
81
+ """Register mandatory services, session, metadata and callbacks.
82
+
83
+ Args:
84
+ agent: AgentStrategy.
85
+ cost: CostStrategy.
86
+ filesystem: FilesystemStrategy.
87
+ identity: IdentityStrategy.
88
+ registry: RegistryStrategy.
89
+ snapshot: SnapshotStrategy.
90
+ storage: StorageStrategy.
91
+ metadata: dict defining differents Module metadata.
92
+ helpers: dict different user defined helpers.
93
+ session: dict referring the session IDs or informations.
94
+ callbacks: Functions allowing user to agent interaction.
95
+ """
96
+ # Core services
97
+ self.agent = agent
98
+ self.cost = cost
99
+ self.filesystem = filesystem
100
+ self.identity = identity
101
+ self.registry = registry
102
+ self.snapshot = snapshot
103
+ self.storage = storage
104
+
105
+ self.metadata = SimpleNamespace(**metadata)
106
+ self.session = Session(**session)
107
+ self.helpers = SimpleNamespace(**helpers)
108
+ self.callbacks = SimpleNamespace(**callbacks)
@@ -9,9 +9,9 @@ from digitalkin.logger import logger
9
9
 
10
10
 
11
11
  class DataTrigger(BaseModel):
12
- """Defines the root input model exposing the protocol.
12
+ """Defines the root input/output model exposing the protocol.
13
13
 
14
- The mandatory protocol is important to define the module beahvior following the user or agent input.
14
+ The mandatory protocol is important to define the module beahvior following the user or agent input/output.
15
15
 
16
16
  Example:
17
17
  class MyInput(DataModel):
@@ -31,9 +31,9 @@ DataTriggerT = TypeVar("DataTriggerT", bound=DataTrigger)
31
31
 
32
32
 
33
33
  class DataModel(BaseModel, Generic[DataTriggerT]):
34
- """Base definition of input model showing mandatory root fields.
34
+ """Base definition of input/output model showing mandatory root fields.
35
35
 
36
- The Model define the Module Input, usually referring to multiple input type defined by an union.
36
+ The Model define the Module Input/output, usually referring to multiple input/output type defined by an union.
37
37
 
38
38
  Example:
39
39
  class ModuleInput(DataModel):
@@ -102,4 +102,4 @@ class SetupModel(BaseModel):
102
102
  __config__=ConfigDict(arbitrary_types_allowed=True),
103
103
  **clean_fields,
104
104
  )
105
- return cast("type[SetupModelT]", m)
105
+ return cast("type[SetupModelT]", m) # type: ignore
@@ -0,0 +1,51 @@
1
+ """."""
2
+
3
+ from datetime import datetime, timezone
4
+ from enum import Enum
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class TaskStatus(Enum):
11
+ """."""
12
+
13
+ PENDING = "pending"
14
+ RUNNING = "running"
15
+ CANCELLED = "cancelled"
16
+ COMPLETED = "completed"
17
+ FAILED = "failed"
18
+
19
+
20
+ class SignalType(Enum):
21
+ """."""
22
+
23
+ START = "start"
24
+ STOP = "stop"
25
+ CANCEL = "cancel"
26
+ PAUSE = "pause"
27
+ RESUME = "resume"
28
+ STATUS = "status"
29
+
30
+ ACK_CANCEL = "ack_cancel"
31
+ ACK_PAUSE = "ack_pause"
32
+ ACK_RESUME = "ack_resume"
33
+ ACK_STATUS = "ack_status"
34
+
35
+
36
+ class SignalMessage(BaseModel):
37
+ """."""
38
+
39
+ task_id: str = Field(..., description="Unique identifier for the task")
40
+ status: TaskStatus = Field(..., description="Current status of the task")
41
+ action: SignalType = Field(..., description="Type of signal action")
42
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
43
+ payload: dict[str, Any] = Field(default={}, description="Optional payload for the signal")
44
+ model_config = {"use_enum_values": True}
45
+
46
+
47
+ class HeartbeatMessage(BaseModel):
48
+ """."""
49
+
50
+ task_id: str = Field(..., description="Unique identifier for the task")
51
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
@@ -1 +1,10 @@
1
1
  """This module contains the models for the services."""
2
+
3
+ from digitalkin.models.services.storage import BaseMessage, BaseRole, ChatHistory, Role
4
+
5
+ __all__ = [
6
+ "BaseMessage",
7
+ "BaseRole",
8
+ "ChatHistory",
9
+ "Role",
10
+ ]
@@ -1,10 +1,44 @@
1
1
  """Storage model."""
2
2
 
3
- from pydantic import BaseModel
3
+ from enum import Enum
4
+ from typing import Any
4
5
 
6
+ from pydantic import BaseModel, Field
5
7
 
6
- class StorageModel(BaseModel):
7
- """Storage model."""
8
8
 
9
- data: object
10
- type: str
9
+ class BaseRole(str, Enum):
10
+ """Officially supported Role Enum for chat messages."""
11
+
12
+ ASSISTANT = "assistant"
13
+ HUMAN = "human"
14
+ SYSTEM = "system"
15
+
16
+
17
+ Role = BaseRole | str
18
+
19
+
20
+ class BaseMessage(BaseModel):
21
+ """Base Model representing a simple message in the chat history."""
22
+
23
+ role: Role = Field(..., description="Role of the message sender")
24
+ content: Any = Field(..., description="The content of the message | preferably a BaseModel.")
25
+
26
+
27
+ class ChatHistory(BaseModel):
28
+ """Storage chat history model for the OpenAI Archetype module."""
29
+
30
+ messages: list[BaseMessage] = Field(..., description="List of messages in the chat history")
31
+
32
+
33
+ class FileModel(BaseModel):
34
+ """File model."""
35
+
36
+ file_id: str = Field(..., description="ID of the file")
37
+ name: str = Field(..., description="Name of the file")
38
+ metadata: dict[str, Any] = Field(..., description="Metadata of the file")
39
+
40
+
41
+ class FileHistory(BaseModel):
42
+ """File history model."""
43
+
44
+ files: list[FileModel] = Field(..., description="List of files")