digitalkin 0.3.2.dev16__py3-none-any.whl → 0.3.2.dev18__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.dev16"
8
+ __version__ = "0.3.2.dev18"
@@ -6,6 +6,12 @@ from digitalkin.models.module.module_types import (
6
6
  DataTrigger,
7
7
  SetupModel,
8
8
  )
9
+ from digitalkin.models.module.tool_cache import (
10
+ ToolCache,
11
+ ToolDefinition,
12
+ ToolModuleInfo,
13
+ ToolParameter,
14
+ )
9
15
  from digitalkin.models.module.tool_reference import (
10
16
  ToolReference,
11
17
  ToolReferenceConfig,
@@ -25,6 +31,10 @@ __all__ = [
25
31
  "ModuleContext",
26
32
  "ModuleStartInfoOutput",
27
33
  "SetupModel",
34
+ "ToolCache",
35
+ "ToolDefinition",
36
+ "ToolModuleInfo",
37
+ "ToolParameter",
28
38
  "ToolReference",
29
39
  "ToolReferenceConfig",
30
40
  "ToolSelectionMode",
@@ -8,7 +8,7 @@ from typing import Any
8
8
  from zoneinfo import ZoneInfo
9
9
 
10
10
  from digitalkin.logger import logger
11
- from digitalkin.models.module.tool_cache import ToolCache
11
+ from digitalkin.models.module.tool_cache import ToolCache, ToolDefinition, ToolModuleInfo, ToolParameter
12
12
  from digitalkin.services.agent.agent_strategy import AgentStrategy
13
13
  from digitalkin.services.communication.communication_strategy import CommunicationStrategy
14
14
  from digitalkin.services.cost.cost_strategy import CostStrategy
@@ -227,74 +227,120 @@ class ModuleContext:
227
227
  llm_format=llm_format,
228
228
  )
229
229
 
230
- async def create_openai_style_tool(self, tool_name: str) -> dict[str, Any] | None:
231
- """Create OpenAI-style function calling schema for a tool.
230
+ async def create_openai_style_tools(self, module_id: str) -> list[dict[str, Any]]:
231
+ """Create OpenAI-style function calling schemas for a tool module.
232
232
 
233
- Uses tool cache (fast path) with registry fallback. Fetches the tool's
234
- input schema and wraps it in OpenAI function calling format.
233
+ Uses tool cache (fast path) with registry fallback. Returns one schema
234
+ per ToolDefinition (protocol) in the module.
235
235
 
236
236
  Args:
237
- tool_name: Module ID to look up (checks cache first, then registry).
237
+ module_id: Module ID to look up (checks cache first, then registry).
238
238
 
239
239
  Returns:
240
- OpenAI-style tool schema if found, None otherwise.
240
+ List of OpenAI-style tool schemas, one per protocol. Empty if not found.
241
241
  """
242
- module_info = self.tool_cache.get(tool_name, registry=self.registry)
243
- if not module_info:
244
- return None
245
-
246
- schemas = await self.communication.get_module_schemas(
247
- module_address=module_info.address,
248
- module_port=module_info.port,
249
- llm_format=True,
242
+ tool_module_info = await self.tool_cache.get(
243
+ module_id, registry=self.registry, communication=self.communication
250
244
  )
245
+ if not tool_module_info:
246
+ return []
247
+
248
+ return [
249
+ {
250
+ "type": "function",
251
+ "function": {
252
+ "module_id": tool_module_info.module_id,
253
+ "toolkit_name": tool_module_info.name or "undefined",
254
+ "name": tool_def.name,
255
+ "description": tool_def.description,
256
+ "parameters": ModuleContext._build_parameters_schema(tool_def.parameters),
257
+ },
258
+ }
259
+ for tool_def in tool_module_info.tools
260
+ ]
261
+
262
+ @staticmethod
263
+ def _build_parameters_schema(params: list[ToolParameter]) -> dict[str, Any]:
264
+ """Convert ToolParameter list to JSON Schema.
265
+
266
+ Args:
267
+ params: List of tool parameters.
251
268
 
269
+ Returns:
270
+ JSON Schema object with properties and required fields.
271
+ """
252
272
  return {
253
- "type": "function",
254
- "function": {
255
- "module_id": module_info.module_id,
256
- "name": module_info.name or "undefined",
257
- "description": module_info.documentation or "",
258
- "parameters": schemas["input"],
259
- },
273
+ "type": "object",
274
+ "properties": {p.name: {"type": p.type, "description": p.description or ""} for p in params},
275
+ "required": [p.name for p in params if p.required],
260
276
  }
261
277
 
262
- def create_tool_function(
278
+ def create_tool_functions(
263
279
  self,
264
280
  module_id: str,
265
- ) -> Callable[..., AsyncGenerator[dict, None]] | None:
266
- """Create async generator function for a tool.
281
+ ) -> list[tuple[ToolDefinition, Callable[..., AsyncGenerator[dict, None]]]]:
282
+ """Create tool functions for all protocols in a tool module.
283
+
284
+ Returns an async generator per ToolDefinition that calls the remote tool
285
+ module via gRPC with the protocol auto-injected.
267
286
 
268
- Returns an async generator that calls the remote tool module via gRPC
269
- and yields each response as it arrives until end_of_stream or gRPC ends.
287
+ This method only uses the tool cache (no registry fallback). Use this
288
+ in sync contexts like __init__ methods.
270
289
 
271
290
  Args:
272
- module_id: Module ID to look up (checks cache first, then registry).
291
+ module_id: Module ID to look up in cache.
273
292
 
274
293
  Returns:
275
- Async generator function if tool found, None otherwise.
294
+ List of (ToolDefinition, async_generator_function) tuples. Empty if not found.
276
295
  """
277
- module_info = self.tool_cache.get(module_id, registry=self.registry)
278
- if not module_info:
279
- return None
296
+ tool_module_info = self.tool_cache.entries.get(module_id)
297
+ if not tool_module_info:
298
+ return []
280
299
 
281
300
  communication = self.communication
282
301
  session = self.session
