digitalkin 0.3.2.dev15__py3-none-any.whl → 0.3.2.dev17__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 +1 -1
- digitalkin/models/module/__init__.py +10 -0
- digitalkin/models/module/module_context.py +85 -35
- digitalkin/models/module/setup_types.py +42 -27
- digitalkin/models/module/tool_cache.py +168 -13
- digitalkin/models/module/tool_reference.py +34 -17
- digitalkin/models/services/registry.py +42 -0
- digitalkin/modules/_base_module.py +6 -3
- digitalkin/services/filesystem/grpc_filesystem.py +1 -1
- digitalkin/services/registry/grpc_registry.py +57 -0
- digitalkin/services/registry/registry_strategy.py +6 -0
- digitalkin/utils/__init__.py +1 -3
- digitalkin/utils/dynamic_schema.py +4 -0
- {digitalkin-0.3.2.dev15.dist-info → digitalkin-0.3.2.dev17.dist-info}/METADATA +1 -1
- {digitalkin-0.3.2.dev15.dist-info → digitalkin-0.3.2.dev17.dist-info}/RECORD +18 -18
- {digitalkin-0.3.2.dev15.dist-info → digitalkin-0.3.2.dev17.dist-info}/WHEEL +0 -0
- {digitalkin-0.3.2.dev15.dist-info → digitalkin-0.3.2.dev17.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.3.2.dev15.dist-info → digitalkin-0.3.2.dev17.dist-info}/top_level.txt +0 -0
digitalkin/__version__.py
CHANGED
|
@@ -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, 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,63 +227,113 @@ class ModuleContext:
|
|
|
227
227
|
llm_format=llm_format,
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
-
async def
|
|
231
|
-
"""Create OpenAI-style function calling
|
|
230
|
+
async def create_openai_style_tools(self, tool_name: 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.
|
|
234
|
-
|
|
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
237
|
tool_name: Module ID to look up (checks cache first, then registry).
|
|
238
238
|
|
|
239
239
|
Returns:
|
|
240
|
-
OpenAI-style tool
|
|
240
|
+
List of OpenAI-style tool schemas, one per protocol. Empty if not found.
|
|
241
241
|
"""
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
tool_name, 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": "
|
|
254
|
-
"
|
|
255
|
-
|
|
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
|
|
278
|
+
def create_tool_functions(
|
|
263
279
|
self,
|
|
264
280
|
module_id: str,
|
|
265
|
-
) -> Callable[..., AsyncGenerator[dict, None]]
|
|
266
|
-
"""Create
|
|
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
|
-
|
|
269
|
-
|
|
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
|
|
291
|
+
module_id: Module ID to look up in cache.
|
|
273
292
|
|
|
274
293
|
Returns:
|
|
275
|
-
|
|
294
|
+
List of (ToolDefinition, async_generator_function) tuples. Empty if not found.
|
|
276
295
|
"""
|
|
277
|
-
|
|
278
|
-
if not
|
|
279
|
-
return
|
|
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 =
|
|
284
|
-
port =
|
|
302
|
+
address = tool_module_info.address
|
|
303
|
+
port = tool_module_info.port
|
|
304
|
+
|
|
305
|
+
result = []
|
|
306
|
+
for tool_def in tool_module_info.tools:
|
|
307
|
+
# Capture tool_def in closure via separate method
|
|
308
|
+
fn = ModuleContext._create_single_tool_function(communication, session, address, port, tool_def)
|
|
309
|
+
result.append((tool_def, fn))
|
|
310
|
+
|
|
311
|
+
return result
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def _create_single_tool_function(
|
|
315
|
+
communication: CommunicationStrategy,
|
|
316
|
+
session: Session,
|
|
317
|
+
address: str,
|
|
318
|
+
port: int,
|
|
319
|
+
tool_def: ToolDefinition,
|
|
320
|
+
) -> Callable[..., AsyncGenerator[dict, None]]:
|
|
321
|
+
"""Create a single tool function for a specific protocol.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
communication: Communication strategy for gRPC calls.
|
|
325
|
+
session: Current session with setup_id and mission_id.
|
|
326
|
+
address: Module address.
|
|
327
|
+
port: Module port.
|
|
328
|
+
tool_def: Tool definition with protocol name.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Async generator function that calls the module with protocol injected.
|
|
332
|
+
"""
|
|
333
|
+
protocol = tool_def.name
|
|
285
334
|
|
|
286
335
|
async def tool_function(**kwargs: Any) -> AsyncGenerator[dict, None]: # noqa: ANN401
|
|
336
|
+
kwargs["protocol"] = protocol
|
|
287
337
|
wrapped_input = {"root": kwargs}
|
|
288
338
|
async for response in communication.call_module(
|
|
289
339
|
module_address=address,
|
|
@@ -294,7 +344,7 @@ class ModuleContext:
|
|
|
294
344
|
):
|
|
295
345
|
yield response
|
|
296
346
|
|
|
297
|
-
tool_function.__name__ =
|
|
298
|
-
tool_function.__doc__ =
|
|
347
|
+
tool_function.__name__ = tool_def.name
|
|
348
|
+
tool_function.__doc__ = tool_def.description
|
|
299
349
|
|
|
300
350
|
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[
|
|
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] =
|
|
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(
|
|
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
|
|
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.
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
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[
|
|
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.
|
|
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,55 +1,107 @@
|
|
|
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
|
+
|
|
9
56
|
|
|
10
57
|
class ToolCache(BaseModel):
|
|
11
58
|
"""Registry cache storing resolved tool references by setup field name."""
|
|
12
59
|
|
|
13
|
-
entries: dict[str,
|
|
60
|
+
entries: dict[str, ToolModuleInfo] = Field(default_factory=dict)
|
|
14
61
|
|
|
15
|
-
def add(self, setup_tool_name: str,
|
|
62
|
+
def add(self, setup_tool_name: str, tool_module_info: ToolModuleInfo) -> None:
|
|
16
63
|
"""Add a tool to the cache.
|
|
17
64
|
|
|
18
65
|
Args:
|
|
19
66
|
setup_tool_name: Field name from SetupModel used as cache key.
|
|
20
|
-
|
|
67
|
+
tool_module_info: Resolved tool module information.
|
|
21
68
|
"""
|
|
22
|
-
self.entries[setup_tool_name] =
|
|
69
|
+
self.entries[setup_tool_name] = tool_module_info
|
|
23
70
|
logger.debug(
|
|
24
71
|
"Tool cached",
|
|
25
|
-
extra={"setup_tool_name": setup_tool_name, "module_id":
|
|
72
|
+
extra={"setup_tool_name": setup_tool_name, "module_id": tool_module_info.module_id},
|
|
26
73
|
)
|
|
27
74
|
|
|
28
|
-
def get(
|
|
75
|
+
async def get(
|
|
29
76
|
self,
|
|
30
77
|
setup_tool_name: str,
|
|
31
78
|
*,
|
|
32
79
|
registry: RegistryStrategy | None = None,
|
|
33
|
-
|
|
80
|
+
communication: "CommunicationStrategy | None" = None,
|
|
81
|
+
) -> ToolModuleInfo | None:
|
|
34
82
|
"""Get a tool from cache, optionally querying registry on miss.
|
|
35
83
|
|
|
36
84
|
Args:
|
|
37
85
|
setup_tool_name: Field name to look up.
|
|
38
86
|
registry: Optional registry to query on cache miss.
|
|
87
|
+
communication: Optional communication strategy for schema fetching.
|
|
39
88
|
|
|
40
89
|
Returns:
|
|
41
|
-
|
|
90
|
+
ToolModuleInfo if found, None otherwise.
|
|
42
91
|
"""
|
|
43
92
|
cached = self.entries.get(setup_tool_name)
|
|
44
93
|
if cached:
|
|
45
94
|
return cached
|
|
46
95
|
|
|
47
|
-
if registry:
|
|
96
|
+
if registry and communication:
|
|
48
97
|
try:
|
|
49
|
-
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
setup_info = registry.get_setup(setup_tool_name)
|
|
99
|
+
if setup_info and setup_info.module_id:
|
|
100
|
+
info = registry.discover_by_id(setup_info.module_id)
|
|
101
|
+
if info:
|
|
102
|
+
tool_info = await module_info_to_tool_module_info(info, communication)
|
|
103
|
+
self.add(setup_tool_name, tool_info)
|
|
104
|
+
return tool_info
|
|
53
105
|
except Exception:
|
|
54
106
|
logger.exception("Registry lookup failed", extra={"setup_tool_name": setup_tool_name})
|
|
55
107
|
|
|
@@ -66,3 +118,106 @@ class ToolCache(BaseModel):
|
|
|
66
118
|
List of setup field names in cache.
|
|
67
119
|
"""
|
|
68
120
|
return list(self.entries.keys())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def module_info_to_tool_module_info(
|
|
124
|
+
module_info: ModuleInfo,
|
|
125
|
+
communication: "CommunicationStrategy",
|
|
126
|
+
*,
|
|
127
|
+
llm_format: bool = True,
|
|
128
|
+
) -> ToolModuleInfo:
|
|
129
|
+
"""Convert ModuleInfo to ToolModuleInfo by fetching schemas via gRPC.
|
|
130
|
+
|
|
131
|
+
Fetches the module's input schema and extracts tool definitions from
|
|
132
|
+
the discriminated union structure.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
module_info: Module info from registry.
|
|
136
|
+
communication: Communication strategy for gRPC calls.
|
|
137
|
+
llm_format: Use LLM-friendly schema format.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
ToolModuleInfo with tools extracted from input schema.
|
|
141
|
+
"""
|
|
142
|
+
schemas = await communication.get_module_schemas(
|
|
143
|
+
module_info.address,
|
|
144
|
+
module_info.port,
|
|
145
|
+
llm_format=llm_format,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
input_schema = schemas.get("input", {})
|
|
149
|
+
if llm_format:
|
|
150
|
+
input_schema = input_schema.get("json_schema", input_schema)
|
|
151
|
+
|
|
152
|
+
tools = _extract_tools_from_schema(input_schema)
|
|
153
|
+
|
|
154
|
+
return ToolModuleInfo(
|
|
155
|
+
module_id=module_info.module_id,
|
|
156
|
+
module_type=module_info.module_type,
|
|
157
|
+
address=module_info.address,
|
|
158
|
+
port=module_info.port,
|
|
159
|
+
version=module_info.version,
|
|
160
|
+
name=module_info.name,
|
|
161
|
+
documentation=module_info.documentation,
|
|
162
|
+
status=module_info.status,
|
|
163
|
+
tools=tools,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _extract_tools_from_schema(schema: dict[str, Any]) -> list[ToolDefinition]:
|
|
168
|
+
"""Extract tool definitions from a discriminated union input schema.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
schema: JSON schema with $defs containing protocol-based types.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
List of ToolDefinition with parameters grouped by trigger.
|
|
175
|
+
"""
|
|
176
|
+
tools: list[ToolDefinition] = []
|
|
177
|
+
defs = schema.get("$defs", {})
|
|
178
|
+
|
|
179
|
+
# Skip SDK utility protocols
|
|
180
|
+
utility_protocols = {"HealthcheckPingInput", "HealthcheckServicesInput", "HealthcheckStatusInput"}
|
|
181
|
+
|
|
182
|
+
for def_name, def_schema in defs.items():
|
|
183
|
+
if def_name in utility_protocols:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
properties = def_schema.get("properties", {})
|
|
187
|
+
protocol_prop = properties.get("protocol", {})
|
|
188
|
+
|
|
189
|
+
# Skip if no protocol const (not a tool input type)
|
|
190
|
+
if "const" not in protocol_prop:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
# Extract tool-level info from trigger
|
|
194
|
+
tool_name = protocol_prop.get("const", def_name)
|
|
195
|
+
tool_description = def_schema.get("description", "")
|
|
196
|
+
|
|
197
|
+
required_fields = set(def_schema.get("required", []))
|
|
198
|
+
parameters: list[ToolParameter] = []
|
|
199
|
+
|
|
200
|
+
for prop_name, prop_info in properties.items():
|
|
201
|
+
if prop_name in {"protocol", "created_at"}:
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
param = ToolParameter(
|
|
205
|
+
name=prop_name,
|
|
206
|
+
type=prop_info.get("type", "string"),
|
|
207
|
+
description=prop_info.get("description", ""),
|
|
208
|
+
required=prop_name in required_fields,
|
|
209
|
+
enum=prop_info.get("enum"),
|
|
210
|
+
items=prop_info.get("items"),
|
|
211
|
+
properties=prop_info.get("properties"),
|
|
212
|
+
)
|
|
213
|
+
parameters.append(param)
|
|
214
|
+
|
|
215
|
+
tools.append(
|
|
216
|
+
ToolDefinition(
|
|
217
|
+
name=tool_name,
|
|
218
|
+
description=tool_description,
|
|
219
|
+
parameters=parameters,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
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.
|
|
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.
|
|
38
|
-
msg = "
|
|
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:
|
|
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.
|
|
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
|
|
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
|
-
|
|
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) ->
|
|
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
|
-
|
|
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.
|
|
101
|
-
|
|
102
|
-
if
|
|
103
|
-
self.
|
|
104
|
-
|
|
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, 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,9 @@ class ToolReference(BaseModel):
|
|
|
110
126
|
organization_id=self.config.organization_id,
|
|
111
127
|
)
|
|
112
128
|
if results:
|
|
113
|
-
|
|
114
|
-
self.
|
|
115
|
-
|
|
129
|
+
tool_module_info = await module_info_to_tool_module_info(results[0], communication)
|
|
130
|
+
self._cached_info = tool_module_info
|
|
131
|
+
self.config.module_id = tool_module_info.module_id
|
|
132
|
+
return tool_module_info
|
|
116
133
|
|
|
117
134
|
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(
|
|
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
|
|
@@ -97,7 +97,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper, GrpcErrorHandlerMixi
|
|
|
97
97
|
case "mission":
|
|
98
98
|
context_id = self.mission_id
|
|
99
99
|
return filesystem_pb2.FileFilter(
|
|
100
|
-
**filters.model_dump(exclude={"file_types", "status"}),
|
|
100
|
+
**filters.model_dump(exclude={"file_types", "status", "context"}),
|
|
101
101
|
file_types=[self._file_type_to_enum(file_type) for file_type in filters.file_types]
|
|
102
102
|
if filters.file_types
|
|
103
103
|
else None,
|
|
@@ -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
|
digitalkin/utils/__init__.py
CHANGED
|
@@ -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
|
|
|
@@ -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=
|
|
10
|
+
digitalkin/__version__.py,sha256=Y3kvX8EPStPR7XivYOXc5tCfaRbBYw08EWY8LQ_zjgM,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=
|
|
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=
|
|
56
|
+
digitalkin/models/module/module_context.py,sha256=u4yvvtYNwbSJSnV0omLasGN7wRZgaLS7trBBneeT2CI,12178
|
|
57
57
|
digitalkin/models/module/module_types.py,sha256=C9azCNBk76xMa-Mww8_6AiwQR8MLAsEyUOvBYxytovI,739
|
|
58
|
-
digitalkin/models/module/setup_types.py,sha256=
|
|
59
|
-
digitalkin/models/module/tool_cache.py,sha256=
|
|
60
|
-
digitalkin/models/module/tool_reference.py,sha256=
|
|
58
|
+
digitalkin/models/module/setup_types.py,sha256=namyC-0iA4ryHqKCzjYsVYeFDduwMqYCYRmQSSH8414,19356
|
|
59
|
+
digitalkin/models/module/tool_cache.py,sha256=bWZAjL2UMJIpQw0SeoOGa0S1flU6V8eKUYe09mluNog,7111
|
|
60
|
+
digitalkin/models/module/tool_reference.py,sha256=DBOw22_ZtmOwooejl2J8QZ_gRazNkhGyeD-CvnMpHvE,4388
|
|
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=
|
|
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=
|
|
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
|
|
@@ -90,16 +90,16 @@ digitalkin/services/cost/grpc_cost.py,sha256=2xJ3baTNubCnawpbNftTuFVnPpDZPouSrUW
|
|
|
90
90
|
digitalkin/services/filesystem/__init__.py,sha256=BhwMl_BUvM0d65fmglkp0SVwn3RfYiUOKJgIMnOCaGM,381
|
|
91
91
|
digitalkin/services/filesystem/default_filesystem.py,sha256=WQbU-Bsi9r-28VqhKbrplce3otzjSKS-5iqKEpGWdQU,15117
|
|
92
92
|
digitalkin/services/filesystem/filesystem_strategy.py,sha256=zibVLvX_IBQ-kgh-KYzHdszDeiHFPEAZszu_k99x1GQ,9487
|
|
93
|
-
digitalkin/services/filesystem/grpc_filesystem.py,sha256=
|
|
93
|
+
digitalkin/services/filesystem/grpc_filesystem.py,sha256=7vUR0n8_1KmG35mXsSWgfFRIGsb6_m9A3NAOIHDfHyw,12384
|
|
94
94
|
digitalkin/services/identity/__init__.py,sha256=InkeyLgFYYwItx8mePA8HpfacOMWZwwuc0G4pWtKq9s,270
|
|
95
95
|
digitalkin/services/identity/default_identity.py,sha256=Y2auZHrGSZTIN5D8HyjLvLcNbYFM1CNUE23x7p5VIGw,386
|
|
96
96
|
digitalkin/services/identity/identity_strategy.py,sha256=skappBbds1_qa0Gr24FGrNX1N0_OYhYT1Lh7dUaAirE,429
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
124
|
digitalkin/utils/schema_splitter.py,sha256=KMvYRHDHlwdhh_c6FJxkWvLStZo9Kbj-jd3pIGPZfxk,9317
|
|
125
|
-
digitalkin-0.3.2.
|
|
125
|
+
digitalkin-0.3.2.dev17.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.
|
|
141
|
-
digitalkin-0.3.2.
|
|
142
|
-
digitalkin-0.3.2.
|
|
143
|
-
digitalkin-0.3.2.
|
|
140
|
+
digitalkin-0.3.2.dev17.dist-info/METADATA,sha256=rhqhGY-Zx3iq_pSrr0nRpsygx7UhYYsVLjUoVDzXMq8,29725
|
|
141
|
+
digitalkin-0.3.2.dev17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
142
|
+
digitalkin-0.3.2.dev17.dist-info/top_level.txt,sha256=AYVIesKrO0jnedQ-Muog9JBehG81WeTCNeOFoJgwsgE,51
|
|
143
|
+
digitalkin-0.3.2.dev17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|