omnibase_infra 0.2.1__py3-none-any.whl → 0.2.3__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.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
- omnibase_infra/capabilities/__init__.py +15 -0
- omnibase_infra/capabilities/capability_inference_rules.py +211 -0
- omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
- omnibase_infra/capabilities/intent_type_extractor.py +160 -0
- omnibase_infra/cli/commands.py +1 -1
- omnibase_infra/configs/widget_mapping.yaml +176 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
- omnibase_infra/enums/__init__.py +6 -0
- omnibase_infra/enums/enum_handler_error_type.py +10 -0
- omnibase_infra/enums/enum_handler_source_mode.py +72 -0
- omnibase_infra/enums/enum_kafka_acks.py +99 -0
- omnibase_infra/errors/error_compute_registry.py +4 -1
- omnibase_infra/errors/error_event_bus_registry.py +4 -1
- omnibase_infra/errors/error_infra.py +3 -1
- omnibase_infra/errors/error_policy_registry.py +4 -1
- omnibase_infra/event_bus/event_bus_kafka.py +1 -1
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
- omnibase_infra/handlers/__init__.py +8 -1
- omnibase_infra/handlers/handler_consul.py +7 -1
- omnibase_infra/handlers/handler_db.py +10 -3
- omnibase_infra/handlers/handler_graph.py +10 -5
- omnibase_infra/handlers/handler_http.py +8 -2
- omnibase_infra/handlers/handler_intent.py +387 -0
- omnibase_infra/handlers/handler_mcp.py +745 -63
- omnibase_infra/handlers/handler_vault.py +11 -5
- omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
- omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
- omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
- omnibase_infra/mixins/mixin_node_introspection.py +42 -7
- omnibase_infra/mixins/mixin_retry_execution.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +11 -0
- omnibase_infra/models/handlers/__init__.py +48 -5
- omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
- omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
- omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
- omnibase_infra/models/mcp/__init__.py +15 -0
- omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
- omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
- omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
- omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
- omnibase_infra/models/registration/model_node_capabilities.py +11 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
- omnibase_infra/models/runtime/model_handler_contract.py +25 -9
- omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
- omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
- omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
- omnibase_infra/nodes/effects/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
- omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
- omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
- omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
- omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
- omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
- omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
- omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
- omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
- omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
- omnibase_infra/plugins/plugin_compute_base.py +16 -2
- omnibase_infra/protocols/__init__.py +2 -0
- omnibase_infra/protocols/protocol_container_aware.py +200 -0
- omnibase_infra/protocols/protocol_event_projector.py +1 -1
- omnibase_infra/runtime/__init__.py +90 -1
- omnibase_infra/runtime/binding_config_resolver.py +102 -37
- omnibase_infra/runtime/constants_notification.py +75 -0
- omnibase_infra/runtime/contract_handler_discovery.py +6 -1
- omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
- omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
- omnibase_infra/runtime/handler_contract_source.py +267 -186
- omnibase_infra/runtime/handler_identity.py +81 -0
- omnibase_infra/runtime/handler_plugin_loader.py +19 -2
- omnibase_infra/runtime/handler_registry.py +11 -3
- omnibase_infra/runtime/handler_source_resolver.py +326 -0
- omnibase_infra/runtime/mixin_semver_cache.py +25 -1
- omnibase_infra/runtime/mixins/__init__.py +7 -0
- omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
- omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
- omnibase_infra/runtime/models/__init__.py +24 -0
- omnibase_infra/runtime/models/model_health_check_result.py +2 -1
- omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
- omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
- omnibase_infra/runtime/projector_plugin_loader.py +1 -1
- omnibase_infra/runtime/projector_shell.py +229 -1
- omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
- omnibase_infra/runtime/protocols/__init__.py +10 -0
- omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
- omnibase_infra/runtime/registry_contract_source.py +693 -0
- omnibase_infra/runtime/registry_policy.py +9 -326
- omnibase_infra/runtime/secret_resolver.py +4 -2
- omnibase_infra/runtime/service_kernel.py +11 -3
- omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
- omnibase_infra/runtime/service_runtime_host_process.py +589 -106
- omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
- omnibase_infra/runtime/transition_notification_publisher.py +764 -0
- omnibase_infra/runtime/util_container_wiring.py +6 -5
- omnibase_infra/runtime/util_wiring.py +17 -4
- omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
- omnibase_infra/services/__init__.py +21 -0
- omnibase_infra/services/corpus_capture.py +7 -1
- omnibase_infra/services/mcp/__init__.py +31 -0
- omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
- omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
- omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
- omnibase_infra/services/registry_api/__init__.py +40 -0
- omnibase_infra/services/registry_api/main.py +261 -0
- omnibase_infra/services/registry_api/models/__init__.py +66 -0
- omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
- omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
- omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
- omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
- omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
- omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
- omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
- omnibase_infra/services/registry_api/models/model_warning.py +49 -0
- omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
- omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
- omnibase_infra/services/registry_api/routes.py +371 -0
- omnibase_infra/services/registry_api/service.py +837 -0
- omnibase_infra/services/service_capability_query.py +4 -4
- omnibase_infra/services/service_health.py +3 -2
- omnibase_infra/services/service_timeout_emitter.py +20 -3
- omnibase_infra/services/service_timeout_scanner.py +7 -3
- omnibase_infra/services/session/__init__.py +56 -0
- omnibase_infra/services/session/config_consumer.py +120 -0
- omnibase_infra/services/session/config_store.py +139 -0
- omnibase_infra/services/session/consumer.py +1007 -0
- omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
- omnibase_infra/services/session/store.py +997 -0
- omnibase_infra/utils/__init__.py +19 -0
- omnibase_infra/utils/util_atomic_file.py +261 -0
- omnibase_infra/utils/util_db_transaction.py +239 -0
- omnibase_infra/utils/util_dsn_validation.py +1 -1
- omnibase_infra/utils/util_retry_optimistic.py +281 -0
- omnibase_infra/validation/__init__.py +3 -19
- omnibase_infra/validation/contracts/security.validation.yaml +114 -0
- omnibase_infra/validation/infra_validators.py +35 -24
- omnibase_infra/validation/validation_exemptions.yaml +140 -9
- omnibase_infra/validation/validator_chain_propagation.py +2 -2
- omnibase_infra/validation/validator_runtime_shape.py +1 -1
- omnibase_infra/validation/validator_security.py +473 -370
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Intent Handler - Temporary demo wiring for intent graph operations.
|
|
4
|
+
|
|
5
|
+
Wraps HandlerGraph to provide intent-specific graph operations for the demo.
|
|
6
|
+
This is temporary hardcoded routing that will be replaced by contract-driven
|
|
7
|
+
handler routing in production.
|
|
8
|
+
|
|
9
|
+
Supported Operations:
|
|
10
|
+
- intent.store: Store an intent as a graph node with label "Intent"
|
|
11
|
+
- intent.query_session: Query intents by session_id property
|
|
12
|
+
- intent.query_distribution: Get intent count/statistics
|
|
13
|
+
|
|
14
|
+
Note:
|
|
15
|
+
This is TEMPORARY demo wiring. Keep it simple and focused on the demo use case.
|
|
16
|
+
Production implementation should use contract-driven handler routing.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# TODO(OMN-1515): Remove demo wiring after intent routing is contract-driven
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from uuid import UUID, uuid4
|
|
25
|
+
|
|
26
|
+
from omnibase_core.container import ModelONEXContainer
|
|
27
|
+
from omnibase_core.types import JsonType
|
|
28
|
+
from omnibase_infra.enums import EnumInfraTransportType
|
|
29
|
+
from omnibase_infra.errors import (
|
|
30
|
+
ModelInfraErrorContext,
|
|
31
|
+
RuntimeHostError,
|
|
32
|
+
)
|
|
33
|
+
from omnibase_infra.handlers.handler_graph import HandlerGraph
|
|
34
|
+
from omnibase_infra.mixins import MixinEnvelopeExtraction
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
HANDLER_ID_INTENT: str = "intent-handler"
|
|
39
|
+
_SUPPORTED_OPERATIONS: frozenset[str] = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"intent.store",
|
|
42
|
+
"intent.query_session",
|
|
43
|
+
"intent.query_distribution",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class HandlerIntent(MixinEnvelopeExtraction): # DEMO ONLY
|
|
49
|
+
"""Intent handler wrapping HandlerGraph for intent-specific operations.
|
|
50
|
+
|
|
51
|
+
This handler provides a simplified interface for storing and querying
|
|
52
|
+
intents in the graph database. It wraps HandlerGraph and translates
|
|
53
|
+
intent-specific operations to graph operations.
|
|
54
|
+
|
|
55
|
+
Note:
|
|
56
|
+
This is temporary demo wiring. The handler assumes HandlerGraph
|
|
57
|
+
is already initialized and passed via config during initialize().
|
|
58
|
+
|
|
59
|
+
Idempotency:
|
|
60
|
+
- intent.store: NOT idempotent (creates new node each call)
|
|
61
|
+
- intent.query_session: Idempotent (read-only query)
|
|
62
|
+
- intent.query_distribution: Idempotent (read-only aggregation)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, container: ModelONEXContainer) -> None:
|
|
66
|
+
"""Initialize HandlerIntent with ONEX container for dependency injection.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
container: ONEX container for dependency injection.
|
|
70
|
+
"""
|
|
71
|
+
self._container = container
|
|
72
|
+
self._graph_handler: HandlerGraph | None = None
|
|
73
|
+
self._initialized: bool = False
|
|
74
|
+
|
|
75
|
+
async def initialize(self, config: dict[str, object]) -> None:
|
|
76
|
+
"""Initialize the intent handler.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
config: Configuration dict containing:
|
|
80
|
+
- graph_handler: Pre-initialized HandlerGraph instance (required)
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
RuntimeHostError: If graph_handler is missing or invalid.
|
|
84
|
+
"""
|
|
85
|
+
init_correlation_id = uuid4()
|
|
86
|
+
|
|
87
|
+
logger.info(
|
|
88
|
+
"Initializing %s",
|
|
89
|
+
self.__class__.__name__,
|
|
90
|
+
extra={
|
|
91
|
+
"handler": self.__class__.__name__,
|
|
92
|
+
"correlation_id": str(init_correlation_id),
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
graph_handler = config.get("graph_handler")
|
|
97
|
+
if not isinstance(graph_handler, HandlerGraph):
|
|
98
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
99
|
+
transport_type=EnumInfraTransportType.GRAPH,
|
|
100
|
+
operation="initialize",
|
|
101
|
+
target_name="intent_handler",
|
|
102
|
+
correlation_id=init_correlation_id,
|
|
103
|
+
)
|
|
104
|
+
raise RuntimeHostError(
|
|
105
|
+
"Missing or invalid 'graph_handler' in config - "
|
|
106
|
+
"must be an initialized HandlerGraph instance",
|
|
107
|
+
context=ctx,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
self._graph_handler = graph_handler
|
|
111
|
+
self._initialized = True
|
|
112
|
+
|
|
113
|
+
logger.info(
|
|
114
|
+
"%s initialized successfully",
|
|
115
|
+
self.__class__.__name__,
|
|
116
|
+
extra={
|
|
117
|
+
"handler": self.__class__.__name__,
|
|
118
|
+
"correlation_id": str(init_correlation_id),
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
async def shutdown(self) -> None:
|
|
123
|
+
"""Shutdown the intent handler.
|
|
124
|
+
|
|
125
|
+
Note:
|
|
126
|
+
This handler does not own the graph handler, so we do not
|
|
127
|
+
shut it down here. The caller is responsible for managing
|
|
128
|
+
the graph handler lifecycle.
|
|
129
|
+
"""
|
|
130
|
+
self._graph_handler = None
|
|
131
|
+
self._initialized = False
|
|
132
|
+
logger.info("HandlerIntent shutdown complete")
|
|
133
|
+
|
|
134
|
+
async def execute(self, envelope: dict[str, object]) -> dict[str, object]:
|
|
135
|
+
"""Execute intent operation from envelope.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
envelope: Request envelope containing:
|
|
139
|
+
- operation: Intent operation (intent.store, intent.query_session, etc.)
|
|
140
|
+
- payload: dict with operation-specific parameters
|
|
141
|
+
- correlation_id: Optional correlation ID for tracing
|
|
142
|
+
- envelope_id: Optional envelope ID for causality tracking
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
dict containing operation result with:
|
|
146
|
+
- success: bool indicating operation success
|
|
147
|
+
- data: Operation-specific result data
|
|
148
|
+
- correlation_id: UUID string for tracing
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
RuntimeHostError: If handler not initialized or invalid input.
|
|
152
|
+
"""
|
|
153
|
+
correlation_id = self._extract_correlation_id(envelope)
|
|
154
|
+
|
|
155
|
+
if not self._initialized or self._graph_handler is None:
|
|
156
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
157
|
+
transport_type=EnumInfraTransportType.GRAPH,
|
|
158
|
+
operation="execute",
|
|
159
|
+
target_name="intent_handler",
|
|
160
|
+
correlation_id=correlation_id,
|
|
161
|
+
)
|
|
162
|
+
raise RuntimeHostError(
|
|
163
|
+
"HandlerIntent not initialized. Call initialize() first.",
|
|
164
|
+
context=ctx,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
operation = envelope.get("operation")
|
|
168
|
+
if not isinstance(operation, str):
|
|
169
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
170
|
+
transport_type=EnumInfraTransportType.GRAPH,
|
|
171
|
+
operation="execute",
|
|
172
|
+
target_name="intent_handler",
|
|
173
|
+
correlation_id=correlation_id,
|
|
174
|
+
)
|
|
175
|
+
raise RuntimeHostError(
|
|
176
|
+
"Missing or invalid 'operation' in envelope",
|
|
177
|
+
context=ctx,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if operation not in _SUPPORTED_OPERATIONS:
|
|
181
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
182
|
+
transport_type=EnumInfraTransportType.GRAPH,
|
|
183
|
+
operation=operation,
|
|
184
|
+
target_name="intent_handler",
|
|
185
|
+
correlation_id=correlation_id,
|
|
186
|
+
)
|
|
187
|
+
raise RuntimeHostError(
|
|
188
|
+
f"Operation '{operation}' not supported. "
|
|
189
|
+
f"Available: {', '.join(sorted(_SUPPORTED_OPERATIONS))}",
|
|
190
|
+
context=ctx,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
payload = envelope.get("payload")
|
|
194
|
+
if not isinstance(payload, dict):
|
|
195
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
196
|
+
transport_type=EnumInfraTransportType.GRAPH,
|
|
197
|
+
operation=operation,
|
|
198
|
+
target_name="intent_handler",
|
|
199
|
+
correlation_id=correlation_id,
|
|
200
|
+
)
|
|
201
|
+
raise RuntimeHostError(
|
|
202
|
+
"Missing or invalid 'payload' in envelope",
|
|
203
|
+
context=ctx,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Route to appropriate handler method
|
|
207
|
+
if operation == "intent.store":
|
|
208
|
+
return await self._store_intent(payload, correlation_id)
|
|
209
|
+
elif operation == "intent.query_session":
|
|
210
|
+
return await self._query_session(payload, correlation_id)
|
|
211
|
+
else: # intent.query_distribution
|
|
212
|
+
return await self._query_distribution(correlation_id)
|
|
213
|
+
|
|
214
|
+
async def _store_intent(
|
|
215
|
+
self, payload: dict[str, object], correlation_id: UUID
|
|
216
|
+
) -> dict[str, object]:
|
|
217
|
+
"""Store an intent as a graph node.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
payload: Intent data to store. Should contain:
|
|
221
|
+
- intent_type: Type of intent (required)
|
|
222
|
+
- session_id: Session identifier (optional)
|
|
223
|
+
- Additional properties as needed
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
dict with created node details.
|
|
227
|
+
"""
|
|
228
|
+
# Note: _graph_handler is guaranteed non-None by execute() validation
|
|
229
|
+
assert self._graph_handler is not None # Type narrowing for mypy
|
|
230
|
+
|
|
231
|
+
# Extract intent properties - use JsonType for graph compatibility
|
|
232
|
+
properties: dict[str, JsonType] = {
|
|
233
|
+
"correlation_id": str(correlation_id),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Copy all payload properties to node properties
|
|
237
|
+
for key, value in payload.items():
|
|
238
|
+
# Convert non-primitive types to strings for graph storage
|
|
239
|
+
# NOTE: Using tuple form for isinstance to avoid union validator flag
|
|
240
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
241
|
+
properties[key] = value
|
|
242
|
+
else:
|
|
243
|
+
properties[key] = str(value)
|
|
244
|
+
|
|
245
|
+
# Create the intent node
|
|
246
|
+
node = await self._graph_handler.create_node(
|
|
247
|
+
labels=["Intent"],
|
|
248
|
+
properties=properties,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"success": True,
|
|
253
|
+
"data": {
|
|
254
|
+
"node_id": node.id,
|
|
255
|
+
"element_id": node.element_id,
|
|
256
|
+
"labels": node.labels,
|
|
257
|
+
"properties": node.properties,
|
|
258
|
+
},
|
|
259
|
+
"correlation_id": str(correlation_id),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async def _query_session(
|
|
263
|
+
self, payload: dict[str, object], correlation_id: UUID
|
|
264
|
+
) -> dict[str, object]:
|
|
265
|
+
"""Query intents by session_id.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
payload: Query parameters. Should contain:
|
|
269
|
+
- session_id: Session identifier to filter by (required)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
dict with matching intent nodes.
|
|
273
|
+
"""
|
|
274
|
+
# Note: _graph_handler is guaranteed non-None by execute() validation
|
|
275
|
+
assert self._graph_handler is not None # Type narrowing for mypy
|
|
276
|
+
|
|
277
|
+
session_id = payload.get("session_id")
|
|
278
|
+
if not session_id:
|
|
279
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
280
|
+
transport_type=EnumInfraTransportType.GRAPH,
|
|
281
|
+
operation="intent.query_session",
|
|
282
|
+
target_name="intent_handler",
|
|
283
|
+
correlation_id=correlation_id,
|
|
284
|
+
)
|
|
285
|
+
raise RuntimeHostError(
|
|
286
|
+
"Missing 'session_id' in payload",
|
|
287
|
+
context=ctx,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Query intents by session_id
|
|
291
|
+
# SECURITY: Using parameterized query ($session_id) to prevent Cypher injection
|
|
292
|
+
query = """
|
|
293
|
+
MATCH (i:Intent {session_id: $session_id})
|
|
294
|
+
RETURN i, elementId(i) as eid, id(i) as nid
|
|
295
|
+
ORDER BY i.created_at DESC
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
result = await self._graph_handler.execute_query(
|
|
299
|
+
query=query,
|
|
300
|
+
parameters={"session_id": str(session_id)},
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Transform records to intent data
|
|
304
|
+
intents = []
|
|
305
|
+
for record in result.records:
|
|
306
|
+
node = record.get("i")
|
|
307
|
+
if node:
|
|
308
|
+
intents.append(
|
|
309
|
+
{
|
|
310
|
+
"node_id": str(record.get("nid", "")),
|
|
311
|
+
"element_id": str(record.get("eid", "")),
|
|
312
|
+
"properties": dict(node) if isinstance(node, dict) else {},
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
"success": True,
|
|
318
|
+
"data": {
|
|
319
|
+
"session_id": str(session_id),
|
|
320
|
+
"intents": intents,
|
|
321
|
+
"count": len(intents),
|
|
322
|
+
},
|
|
323
|
+
"correlation_id": str(correlation_id),
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async def _query_distribution(self, correlation_id: UUID) -> dict[str, object]:
|
|
327
|
+
"""Query intent distribution/statistics.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
dict with intent statistics including counts by intent_type.
|
|
331
|
+
"""
|
|
332
|
+
# Note: _graph_handler is guaranteed non-None by execute() validation
|
|
333
|
+
assert self._graph_handler is not None # Type narrowing for mypy
|
|
334
|
+
|
|
335
|
+
# Query total count
|
|
336
|
+
count_query = "MATCH (i:Intent) RETURN count(i) as total"
|
|
337
|
+
count_result = await self._graph_handler.execute_query(query=count_query)
|
|
338
|
+
|
|
339
|
+
total_count = 0
|
|
340
|
+
if count_result.records:
|
|
341
|
+
raw_total = count_result.records[0].get("total", 0)
|
|
342
|
+
total_count = int(raw_total) if isinstance(raw_total, int | float) else 0
|
|
343
|
+
|
|
344
|
+
# Query distribution by intent_type
|
|
345
|
+
distribution_query = """
|
|
346
|
+
MATCH (i:Intent)
|
|
347
|
+
RETURN i.intent_type as intent_type, count(i) as count
|
|
348
|
+
ORDER BY count DESC
|
|
349
|
+
"""
|
|
350
|
+
distribution_result = await self._graph_handler.execute_query(
|
|
351
|
+
query=distribution_query
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Build distribution dict
|
|
355
|
+
distribution: dict[str, int] = {}
|
|
356
|
+
for record in distribution_result.records:
|
|
357
|
+
intent_type = record.get("intent_type")
|
|
358
|
+
raw_count = record.get("count", 0)
|
|
359
|
+
if intent_type:
|
|
360
|
+
count_val = int(raw_count) if isinstance(raw_count, int | float) else 0
|
|
361
|
+
distribution[str(intent_type)] = count_val
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
"success": True,
|
|
365
|
+
"data": {
|
|
366
|
+
"total_count": total_count,
|
|
367
|
+
"distribution": distribution,
|
|
368
|
+
},
|
|
369
|
+
"correlation_id": str(correlation_id),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
def describe(self) -> dict[str, object]:
|
|
373
|
+
"""Return handler metadata and capabilities.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
dict containing handler information.
|
|
377
|
+
"""
|
|
378
|
+
return {
|
|
379
|
+
"handler_id": HANDLER_ID_INTENT,
|
|
380
|
+
"handler_type": "intent_handler",
|
|
381
|
+
"supported_operations": sorted(_SUPPORTED_OPERATIONS),
|
|
382
|
+
"initialized": self._initialized,
|
|
383
|
+
"version": "0.1.0-demo",
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
__all__: list[str] = ["HandlerIntent", "HANDLER_ID_INTENT"]
|