283
- address = module_info.address
284
- port = module_info.port
302
+
303
+ result = []
304
+ for tool_def in tool_module_info.tools:
305
+ # Capture tool_def in closure via separate method
306
+ fn = ModuleContext._create_single_tool_function(communication, session, tool_module_info, tool_def)
307
+ result.append((tool_def, fn))
308
+
309
+ return result
310
+
311
+ @staticmethod
312
+ def _create_single_tool_function(
313
+ communication: CommunicationStrategy,
314
+ session: Session,
315
+ tool_module_info: ToolModuleInfo,
316
+ tool_def: ToolDefinition,
317
+ ) -> Callable[..., AsyncGenerator[dict, None]]:
318
+ """Create a single tool function for a specific protocol.
319
+
320
+ Args:
321
+ communication: Communication strategy for gRPC calls.
322
+ session: Current session with setup_id and mission_id.
323
+ tool_module_info: Tool module information containing address and port.
324
+ tool_def: Tool definition with protocol name.
325
+
326
+ Returns:
327
+ Async generator function that calls the module with protocol injected.
328
+ """
329
+ protocol = tool_def.name
285
330
 
286
331
  async def tool_function(**kwargs: Any) -> AsyncGenerator[dict, None]: # noqa: ANN401
332
+ kwargs["protocol"] = protocol
287
333
  wrapped_input = {"root": kwargs}
288
334
  async for response in communication.call_module(
289
- module_address=address,
290
- module_port=port,
335
+ module_address=tool_module_info.address,
336
+ module_port=tool_module_info.port,
291
337
  input_data=wrapped_input,
292
- setup_id=session.setup_id,
338
+ setup_id=tool_module_info.setup_id,
293
339
  mission_id=session.mission_id,
294
340
  ):
295
341
  yield response
296
342
 
297
- tool_function.__name__ = module_info.name or module_info.module_id
298
- tool_function.__doc__ = module_info.documentation or ""
343
+ tool_function.__name__ = tool_def.name
344
+ tool_function.__doc__ = tool_def.description
299
345
 
300
346
  return tool_function
@@ -8,9 +8,8 @@ from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast, get_arg
8
8
  from pydantic import BaseModel, ConfigDict, Field, create_model
9
9
 
10
10
  from digitalkin.logger import logger
11
- from digitalkin.models.module.tool_cache import ToolCache
11
+ from digitalkin.models.module.tool_cache import ToolCache, ToolModuleInfo
12
12
  from digitalkin.models.module.tool_reference import ToolReference
