digitalkin 0.3.2.dev5__py3-none-any.whl → 0.3.2.dev6__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.
digitalkin/__version__.py CHANGED
@@ -5,4 +5,4 @@ from importlib.metadata import PackageNotFoundError, version
5
5
  try:
6
6
  __version__ = version("digitalkin")
7
7
  except PackageNotFoundError:
8
- __version__ = "0.3.2.dev5"
8
+ __version__ = "0.3.2.dev6"
@@ -174,7 +174,7 @@ class ModuleServer(BaseServer):
174
174
  "Attempting to register module with registry",
175
175
  extra={
176
176
  "module_id": module_id,
177
- "address": self.server_config.address,
177
+ "address": self.server_config.host,
178
178
  "port": self.server_config.port,
179
179
  "version": version,
180
180
  "registry_address": self.server_config.registry_address,
@@ -183,7 +183,7 @@ class ModuleServer(BaseServer):
183
183
 
184
184
  result = self.registry.register(
185
185
  module_id=module_id,
186
- address=self.server_config.address,
186
+ address=self.server_config.host,
187
187
  port=self.server_config.port,
188
188
  version=version,
189
189
  )
@@ -193,7 +193,7 @@ class ModuleServer(BaseServer):
193
193
  "Module registered successfully",
194
194
  extra={
195
195
  "module_id": result.module_id,
196
- "address": self.server_config.address,
196
+ "address": self.server_config.host,
197
197
  "port": self.server_config.port,
198
198
  "registry_address": self.server_config.registry_address,
199
199
  },
@@ -67,7 +67,8 @@ class UtilitySchemaExtender:
67
67
  original_annotation = base_model.model_fields["root"].annotation
68
68
  original_types = cls._extract_union_types(original_annotation)
69
69
  extended_types = (*original_types, *cls._output_protocols)
70
- extended_root = Annotated[extended_types, Field(discriminator="protocol")] # type: ignore[valid-type]
70
+ union_type = Union[extended_types] # type: ignore[valid-type] # noqa: UP007
71
+ extended_root = Annotated[union_type, Field(discriminator="protocol")] # type: ignore[valid-type]
71
72
  return create_model(
72
73
  f"{base_model.__name__}Utilities",
73
74
  __base__=DataModel,
@@ -88,7 +89,8 @@ class UtilitySchemaExtender:
88
89
  original_annotation = base_model.model_fields["root"].annotation
89
90
  original_types = cls._extract_union_types(original_annotation)
90
91
  extended_types = (*original_types, *cls._input_protocols)
91
- extended_root = Annotated[extended_types, Field(discriminator="protocol")] # type: ignore[valid-type]
92
+ union_type = Union[extended_types] # type: ignore[valid-type] # noqa: UP007
93
+ extended_root = Annotated[union_type, Field(discriminator="protocol")] # type: ignore[valid-type]
92
94
  return create_model(
93
95
  f"{base_model.__name__}Utilities",
94
96
  __base__=DataModel,
@@ -6,6 +6,7 @@ from types import SimpleNamespace
6
6
  from typing import TYPE_CHECKING, Any
7
7
  from zoneinfo import ZoneInfo
8
8
 
9
+ from digitalkin.models.module.module_helpers import ModuleHelpers
9
10
  from digitalkin.models.module.tool_cache import ToolCache
10
11
  from digitalkin.services.agent.agent_strategy import AgentStrategy
11
12
  from digitalkin.services.communication.communication_strategy import CommunicationStrategy
@@ -100,7 +101,7 @@ class ModuleContext:
100
101
  session: Session
101
102
  callbacks: SimpleNamespace
102
103
  metadata: SimpleNamespace
103
- helpers: SimpleNamespace
104
+ helpers: ModuleHelpers
104
105
  state: SimpleNamespace = SimpleNamespace()
105
106
  tool_cache: ToolCache
106
107
 
@@ -152,7 +153,7 @@ class ModuleContext:
152
153
 
153
154
  self.metadata = SimpleNamespace(**metadata)
154
155
  self.session = Session(**session)
155
- self.helpers = SimpleNamespace(**helpers)
156
+ self.helpers = ModuleHelpers(context=self, **helpers)
156
157
  self.callbacks = SimpleNamespace(**callbacks)
157
158
  self.tool_cache = tool_cache or ToolCache()
158
159
 
@@ -0,0 +1,189 @@
1
+ """Module helpers for inter-module communication."""
2
+
3
+ from collections.abc import AsyncGenerator, Callable, Coroutine
4
+ from types import SimpleNamespace
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from digitalkin.logger import logger
8
+
9
+ if TYPE_CHECKING:
10
+ from digitalkin.models.module.module_context import ModuleContext
11
+
12
+
13
+ class ModuleHelpers(SimpleNamespace):
14
+ """Helpers for module-to-module communication.
15
+
16
+ Extends SimpleNamespace to allow dynamic attribute assignment
17
+ while providing built-in helper methods.
18
+ """
19
+
20
+ def __init__(self, context: "ModuleContext", **kwargs: dict[str, Any]) -> None:
21
+ """Initialize helpers with context reference.
22
+
23
+ Args:
24
+ context: ModuleContext providing access to services.
25
+ **kwargs: Additional attributes to set on the namespace.
26
+ """
27
+ super().__init__(**kwargs)
28
+ self._context = context
29
+
30
+ async def call_module_by_id(
31
+ self,
32
+ module_id: str,
33
+ input_data: dict,
34
+ setup_id: str,
35
+ mission_id: str,
36
+ callback: Callable[[dict], Coroutine[Any, Any, None]] | None = None,
37
+ ) -> AsyncGenerator[dict, None]:
38
+ """Call a module by ID, discovering address/port from registry.
39
+
40
+ Args:
41
+ module_id: Module identifier to look up in registry
42
+ input_data: Input data as dictionary
43
+ setup_id: Setup configuration ID
44
+ mission_id: Mission context ID
45
+ callback: Optional callback for each response
46
+
47
+ Yields:
48
+ Streaming responses from module as dictionaries
49
+ """
50
+ module_info = self._context.registry.discover_by_id(module_id)
51
+
52
+ logger.debug(
53
+ "Calling module by ID",
54
+ extra={
55
+ "module_id": module_id,
56
+ "address": module_info.address,
57
+ "port": module_info.port,
58
+ },
59
+ )
60
+
61
+ async for response in self._context.communication.call_module(
62
+ module_address=module_info.address,
63
+ module_port=module_info.port,
64
+ input_data=input_data,
65
+ setup_id=setup_id,
66
+ mission_id=mission_id,
67
+ callback=callback,
68
+ ):
69
+ yield response
70
+
71
+ async def get_module_schemas_by_id(
72
+ self,
73
+ module_id: str,
74
+ *,
75
+ llm_format: bool = False,
76
+ ) -> dict[str, dict]:
77
+ """Get module schemas by ID, discovering address/port from registry.
78
+
79
+ Args:
80
+ module_id: Module identifier to look up in registry
81
+ llm_format: If True, return LLM-optimized schema format
82
+
83
+ Returns:
84
+ Dictionary containing schemas: {"input": ..., "output": ..., "setup": ..., "secret": ...}
85
+ """
86
+ module_info = self._context.registry.discover_by_id(module_id)
87
+
88
+ logger.debug(
89
+ "Getting module schemas by ID",
90
+ extra={
91
+ "module_id": module_id,
92
+ "address": module_info.address,
93
+ "port": module_info.port,
94
+ },
95
+ )
96
+
97
+ return await self._context.communication.get_module_schemas(
98
+ module_address=module_info.address,
99
+ module_port=module_info.port,
100
+ llm_format=llm_format,
101
+ )
102
+
103
+ async def create_openai_style_tool(self, module_id: str) -> dict[str, Any] | None:
104
+ """Create OpenAI-style function calling schema for a tool.
105
+
106
+ Uses tool cache (fast path) with registry fallback. Fetches the tool's
107
+ input schema and wraps it in OpenAI function calling format.
108
+
109
+ Args:
110
+ module_id: Module ID to look up (checks cache first, then registry)
111
+
112
+ Returns:
113
+ OpenAI-style tool schema if found:
114
+ {
115
+ "type": "function",
116
+ "function": {
117
+ "name": "...",
118
+ "description": "...",
119
+ "parameters": {...} # Input JSON Schema
120
+ }
121
+ }
122
+ None if tool not found.
123
+ """
124
+ module_info = self._context.tool_cache.check_and_get(module_id, self._context.registry)
125
+ if not module_info:
126
+ return None
127
+
128
+ schemas = await self._context.communication.get_module_schemas(
129
+ module_address=module_info.address,
130
+ module_port=module_info.port,
131
+ llm_format=True,
132
+ )
133
+
134
+ return {
135
+ "type": "function",
136
+ "function": {
137
+ "name": module_info.name or module_info.module_id,
138
+ "description": module_info.documentation or "",
139
+ "parameters": schemas["input"],
140
+ },
141
+ }
142
+
143
+ def create_tool_function(
144
+ self,
145
+ module_id: str,
146
+ ) -> Callable[..., AsyncGenerator[dict, None]] | None:
147
+ """Create async generator function for a tool.
148
+
149
+ Returns an async generator that calls the remote tool module via gRPC
150
+ and yields each response as it arrives until end_of_stream or gRPC ends.
151
+
152
+ Args:
153
+ module_id: Module ID to look up (checks cache first, then registry)
154
+
155
+ Returns:
156
+ Async generator function if tool found, None otherwise.
157
+ The function accepts **kwargs matching the tool's input schema
158
+ and yields dict responses.
159
+ """
160
+ module_info = self._context.tool_cache.check_and_get(module_id, self._context.registry)
161
+ if not module_info:
162
+ return None
163
+
164
+ # Capture references for closure
165
+ communication = self._context.communication
166
+ session = self._context.session
167
+ address = module_info.address
168
+ port = module_info.port
169
+
170
+ async def tool_function(**kwargs: Any) -> AsyncGenerator[dict, None]: # noqa: ANN401
171
+ """Call remote tool module and yield responses.
172
+
173
+ Yields:
174
+ dict: Each response from the module until end_of_stream.
175
+ """
176
+ wrapped_input = {"root": kwargs}
177
+ async for response in communication.call_module(
178
+ module_address=address,
179
+ module_port=port,
180
+ input_data=wrapped_input,
181
+ setup_id=session.setup_id,
182
+ mission_id=session.mission_id,
183
+ ):
184
+ yield response
185
+
186
+ tool_function.__name__ = module_info.name or module_info.module_id
187
+ tool_function.__doc__ = module_info.documentation or ""
188
+
189
+ return tool_function
@@ -187,6 +187,17 @@ class GrpcCommunication(CommunicationStrategy, GrpcClientWrapper):
187
187
  # Convert protobuf Struct to dict
188
188
  output_dict = json_format.MessageToDict(response.output)
189
189
 
190
+ # Check for end_of_stream signal
191
+ if output_dict.get("root", {}).get("protocol") == "end_of_stream":
192
+ logger.debug(
193
+ "End of stream received",
194
+ extra={
195
+ "module_address": module_address,
196
+ "module_port": module_port,
197
+ },
198
+ )
199
+ break
200
+
190
201
  # Add job_id and success flag
191
202
  response_dict = {
192
203
  "success": response.success,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 0.3.2.dev5
3
+ Version: 0.3.2.dev6
4
4
  Summary: SDK to build kin used in DigitalKin
5
5
  Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
6
6
  License: Attribution-NonCommercial-ShareAlike 4.0 International
@@ -7,7 +7,7 @@ base_server/mock/__init__.py,sha256=YZFT-F1l_TpvJYuIPX-7kTeE1CfOjhx9YmNRXVoi-jQ,
7
7
  base_server/mock/mock_pb2.py,sha256=sETakcS3PAAm4E-hTCV1jIVaQTPEAIoVVHupB8Z_k7Y,1843
8
8
  base_server/mock/mock_pb2_grpc.py,sha256=BbOT70H6q3laKgkHfOx1QdfmCS_HxCY4wCOX84YAdG4,3180
9
9
  digitalkin/__init__.py,sha256=7LLBAba0th-3SGqcpqFO-lopWdUkVLKzLZiMtB-mW3M,162
10
- digitalkin/__version__.py,sha256=zLz_S8lLDo3mShbN4zD8z-GrY5ZII1yQEjZz5XwyFwE,195
10
+ digitalkin/__version__.py,sha256=eog3-5zb_1ZBSzfiDIaGQRokNStum1iswtrpGBkQF5s,195
11
11
  digitalkin/logger.py,sha256=8ze_tjt2G6mDTuQcsf7-UTXWP3UHZ7LZVSs_iqF4rX4,4685
12
12
  digitalkin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  digitalkin/core/__init__.py,sha256=FJRcJ-B1Viyn-38L8XpOpZ8KOnf1I7PCDOAmKXLQhqc,71
@@ -27,13 +27,13 @@ digitalkin/core/task_manager/task_executor.py,sha256=8xh5_1zuRAaGZIH_gWyNsA4T7YY
27
27
  digitalkin/core/task_manager/task_session.py,sha256=5jw21bT_SPXUzWE7tk6YG62EXqlRJcrSakFXDFDRy28,12730
28
28
  digitalkin/grpc_servers/__init__.py,sha256=ZIRMJ1Lcas8yQ106GCup6hn2UBOsx1sNk8ap0lpEDnY,72
29
29
  digitalkin/grpc_servers/_base_server.py,sha256=ZVeCDwI7w7fFbPTXPkeJb_SOuLfd2T7za3T4oCu2UWY,18680
30
- digitalkin/grpc_servers/module_server.py,sha256=7jO6Bz4pxno0AjRFercZMwgOFW7dutywynoCCht3dnQ,7858
30
+ digitalkin/grpc_servers/module_server.py,sha256=Ec3izzV2YpdN8rGs_cX-iVulQ00FkLR5dBflHlQ8a6Y,7849
31
31
  digitalkin/grpc_servers/module_servicer.py,sha256=a5qVOd0beAwXlFAch9VfLNR8PdaYj_fF2O8rXcwInSU,21087
32
32
  digitalkin/grpc_servers/utils/__init__.py,sha256=ZnAIb_F8z4NhtPypqkdmzgRSzolKnJTk3oZx5GfWH5Y,38
33
33
  digitalkin/grpc_servers/utils/exceptions.py,sha256=LtaDtlqXCeT6iqApogs4pbtezotOVeg4fhnFzGBvFsY,692
34
34
  digitalkin/grpc_servers/utils/grpc_client_wrapper.py,sha256=ElGvp6evY5q-EBmDVyQZaDJktfShtMsptMmq16jfgxA,3285
35
35
  digitalkin/grpc_servers/utils/grpc_error_handler.py,sha256=0wPEU4713_ZlgIilaeXJV2bi90tHwYO1myDrSLeenKk,1848
36
- digitalkin/grpc_servers/utils/utility_schema_extender.py,sha256=XTHIzb7-o0nKgyDuspW8U4HQ0mZJd5azfBCtmPx2tgw,3557
36
+ digitalkin/grpc_servers/utils/utility_schema_extender.py,sha256=qTnGrRcze2qe_W61FOfcXGWRILNctidm7z_1GKlCM1E,3719
37
37
  digitalkin/mixins/__init__.py,sha256=d6ljaoyJZJT9XxOrXZG5FVNvbLURb3_CZrkp4GPZWYM,590
38
38
  digitalkin/mixins/base_mixin.py,sha256=uLkg6MbDtVc9DysjdfNIGKahxQLnnjuL3DYpuyNLbk8,486
39
39
  digitalkin/mixins/callback_mixin.py,sha256=90nHm9-pbKT14GAy3CB3fsBtpYu5IH0woOQdNLM2e_Y,836
@@ -53,7 +53,8 @@ digitalkin/models/grpc_servers/types.py,sha256=rQ78s4nAet2jy-NIDj_PUWriT0kuGHr_w
53
53
  digitalkin/models/module/__init__.py,sha256=N55wan3rAUVPEGLIDjXoFM_-DYY_zxvbQOZHzNDfwoY,751
54
54
  digitalkin/models/module/base_types.py,sha256=oIylVNqo0idTFj4dRgCt7P19daNZ-AlvgCPpL9TJvto,1850
55
55
  digitalkin/models/module/module.py,sha256=k0W8vfJJFth8XdDzkHm32SyTuSf3h2qF0hSrxAfGF1s,956
56
- digitalkin/models/module/module_context.py,sha256=cnk5OpgevgqkmD_gU5KWSBzZ2vsNFUyN8rEzW0TNI-s,6354
56
+ digitalkin/models/module/module_context.py,sha256=cjMP3cYPC4ZjTWXY3JhF_77PMT72ZglhHfq2I4JIl6I,6430
57
+ digitalkin/models/module/module_helpers.py,sha256=n5lx25c5zRmuivcThC-HdumsjyWDv31xC6tI6NMLR7U,6438
57
58
  digitalkin/models/module/module_types.py,sha256=C9azCNBk76xMa-Mww8_6AiwQR8MLAsEyUOvBYxytovI,739
58
59
  digitalkin/models/module/setup_types.py,sha256=e9CG8x75Mkh7dDIQ66UWU99VcWmyk-JAqHCpUtcgyns,21323
59
60
  digitalkin/models/module/tool_cache.py,sha256=WFoPFtHLs1Q3jdeQZpu-ZycnJwhocyYyeAo9k0JXj5o,7434
@@ -82,7 +83,7 @@ digitalkin/services/agent/default_agent.py,sha256=4N_E_eQxJGOx1KVUUg5jNOje-3ncMx
82
83
  digitalkin/services/communication/__init__.py,sha256=YbRjrg_BbNURgS56SlXSCzRBvyUELGgrIkuX19eA1f8,408
83
84
  digitalkin/services/communication/communication_strategy.py,sha256=u35O-al9LQxiORM9ZrGeHrBtT7CegtCsMwGdwvu2MlQ,2335
84
85
  digitalkin/services/communication/default_communication.py,sha256=zBExaitUxpuIyoWANdx5JHY9pHDRnSffTiHLvU5QqwY,3166
85
- digitalkin/services/communication/grpc_communication.py,sha256=0iaEQF4ihGJz2mWjxGjXVzzslEeI5CnbMJA5ddQlNAE,7581
86
+ digitalkin/services/communication/grpc_communication.py,sha256=I78vlWlvn4ACTgOiUedgksApQvX2v1_FkitAGwSgCjs,8023
86
87
  digitalkin/services/cost/__init__.py,sha256=sD_a5LrnLluASOC5m5vgIqjaco-MzZJd6XhillIBHr0,400
87
88
  digitalkin/services/cost/cost_strategy.py,sha256=MpPX33P_S5b2by6F4zT-rcyeRuh2V4NYPZe05VpDOGQ,2649
88
89
  digitalkin/services/cost/default_cost.py,sha256=XE7kNFde8NmbulU9m1lc3mi-vHFkbaJf0XHUc0D4UHE,3945
@@ -121,7 +122,7 @@ digitalkin/utils/development_mode_action.py,sha256=2hznh0ajW_4ZTysfoc0Y49161f_PQ
121
122
  digitalkin/utils/dynamic_schema.py,sha256=5-B3dBGlCYYv6uRJkgudtc0ZpBOTYxl0yKedDGsteZQ,15184
122
123
  digitalkin/utils/llm_ready_schema.py,sha256=JjMug_lrQllqFoanaC091VgOqwAd-_YzcpqFlS7p778,2375
123
124
  digitalkin/utils/package_discover.py,sha256=sa6Zp5Kape1Zr4iYiNrnZxiHDnqM06ODk6yfWHom53w,13465
124
- digitalkin-0.3.2.dev5.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
125
+ digitalkin-0.3.2.dev6.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
125
126
  modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
127
  modules/cpu_intensive_module.py,sha256=GZlirQDZdYuXrI46sv1q4RNAHZjL4EptHVQTvgK9zz8,8363
127
128
  modules/dynamic_setup_module.py,sha256=tKvUWZdlYZkfAgKR0mLuFcLiFGKpVgpsz10LeJ6B2QI,11410
@@ -129,7 +130,7 @@ modules/minimal_llm_module.py,sha256=N9aIzZQI-miyH4AB4xTmGHpMvdSLnYyXNOD4Z3YFzis
129
130
  modules/text_transform_module.py,sha256=MfhI_Ki1U6qk379ne6oazNEu4PhO4R3cRezEcr0nGPw,7251
130
131
  services/filesystem_module.py,sha256=U4dgqtuDadaXz8PJ1d_uQ_1EPncBqudAQCLUICF9yL4,7421
131
132
  services/storage_module.py,sha256=Wz2MzLvqs2D_bnBBgtnujYcAKK2V2KFMk8K21RoepSE,6972
132
- digitalkin-0.3.2.dev5.dist-info/METADATA,sha256=pp1gmxafIj4Im1s-xF4c4XlEq1IazKsnaP3j04QWDWo,29724
133
- digitalkin-0.3.2.dev5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
134
- digitalkin-0.3.2.dev5.dist-info/top_level.txt,sha256=gcjqlyrZuLjIyxrOIavCQM_olpr6ND5kPKkZd2j0xGo,40
135
- digitalkin-0.3.2.dev5.dist-info/RECORD,,
133
+ digitalkin-0.3.2.dev6.dist-info/METADATA,sha256=AYxYAY30Ax3Zr1fmomZcVKwdjb9tSxiE8l0OSH7HfaY,29724
134
+ digitalkin-0.3.2.dev6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
135
+ digitalkin-0.3.2.dev6.dist-info/top_level.txt,sha256=gcjqlyrZuLjIyxrOIavCQM_olpr6ND5kPKkZd2j0xGo,40
136
+ digitalkin-0.3.2.dev6.dist-info/RECORD,,