13
- from digitalkin.models.services.registry import ModuleInfo
14
13
  from digitalkin.utils.dynamic_schema import (
15
14
  DynamicField,
16
15
  get_fetchers,
@@ -21,6 +20,7 @@ from digitalkin.utils.dynamic_schema import (
21
20
  if TYPE_CHECKING:
22
21
  from pydantic.fields import FieldInfo
23
22
 
23
+ from digitalkin.services.communication import CommunicationStrategy
24
24
  from digitalkin.services.registry import RegistryStrategy
25
25
 
26
26
  SetupModelT = TypeVar("SetupModelT", bound="SetupModel")
@@ -53,14 +53,14 @@ class SetupModel(BaseModel, Generic[SetupModelT]):
53
53
  # Check if it's a list type
54
54
  origin = get_origin(annotation)
55
55
  if origin is list:
56
- new_annotations[cache_field_name] = list[ModuleInfo]
56
+ new_annotations[cache_field_name] = list[ToolModuleInfo]
57
57
  setattr(
58
58
  cls,
59
59
  cache_field_name,
60
60
  Field(default_factory=list, json_schema_extra={"hidden": True}),
61
61
  )
62
62
  else:
63
- new_annotations[cache_field_name] = ModuleInfo | None
63
+ new_annotations[cache_field_name] = ToolModuleInfo | None
64
64
  setattr(
65
65
  cls,
66
66
  cache_field_name,
@@ -336,39 +336,45 @@ class SetupModel(BaseModel, Generic[SetupModelT]):
336
336
 
337
337
  return new_field_info
338
338
 
339
- def resolve_tool_references(self, registry: "RegistryStrategy") -> None:
339
+ async def resolve_tool_references(
340
+ self, registry: "RegistryStrategy", communication: "CommunicationStrategy"
341
+ ) -> None:
340
342
  """Resolve all ToolReference fields recursively.
341
343
 
342
344
  Args:
343
345
  registry: Registry service for module discovery.
346
+ communication: Communication service for module schemas.
344
347
  """
345
348
  logger.info("Starting resolve_tool_references")
346
- self._resolve_tool_references_recursive(self, registry)
349
+ await self._resolve_tool_references_recursive(self, registry, communication)
347
350
  logger.info("Finished resolve_tool_references")
348
351
 
349
352
  @classmethod
350
- def _resolve_tool_references_recursive(
353
+ async def _resolve_tool_references_recursive(
351
354
  cls,
352
355
  model_instance: BaseModel,
353
356
  registry: "RegistryStrategy",
357
+ communication: "CommunicationStrategy",
354
358
  ) -> None:
355
359
  """Recursively resolve ToolReference fields in a model.
356
360
 
357
361
  Args:
358
362
  model_instance: Model instance to process.
359
363
  registry: Registry service for resolution.
364
+ communication: Communication service for module schemas.
360
365
  """
361
366
  for field_name, field_value in model_instance.__dict__.items():
362
367
  if field_value is None:
363
368
  continue
364
- cls._resolve_field_value(field_name, field_value, registry)
369
+ await cls._resolve_field_value(field_name, field_value, registry, communication)
365
370
 
366
371
  @classmethod
367
- def _resolve_field_value(
372
+ async def _resolve_field_value(
368
373
  cls,
369
374
  field_name: str,
370
375
  field_value: "BaseModel | ToolReference | list | dict",
371
376
  registry: "RegistryStrategy",
377
+ communication: "CommunicationStrategy",
372
378
  ) -> None:
373
379
  """Resolve a single field value based on its type.
374
380
 
@@ -376,22 +382,24 @@ class SetupModel(BaseModel, Generic[SetupModelT]):
376
382
  field_name: Name of the field.
377
383
  field_value: Value to process.
378
384
  registry: Registry service for resolution.
385
+ communication: Communication service for module schemas.
379
386
  """
380
387
  if isinstance(field_value, ToolReference):
381
- cls._resolve_single_tool_reference(field_name, field_value, registry)
388
+ await cls._resolve_single_tool_reference(field_name, field_value, registry, communication)
382
389
  elif isinstance(field_value, BaseModel):
383
- cls._resolve_tool_references_recursive(field_value, registry)
390
+ await cls._resolve_tool_references_recursive(field_value, registry, communication)
384
391
  elif isinstance(field_value, list):
385
- cls._resolve_list_items(field_value, registry)
392
+ await cls._resolve_list_items(field_value, registry, communication)
386
393
  elif isinstance(field_value, dict):
387
- cls._resolve_dict_values(field_value, registry)
394
+ await cls._resolve_dict_values(field_value, registry, communication)
388
395
 
389
396
  @classmethod
390
- def _resolve_single_tool_reference(
397
+ async def _resolve_single_tool_reference(
391
398
  cls,
392
399
  field_name: str,
393
400
  tool_ref: ToolReference,
394
401
  registry: "RegistryStrategy",
402
+ communication: "CommunicationStrategy",
395
403
  ) -> None:
396
404
  """Resolve a single ToolReference.
397
405
 
@@ -399,47 +407,54 @@ class SetupModel(BaseModel, Generic[SetupModelT]):
399
407
  field_name: Name of the field for logging.
400
408
  tool_ref: ToolReference to resolve.
401
409
  registry: Registry service for resolution.
410
+ communication: Communication service for module schemas.
402
411
  """
403
- logger.info("Resolving ToolReference '%s' with module_id='%s'", field_name, tool_ref.config.module_id)
412
+ logger.info("Resolving ToolReference '%s' with setup_id='%s'", field_name, tool_ref.config.setup_id)
404
413
  try:
405
- tool_ref.resolve(registry)
406
- logger.info("Resolved ToolReference '%s' -> %s", field_name, tool_ref.module_info)
414
+ await tool_ref.resolve(registry, communication)
415
+ logger.info("Resolved ToolReference '%s' -> %s", field_name, tool_ref.tool_module_info)
407
416
  except Exception:
408
417
  logger.exception("Failed to resolve ToolReference '%s'", field_name)
409
418
 
410
419
  @classmethod
411
- def _resolve_list_items(cls, items: list, registry: "RegistryStrategy") -> None:
420
+ async def _resolve_list_items(
421
+ cls, items: list, registry: "RegistryStrategy", communication: "CommunicationStrategy"
422
+ ) -> None:
412
423
  """Resolve ToolReference instances in a list.
413
424
 
414
425
  Args:
415
426
  items: List of items to process.
416
427
  registry: Registry service for resolution.
428
+ communication: Communication service for module schemas.
417
429
  """
418
430
  for item in items:
419
431
  if isinstance(item, ToolReference):
420
- cls._resolve_single_tool_reference("list_item", item, registry)
432
+ await cls._resolve_single_tool_reference("list_item", item, registry, communication)
421
433
  elif isinstance(item, BaseModel):
422
- cls._resolve_tool_references_recursive(item, registry)
434
+ await cls._resolve_tool_references_recursive(item, registry, communication)
423
435
 
424
436
  @classmethod
425
- def _resolve_dict_values(cls, mapping: dict, registry: "RegistryStrategy") -> None:
437
+ async def _resolve_dict_values(
438
+ cls, mapping: dict, registry: "RegistryStrategy", communication: "CommunicationStrategy"
439
+ ) -> None:
426
440
  """Resolve ToolReference instances in dict values.
427
441
 
428
442
  Args:
429
443
  mapping: Dict to process.
430
444
  registry: Registry service for resolution.
445
+ communication: Communication service for module schemas.
431
446
  """
432
447
  for item in mapping.values():
433
448
  if isinstance(item, ToolReference):
434
- cls._resolve_single_tool_reference("dict_value", item, registry)
449
+ await cls._resolve_single_tool_reference("dict_value", item, registry, communication)
435
450
  elif isinstance(item, BaseModel):
436
- cls._resolve_tool_references_recursive(item, registry)
451
+ await cls._resolve_tool_references_recursive(item, registry, communication)
437
452
 
438
453
  def build_tool_cache(self) -> ToolCache:
439
454
  """Build tool cache from resolved ToolReferences, populating companion fields.
440
455
 
441
456
  Returns:
442
- ToolCache with field names as keys and ModuleInfo as values.
457
+ ToolCache with field names as keys and ToolModuleInfo as values.
443
458
  """
444
459
  logger.info("Building tool cache")
445
460
  cache = ToolCache()
@@ -461,7 +476,7 @@ class SetupModel(BaseModel, Generic[SetupModelT]):
461
476
  cache_field_name = f"{field_name}_cache"
462
477
 
463
478
  cached_info = getattr(model_instance, cache_field_name, None)
464
- module_info = field_value.module_info or cached_info
479
+ module_info = field_value.tool_module_info or cached_info
465
480
  if module_info:
466
481
  if not cached_info:
467
482
  setattr(model_instance, cache_field_name, module_info)
@@ -472,12 +487,12 @@ class SetupModel(BaseModel, Generic[SetupModelT]):
472
487
  elif isinstance(field_value, list):
473
488
  cache_field_name = f"{field_name}_cache"
474
489
  cached_infos = getattr(model_instance, cache_field_name, None) or []
475
- resolved_infos: list[ModuleInfo] = []
490
+ resolved_infos: list[ToolModuleInfo] = []
476
491
 
477
492
  for idx, item in enumerate(field_value):
478
493
  if isinstance(item, ToolReference):
479
494
  # Use resolved info or fallback to cached
480
- module_info = item.module_info or (cached_infos[idx] if idx < len(cached_infos) else None)
495
+ module_info = item.tool_module_info or (cached_infos[idx] if idx < len(cached_infos) else None)
481
496
  if module_info:
482
497
  resolved_infos.append(module_info)
483
498
  cache.add(module_info.module_id, module_info)
@@ -1,57 +1,110 @@
1
1
  """Tool cache for resolved tool references."""
2
2
 
3
+ from typing import TYPE_CHECKING, Any
4
+
3
5
  from pydantic import BaseModel, Field
4
6
 
5
7
  from digitalkin.logger import logger
6
8
  from digitalkin.models.services.registry import ModuleInfo
7
9
  from digitalkin.services.registry import RegistryStrategy
8
10
 
11
+ if TYPE_CHECKING:
12
+ from digitalkin.services.communication import CommunicationStrategy
13
+
14
+
15
+ class ToolParameter(BaseModel):
16
+ """Definition of a single tool parameter.
17
+
18
+ Attributes:
19
+ name: Parameter name.
20
+ type: JSON Schema type (string, integer, number, boolean, array, object).
21
+ description: Parameter description for the LLM.
22
+ required: Whether this parameter is required.
23
+ enum: Optional list of allowed values.
24
+ items: Optional schema for array item types.
25
+ properties: Optional schema for object properties.
26
+ """
27
+
28
+ name: str
29
+ type: str
30
+ description: str
31
+ required: bool = True
32
+ enum: list[str] | None = None
33
+ items: dict[str, Any] | None = None
34
+ properties: dict[str, Any] | None = None
35
+
36
+
37
+ class ToolDefinition(BaseModel):
38
+ """Complete definition of an LLM tool with grouped parameters.
39
+
40
+ Attributes:
41
+ name: Tool name (from protocol const or trigger class name).
42
+ description: Tool description (from trigger docstring).
43
+ parameters: List of parameter definitions.
44
+ """
45
+
46
+ name: str
47
+ description: str
48
+ parameters: list[ToolParameter] = Field(default_factory=list)
49
+
50
+
51
+ class ToolModuleInfo(ModuleInfo):
52
+ """Module info for tool modules."""
53
+
54
+ tools: list[ToolDefinition]
55
+ setup_id: str
56
+
9
57
 
10
58
  class ToolCache(BaseModel):
11
59
  """Registry cache storing resolved tool references by setup field name."""
12
60
 
13
- entries: dict[str, ModuleInfo] = Field(default_factory=dict)
61
+ entries: dict[str, ToolModuleInfo] = Field(default_factory=dict)
14
62
 
15
- def add(self, setup_tool_name: str, module_info: ModuleInfo) -> None:
63
+ def add(self, setup_id: str, tool_module_info: ToolModuleInfo) -> None:
16
64
  """Add a tool to the cache.
17
65
 
18
66
  Args:
19
- setup_tool_name: Field name from SetupModel used as cache key.
20
- module_info: Resolved module information.
67
+ setup_id: Field name from SetupModel used as cache key.
68
+ tool_module_info: Resolved tool module information.
21
69
  """
22
- self.entries[setup_tool_name] = module_info
70
+ self.entries[setup_id] = tool_module_info
23
71
  logger.debug(
24
72
  "Tool cached",
25
- extra={"setup_tool_name": setup_tool_name, "module_id": module_info.module_id},
73
+ extra={"setup_id": setup_id, "module_id": tool_module_info.module_id},
26
74
  )
27
75
 
28
- def get(
76
+ async def get(
29
77
  self,
30
- setup_tool_name: str,
78
+ setup_id: str,
31
79
  *,
32
80
  registry: RegistryStrategy | None = None,
33
- ) -> ModuleInfo | None:
81
+ communication: "CommunicationStrategy | None" = None,
82
+ ) -> ToolModuleInfo | None:
34
83
  """Get a tool from cache, optionally querying registry on miss.
35
84
 
36
85
  Args:
37
- setup_tool_name: Field name to look up.
86
+ setup_id: Field name to look up.
38
87
  registry: Optional registry to query on cache miss.
88
+ communication: Optional communication strategy for schema fetching.
39
89
 
40
90
  Returns:
41
- ModuleInfo if found, None otherwise.
91
+ ToolModuleInfo if found, None otherwise.
42
92
  """
43
- cached = self.entries.get(setup_tool_name)
93
+ cached = self.entries.get(setup_id)
44
94
  if cached:
45
95
  return cached
46
96
 
47
- if registry:
97
+ if registry and communication:
48
98
  try:
49
- info = registry.discover_by_id(setup_tool_name)
50
- if info:
51
- self.add(setup_tool_name, info)
52
- return info
99
+ setup_info = registry.get_setup(setup_id)
100
+ if setup_info and setup_info.module_id:
101
+ info = registry.discover_by_id(setup_info.module_id)
102
+ if info:
103
+ tool_info = await module_info_to_tool_module_info(info, setup_id, communication)
104
+ self.add(setup_id, tool_info)
105
+ return tool_info
53
106
  except Exception:
54
- logger.exception("Registry lookup failed", extra={"setup_tool_name": setup_tool_name})
107
+ logger.exception("Registry lookup failed", extra={"setup_id": setup_id})
55
108
 
56
109
  return None
57
110
 
@@ -66,3 +119,109 @@ class ToolCache(BaseModel):
66
119
  List of setup field names in cache.
67
120
  """
68
121
  return list(self.entries.keys())
122
+
123
+
124
+ async def module_info_to_tool_module_info(
125
+ module_info: ModuleInfo,
126
+ setup_id: str,
127
+ communication: "CommunicationStrategy",
128
+ *,
129
+ llm_format: bool = True,
130
+ ) -> ToolModuleInfo:
131
+ """Convert ModuleInfo to ToolModuleInfo by fetching schemas via gRPC.
132
+
133
+ Fetches the module's input schema and extracts tool definitions from
134
+ the discriminated union structure.
135
+
136
+ Args:
137
+ module_info: Module info from registry.
138
+ setup_id: Setup ID from tool configuration.
139
+ communication: Communication strategy for gRPC calls.
140
+ llm_format: Use LLM-friendly schema format.
141
+
142
+ Returns:
143
+ ToolModuleInfo with tools extracted from input schema.
144
+ """
145
+ schemas = await communication.get_module_schemas(
146
+ module_info.address,
147
+ module_info.port,
148
+ llm_format=llm_format,
149
+ )
150
+
151
+ input_schema = schemas.get("input", {})
152
+ if llm_format:
153
+ input_schema = input_schema.get("json_schema", input_schema)
154
+
155
+ tools = _extract_tools_from_schema(input_schema)
156
+
157
+ return ToolModuleInfo(
158
+ module_id=module_info.module_id,
159
+ module_type=module_info.module_type,
160
+ address=module_info.address,
161
+ port=module_info.port,
162
+ version=module_info.version,
163
+ name=module_info.name,
164
+ documentation=module_info.documentation,
165
+ status=module_info.status,
166
+ tools=tools,
167
+ setup_id=setup_id,
168
+ )
169
+
170
+
171
+ def _extract_tools_from_schema(schema: dict[str, Any]) -> list[ToolDefinition]:
172
+ """Extract tool definitions from a discriminated union input schema.
173
+
174
+ Args:
175
+ schema: JSON schema with $defs containing protocol-based types.
176
+
177
+ Returns:
178
+ List of ToolDefinition with parameters grouped by trigger.
179
+ """
180
+ tools: list[ToolDefinition] = []
181
+ defs = schema.get("$defs", {})
182
+
183
+ # Skip SDK utility protocols
184
+ utility_protocols = {"HealthcheckPingInput", "HealthcheckServicesInput", "HealthcheckStatusInput"}
185
+
186
+ for def_name, def_schema in defs.items():
187
+ if def_name in utility_protocols:
188
+ continue
189
+
190
+ properties = def_schema.get("properties", {})
191
+ protocol_prop = properties.get("protocol", {})
192
+
193
+ # Skip if no protocol const (not a tool input type)
194
+ if "const" not in protocol_prop:
195
+ continue
196
+
197
+ # Extract tool-level info from trigger
198
+ tool_name = protocol_prop.get("const", def_name)
199
+ tool_description = def_schema.get("description", "")
200
+
201
+ required_fields = set(def_schema.get("required", []))
202
+ parameters: list[ToolParameter] = []
203
+
204
+ for prop_name, prop_info in properties.items():
205
+ if prop_name in {"protocol", "created_at"}:
206
+ continue
207
+
208
+ param = ToolParameter(
209
+ name=prop_name,
210
+ type=prop_info.get("type", "string"),
211
+ description=prop_info.get("description", ""),
212
+ required=prop_name in required_fields,
213
+ enum=prop_info.get("enum"),
214
+ items=prop_info.get("items"),
215
+ properties=prop_info.get("properties"),
216
+ )
217
+ parameters.append(param)
218
+
219
+ tools.append(
220
+ ToolDefinition(
221
+ name=tool_name,
222
+ description=tool_description,
223
+ parameters=parameters,
224
+ )
225
+ )
226
+
227
+ return tools
@@ -4,7 +4,8 @@ from enum import Enum
4
4
 
5
5
  from pydantic import BaseModel, Field, PrivateAttr, model_validator
6
6
 
7
- from digitalkin.models.services.registry import ModuleInfo
7
+ from digitalkin.models.module.tool_cache import ToolModuleInfo, module_info_to_tool_module_info
8
+ from digitalkin.services.communication.communication_strategy import CommunicationStrategy
8
9
  from digitalkin.services.registry import RegistryStrategy
9
10
 
10
11
 
@@ -20,6 +21,7 @@ class ToolReferenceConfig(BaseModel):
20
21
  """Tool selection configuration. The module_id serves as both identifier and cache key."""
21
22
 
22
23
  mode: ToolSelectionMode = Field(default=ToolSelectionMode.FIXED)
24
+ setup_id: str = Field(default="")
23
25
  module_id: str = Field(default="")
24
26
  tag: str = Field(default="")
25
27
  organization_id: str = Field(default="")
@@ -34,8 +36,8 @@ class ToolReferenceConfig(BaseModel):
34
36
  Raises:
35
37
  ValueError: If required field is missing for the mode.
36
38
  """
37
- if self.mode == ToolSelectionMode.FIXED and not self.module_id:
38
- msg = "module_id required when mode is FIXED"
39
+ if self.mode == ToolSelectionMode.FIXED and not self.setup_id:
40
+ msg = "setup_id required when mode is FIXED"
39
41
  raise ValueError(msg)
40
42
  if self.mode == ToolSelectionMode.TAG and not self.tag:
41
43
  msg = "tag required when mode is TAG"
@@ -47,7 +49,7 @@ class ToolReference(BaseModel):
47
49
  """Reference to a tool module, resolved via registry during config setup."""
48
50
 
49
51
  config: ToolReferenceConfig
50
- _cached_info: ModuleInfo | None = PrivateAttr(default=None)
52
+ _cached_info: ToolModuleInfo | None = PrivateAttr(default=None)
51
53
 
52
54
  @property
53
55
  def slug(self) -> str:
@@ -56,7 +58,7 @@ class ToolReference(BaseModel):
56
58
  Returns:
57
59
  Module ID used as cache key.
58
60
  """
59
- return self.config.module_id
61
+ return self.config.setup_id
60
62
 
61
63
  @property
62
64
  def module_id(self) -> str:
@@ -68,11 +70,20 @@ class ToolReference(BaseModel):
68
70
  return self.config.module_id
69
71
 
70
72
  @property
71
- def module_info(self) -> ModuleInfo | None:
73
+ def setup_id(self) -> str:
74
+ """Setup identifier.
75
+
76
+ Returns:
77
+ Setup ID or empty string if not set.
78
+ """
79
+ return self.config.setup_id
80
+
81
+ @property
82
+ def tool_module_info(self) -> ToolModuleInfo | None:
72
83
  """Resolved module information.
73
84
 
74
85
  Returns:
75
- ModuleInfo if resolved, None otherwise.
86
+ ToolModuleInfo if resolved, None otherwise.
76
87
  """
77
88
  return self._cached_info
78
89
 
@@ -85,23 +96,28 @@ class ToolReference(BaseModel):
85
96
  """
86
97
  return self._cached_info is not None
87
98
 
88
- def resolve(self, registry: RegistryStrategy) -> ModuleInfo | None:
99
+ async def resolve(self, registry: RegistryStrategy, communication: CommunicationStrategy) -> ToolModuleInfo | None:
89
100
  """Resolve this reference using the registry.
90
101
 
91
102
  Args:
92
103
  registry: Registry service for module discovery.
104
+ communication: Communication service for module schemas.
93
105
 
94
106
  Returns:
95
- ModuleInfo if resolved, None for DISCOVERABLE mode or if not found.
107
+ ToolModuleInfo if resolved, None for DISCOVERABLE mode or if not found.
96
108
  """
97
109
  if self.config.mode == ToolSelectionMode.DISCOVERABLE:
98
110
  return None
99
111
 
100
- if self.config.mode == ToolSelectionMode.FIXED and self.config.module_id:
101
- info = registry.discover_by_id(self.config.module_id)
102
- if info:
103
- self._cached_info = info
104
- return info
112
+ if self.config.mode == ToolSelectionMode.FIXED and self.config.setup_id:
113
+ setup = registry.get_setup(self.config.setup_id)
114
+ if setup and setup.module_id:
115
+ self.config.module_id = setup.module_id
116
+ info = registry.discover_by_id(self.config.module_id)
117
+ if info:
118
+ tool_module_info = await module_info_to_tool_module_info(info, self.config.setup_id, communication)
119
+ self._cached_info = tool_module_info
120
+ return tool_module_info
105
121
 
106
122
  if self.config.mode == ToolSelectionMode.TAG and self.config.tag:
107
123
  results = registry.search(
@@ -110,8 +126,11 @@ class ToolReference(BaseModel):
110
126
  organization_id=self.config.organization_id,
111
127
  )
112
128
  if results:
113
- self._cached_info = results[0]
114
- self.config.module_id = results[0].module_id
115
- return results[0]
129
+ tool_module_info = await module_info_to_tool_module_info(
130
+ results[0], self.config.setup_id, communication
131
+ )
132
+ self._cached_info = tool_module_info
133
+ self.config.module_id = tool_module_info.module_id
134
+ return tool_module_info
116
135
 
117
136
  return None
@@ -1,6 +1,7 @@
1
1
  """Registry data models."""
2
2
 
3
3
  from enum import Enum
4
+ from typing import Any
4
5
 
5
6
  from pydantic import BaseModel
6
7
 
@@ -33,3 +34,44 @@ class ModuleInfo(BaseModel):
33
34
  name: str = ""
34
35
  documentation: str | None = None
35
36
  status: RegistryModuleStatus | None = None
37
+
38
+
39
+ class RegistrySetupStatus(str, Enum):
40
+ """Setup status in the registry."""
41
+
42
+ UNSPECIFIED = "unspecified"
43
+ DRAFT = "draft"
44
+ WAITING_FOR_APPROVAL = "waiting_for_approval"
45
+ READY = "ready"
46
+ PAUSED = "paused"
47
+ FAILED = "failed"
48
+ ARCHIVED = "archived"
49
+ NEEDS_CONFIGURATION = "needs_configuration"
50
+ CONFIGURATION_FAILED = "configuration_failed"
51
+ CONFIGURATION_SUCCEEDED = "configuration_succeeded"
52
+
53
+
54
+ class RegistryVisibility(str, Enum):
55
+ """Visibility in the registry."""
56
+
57
+ UNSPECIFIED = "unspecified"
58
+ PUBLIC = "public"
59
+ PRIVATE = "private"
60
+ INTERNAL = "internal"
61
+
62
+
63
+ class SetupInfo(BaseModel):
64
+ """Setup information from registry."""
65
+
66
+ setup_id: str
67
+ name: str
68
+ documentation: str | None = None
69
+ status: RegistrySetupStatus | None = None
70
+ visibility: RegistryVisibility | None = None
71
+ organization_id: str | None = None
72
+ owner_id: str | None = None
73
+ card_id: str | None = None
74
+ module_id: str | None = None
75
+ setup_version_id: str | None = None
76
+ setup_version: str | None = None
77
+ config: dict[str, Any] | None = None
@@ -518,11 +518,14 @@ class BaseModule( # noqa: PLR0904
518
518
  config_setup_data: Setup data containing tool references.
519
519
  """
520
520
  logger.info("Starting tool resolution", extra=self.context.session.current_ids())
521
- if self.context.registry is not None:
522
- config_setup_data.resolve_tool_references(self.context.registry)
521
+ if self.context.registry is not None and self.context.communication is not None:
522
+ await config_setup_data.resolve_tool_references(self.context.registry, self.context.communication)
523
523
  logger.info("Tool references resolved", extra=self.context.session.current_ids())
524
524
  else:
525
- logger.warning("No registry available, skipping tool resolution", extra=self.context.session.current_ids())
525
+ logger.warning(
526
+ "No registry or communication available, skipping tool resolution",
527
+ extra=self.context.session.current_ids(),
528
+ )
526
529
 
527
530
  tool_cache = config_setup_data.build_tool_cache()
528
531
  self.context.tool_cache = tool_cache
@@ -22,6 +22,9 @@ from digitalkin.models.services.registry import (
22
22
  ModuleInfo,
23
23
  RegistryModuleStatus,
24
24
  RegistryModuleType,
25
+ RegistrySetupStatus,
26
+ RegistryVisibility,
27
+ SetupInfo,
25
28
  )
26
29
  from digitalkin.services.registry.exceptions import (
27
30
  RegistryModuleNotFoundError,
@@ -75,6 +78,35 @@ class GrpcRegistry(RegistryStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
75
78
  documentation=descriptor.documentation or None,
76
79
  )
77
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
+
78
110
  def discover_by_id(self, module_id: str) -> ModuleInfo:
79
111
  """Get module info by ID.
80
112
 
@@ -304,3 +336,28 @@ class GrpcRegistry(RegistryStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
304
336
  extra={"module_id": module_id, "status": status_name},
305
337
  )
306
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)
@@ -6,6 +6,7 @@ from typing import Any
6
6
  from digitalkin.models.services.registry import (
7
7
  ModuleInfo,
8
8
  RegistryModuleStatus,
9
+ SetupInfo,
9
10
  )
10
11
  from digitalkin.services.base_strategy import BaseStrategy
11
12
  from digitalkin.services.registry.registry_models import ModuleStatusInfo
@@ -96,3 +97,8 @@ class RegistryStrategy(BaseStrategy, ABC):
96
97
  RegistryModuleNotFoundError: If module not found.
97
98
  """
98
99
  raise NotImplementedError
100
+
101
+ @abstractmethod
102
+ def get_setup(self, setup_id: str) -> SetupInfo | None:
103
+ """Get setup info."""
104
+ raise NotImplementedError
@@ -2,6 +2,7 @@
2
2
 
3
3
  from digitalkin.utils.dynamic_schema import (
4
4
  DEFAULT_TIMEOUT,
5
+ Dynamic,
5
6
  DynamicField,
6
7
  Fetcher,
7
8
  ResolveResult,
@@ -12,9 +13,6 @@ from digitalkin.utils.dynamic_schema import (
12
13
  resolve_safe,
13
14
  )
14
15
 
15
- # Alias for cleaner API: `Dynamic` is shorter than `DynamicField`
16
- Dynamic = DynamicField
17
-
18
16
  __all__ = [
19
17
  "DEFAULT_TIMEOUT",
20
18
  "Dynamic",
@@ -136,6 +136,10 @@ class DynamicField:
136
136
  return hash(tuple(sorted(self.fetchers.keys())))
137
137
 
138
138
 
139
+ # Alias for cleaner API: `Dynamic` is shorter than `DynamicField`
140
+ Dynamic = DynamicField
141
+
142
+
139
143
  def get_dynamic_metadata(field_info: FieldInfo) -> DynamicField | None:
140
144
  """Extract DynamicField metadata from a FieldInfo's metadata list.
141
145
 
@@ -55,6 +55,8 @@ class SchemaSplitter:
55
55
  for item in value:
56
56
  if isinstance(item, dict):
57
57
  cls._extract_ui_properties(item, ui_target)
58
+ elif key in {"if", "then", "else"} and isinstance(value, dict):
59
+ cls._extract_ui_properties(value, ui_target)
58
60
 
59
61
  @classmethod
60
62
  def _process_object( # noqa: C901, PLR0912
@@ -107,11 +109,15 @@ class SchemaSplitter:
107
109
  item_json: dict[str, Any] = {}
108
110
  cls._strip_ui_properties(item, item_json)
109
111
  json_target["allOf"].append(item_json)
112
+ # Extract UI properties from allOf item
113
+ cls._extract_ui_properties(item, ui_target)
110
114
  else:
111
115
  json_target["allOf"].append(item)
112
116
  elif key in {"if", "then", "else"} and isinstance(value, dict):
113
117
  json_target[key] = {}
114
118
  cls._strip_ui_properties(value, json_target[key])
119
+ # Extract UI properties from conditional
120
+ cls._extract_ui_properties(value, ui_target)
115
121
  else:
116
122
  json_target[key] = value
117
123
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 0.3.2.dev16
3
+ Version: 0.3.2.dev18
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=m7kmroPPO7q0C9L9h_vVxt6pHdT877YE_CXSN3uW4Fs,196
10
+ digitalkin/__version__.py,sha256=6eu1NkqoRgH4x9vy4KK1UNb25WQD1SomFTIF5afEldM,196
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
@@ -50,21 +50,21 @@ digitalkin/models/core/task_monitor.py,sha256=CW-jydSgXMV464W0pqfar0HpgqlSxqdujm
50
50
  digitalkin/models/grpc_servers/__init__.py,sha256=0tA71nPSXgRrh9DoLvx-TSwZXdYIRUEItoadpTL1cTo,42
51
51
  digitalkin/models/grpc_servers/models.py,sha256=gRX94eL71a5mLIie-lCOwE7a0As_AuGduxPPzTHbAe4,13797
52
52
  digitalkin/models/grpc_servers/types.py,sha256=rQ78s4nAet2jy-NIDj_PUWriT0kuGHr_w6ELjmjgBao,539
53
- digitalkin/models/module/__init__.py,sha256=N55wan3rAUVPEGLIDjXoFM_-DYY_zxvbQOZHzNDfwoY,751
53
+ digitalkin/models/module/__init__.py,sha256=e2a_AUmobkpyITQKvMkDaDxvb-GOMHhDF9fn0q5_EnQ,959
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=qpjyMYgTCyQS0lW2RhTKZsoQKwFpv6l08FIM0sqC6DQ,10326
56
+ digitalkin/models/module/module_context.py,sha256=QDdjZdhIJpvU_2Tn7kkJsZ1givB4dM1-ksopdF4VySw,12176
57
57
  digitalkin/models/module/module_types.py,sha256=C9azCNBk76xMa-Mww8_6AiwQR8MLAsEyUOvBYxytovI,739
58
- digitalkin/models/module/setup_types.py,sha256=XMKyDm-7n_Za9tDBv1AkgXUowlUj46SMxWANa_yg_LU,18311
59
- digitalkin/models/module/tool_cache.py,sha256=RP3JwASV8dFUZEKudyALbu0_tq81lstuEc4QHMQpmvM,2073
60
- digitalkin/models/module/tool_reference.py,sha256=6GhLQC4pqCV3b7Nmm0mr0TZC13RDHIUOm1J-pIdWsmo,3479
58
+ digitalkin/models/module/setup_types.py,sha256=namyC-0iA4ryHqKCzjYsVYeFDduwMqYCYRmQSSH8414,19356
59
+ digitalkin/models/module/tool_cache.py,sha256=5e30A_GxT2W-w1LZFmVUqOxDjPcrZ8s_eW7p9impO64,7153
60
+ digitalkin/models/module/tool_reference.py,sha256=eIWJrT6syyEaXAWRXIlWYTst-j0XuvtU_va9m3tj_KU,4470
61
61
  digitalkin/models/module/utility.py,sha256=gnbYfWpXGbomUI0fWf7T-Qm_VvT-LXDv1OuA9zObwVg,5589
62
62
  digitalkin/models/services/__init__.py,sha256=jhfVw6egq0OcHmos_fypH9XFehbHTBw09wluVFVFEyw,226
63
63
  digitalkin/models/services/cost.py,sha256=9PXvd5RrIk9vCrRjcUGQ9ZyAokEbwLg4s0RfnE-aLP4,1616
64
- digitalkin/models/services/registry.py,sha256=hz_r03-K633XHu2fOb5HWsE59EPFusBBipy0Gz6OBvI,705
64
+ digitalkin/models/services/registry.py,sha256=mFehnPAVLGimodHquNrltXbH_aE0jEa-PxfyNm6J38E,1828
65
65
  digitalkin/models/services/storage.py,sha256=wp7F-AvTsU46ujGPcguqM5kUKRZx4399D4EGAAJt2zs,1143
66
66
  digitalkin/modules/__init__.py,sha256=vTQk8DWopxQSJ17BjE5dNhq247Rou55iQLJdBxoPUmo,296
67
- digitalkin/modules/_base_module.py,sha256=4-fS376f9ti3xSgM946yRccHcEGttdo2QYKaxdqk1kY,22451
67
+ digitalkin/modules/_base_module.py,sha256=vQX-vqGXafc9DkNJXzTic-1zJ3yrP1BC30fTH8yHB1k,22592
68
68
  digitalkin/modules/archetype_module.py,sha256=XC9tl1Yr6QlbPn_x0eov6UUZwQgwW--BYPPMYVJH_NU,505
69
69
  digitalkin/modules/tool_module.py,sha256=GBis7bKCkvWFCYLRvaS9oZVmLBBve1w8BhVnKOU2sCc,506
70
70
  digitalkin/modules/trigger_handler.py,sha256=qPNMi-8NHqscOxciHeaXtpwjXApT3YzjMF23zQAjaZY,1770
@@ -97,9 +97,9 @@ digitalkin/services/identity/identity_strategy.py,sha256=skappBbds1_qa0Gr24FGrNX
97
97
  digitalkin/services/registry/__init__.py,sha256=WPGQM3U-QvMXhsaOy9BN0kVMU3QkPFwAMT3lGmTR-Ko,835
98
98
  digitalkin/services/registry/default_registry.py,sha256=tOqw9Ve9w_BzhqrZmHuUl5Ps-J_KTEwYg3tu1gNIHmw,4258
99
99
  digitalkin/services/registry/exceptions.py,sha256=tAcVXioCzDqfBvxB_P0uQpaK_LDLrFb0KpymROuqs-8,1371
100
- digitalkin/services/registry/grpc_registry.py,sha256=2_He-I9I4SXdjYA9QYxFWQrr_xBao6LIYXsgiICkeHY,10822
100
+ digitalkin/services/registry/grpc_registry.py,sha256=uBQbETfB3Gheteo2CWZ4l3Ho4QvSOJ0OCoRgzy7ZrVM,13079
101
101
  digitalkin/services/registry/registry_models.py,sha256=DJEwMJg5_BewpgHDtY8xIGWj9jA9H07iYgHLCv81giY,331
102
- digitalkin/services/registry/registry_strategy.py,sha256=N6oLxMwnYkM_XVW7WDxZoSfFafjUtsFDig3F8VQZj7Q,2745
102
+ digitalkin/services/registry/registry_strategy.py,sha256=oxCm5mQcO1PSwMwOtEv8dwzQx_F0uGKgiWeVMeiV51s,2905
103
103
  digitalkin/services/setup/__init__.py,sha256=t6xcvEWqTbcRZstBFK9cESEqaZKvpW14VtYygxIqfYQ,65
104
104
  digitalkin/services/setup/default_setup.py,sha256=zTG9Nvw3Tqy9qctSEnjWE4Mlqol321AUaTlIm6Jl1k0,8195
105
105
  digitalkin/services/setup/grpc_setup.py,sha256=aEAHiG0WSzNSQtJjn7xAGQwj70b0HrFO8GBKFXRx3aI,13925
@@ -115,14 +115,14 @@ digitalkin/services/user_profile/__init__.py,sha256=RKEZCsgCHS7fmswhWgUoQd6vZ_1p
115
115
  digitalkin/services/user_profile/default_user_profile.py,sha256=46DH_VBCHKXJVyagVcc8kH5sLwRK54Fe_0ahqYJ1maA,1847
116
116
  digitalkin/services/user_profile/grpc_user_profile.py,sha256=xDiUC5Ceofa6QtGPmqJV3ik5j8HDHc1zxtpia49rlRw,2780
117
117
  digitalkin/services/user_profile/user_profile_strategy.py,sha256=CH8kT__1MUwA21k5djjmB5ZZ6pYg57OWbe_7owBCgwU,681
118
- digitalkin/utils/__init__.py,sha256=gDoEk5CP8tiBmIq-38aPhIMlnDUiqRrduuVXhpSPQnc,541
118
+ digitalkin/utils/__init__.py,sha256=xusG_EoixTznt9ENCB8XkNbi67SZNt6y9hx5gsPWH_k,464
119
119
  digitalkin/utils/arg_parser.py,sha256=wzscRlE1Qp1gGl-lAJlkkwnbU1O2oezj6BwK_BZFBIk,3158
120
120
  digitalkin/utils/development_mode_action.py,sha256=2hznh0ajW_4ZTysfoc0Y49161f_PQPATRgNk8NAn1_o,1623
121
- digitalkin/utils/dynamic_schema.py,sha256=5-B3dBGlCYYv6uRJkgudtc0ZpBOTYxl0yKedDGsteZQ,15184
121
+ digitalkin/utils/dynamic_schema.py,sha256=y5csxjuqVHjWDpnTUzxbcUuI_wou9-ibRVHQlBs_btY,15275
122
122
  digitalkin/utils/llm_ready_schema.py,sha256=JjMug_lrQllqFoanaC091VgOqwAd-_YzcpqFlS7p778,2375
123
123
  digitalkin/utils/package_discover.py,sha256=sa6Zp5Kape1Zr4iYiNrnZxiHDnqM06ODk6yfWHom53w,13465
124
- digitalkin/utils/schema_splitter.py,sha256=KMvYRHDHlwdhh_c6FJxkWvLStZo9Kbj-jd3pIGPZfxk,9317
125
- digitalkin-0.3.2.dev16.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
124
+ digitalkin/utils/schema_splitter.py,sha256=9PHC-bvEDQudyYZNgXyjFtp7EJlmw4C_gPCJ-JmGDk0,9704
125
+ digitalkin-0.3.2.dev18.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
126
126
  modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
127
  modules/archetype_with_tools_module.py,sha256=PXTS6IXmC_OjxTmVrL_pYVI0MKwXjD5I1UJO_2xa10Q,7632
128
128
  modules/cpu_intensive_module.py,sha256=GZlirQDZdYuXrI46sv1q4RNAHZjL4EptHVQTvgK9zz8,8363
@@ -137,7 +137,7 @@ monitoring/digitalkin_observability/prometheus.py,sha256=gDmM9ySaVwPAe7Yg84pLxmE
137
137
  monitoring/tests/test_metrics.py,sha256=ugnYfAwqBPO6zA8z4afKTlyBWECTivacYSN-URQCn2E,5856
138
138
  services/filesystem_module.py,sha256=U4dgqtuDadaXz8PJ1d_uQ_1EPncBqudAQCLUICF9yL4,7421
139
139
  services/storage_module.py,sha256=Wz2MzLvqs2D_bnBBgtnujYcAKK2V2KFMk8K21RoepSE,6972
140
- digitalkin-0.3.2.dev16.dist-info/METADATA,sha256=NyIT_3xFye3d26AKLS7P383vexIBWTN-UlExTc8QWkM,29725
141
- digitalkin-0.3.2.dev16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
142
- digitalkin-0.3.2.dev16.dist-info/top_level.txt,sha256=AYVIesKrO0jnedQ-Muog9JBehG81WeTCNeOFoJgwsgE,51
143
- digitalkin-0.3.2.dev16.dist-info/RECORD,,
140
+ digitalkin-0.3.2.dev18.dist-info/METADATA,sha256=sJPrEQfDsIC_mcsZfFttiA3fybX3J7KbVT7zozw3qMw,29725
141
+ digitalkin-0.3.2.dev18.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
142
+ digitalkin-0.3.2.dev18.dist-info/top_level.txt,sha256=AYVIesKrO0jnedQ-Muog9JBehG81WeTCNeOFoJgwsgE,51
143
+ digitalkin-0.3.2.dev18.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5