agent_os_kernel 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
amb_core/dlq.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Dead Letter Queue (DLQ) for AMB.
|
|
4
|
+
|
|
5
|
+
This module provides DLQ functionality for handling failed messages.
|
|
6
|
+
Messages that cannot be processed are moved to the DLQ for investigation
|
|
7
|
+
and potential retry.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any, Dict, List, Optional, Callable, Awaitable
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from collections import defaultdict
|
|
15
|
+
import asyncio
|
|
16
|
+
|
|
17
|
+
from amb_core.models import Message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DLQReason(str, Enum):
|
|
21
|
+
"""Reasons why a message was moved to DLQ."""
|
|
22
|
+
HANDLER_ERROR = "handler_error" # Handler raised an exception
|
|
23
|
+
VALIDATION_ERROR = "validation_error" # Schema validation failed
|
|
24
|
+
EXPIRED = "expired" # Message TTL exceeded
|
|
25
|
+
MAX_RETRIES = "max_retries" # Maximum retry attempts exceeded
|
|
26
|
+
REJECTED = "rejected" # Explicitly rejected by handler
|
|
27
|
+
UNKNOWN = "unknown" # Unknown error
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class DLQEntry:
|
|
32
|
+
"""
|
|
33
|
+
Entry in the Dead Letter Queue.
|
|
34
|
+
|
|
35
|
+
Contains the original message plus metadata about the failure.
|
|
36
|
+
"""
|
|
37
|
+
message: Message
|
|
38
|
+
reason: DLQReason
|
|
39
|
+
error_message: str
|
|
40
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
41
|
+
retry_count: int = 0
|
|
42
|
+
original_topic: str = ""
|
|
43
|
+
stack_trace: Optional[str] = None
|
|
44
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
def __post_init__(self):
|
|
47
|
+
if not self.original_topic:
|
|
48
|
+
self.original_topic = self.message.topic
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
51
|
+
"""Convert entry to dictionary for serialization."""
|
|
52
|
+
return {
|
|
53
|
+
"message": self.message.model_dump(),
|
|
54
|
+
"reason": self.reason.value,
|
|
55
|
+
"error_message": self.error_message,
|
|
56
|
+
"timestamp": self.timestamp.isoformat(),
|
|
57
|
+
"retry_count": self.retry_count,
|
|
58
|
+
"original_topic": self.original_topic,
|
|
59
|
+
"stack_trace": self.stack_trace,
|
|
60
|
+
"metadata": self.metadata
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Type alias for DLQ handler
|
|
65
|
+
DLQHandler = Callable[[DLQEntry], Awaitable[None]]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DeadLetterQueue:
|
|
69
|
+
"""
|
|
70
|
+
Dead Letter Queue for managing failed messages.
|
|
71
|
+
|
|
72
|
+
Provides storage, retrieval, and retry capabilities for messages
|
|
73
|
+
that failed to process successfully.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
dlq = DeadLetterQueue(max_size=1000)
|
|
77
|
+
|
|
78
|
+
# Add failed message
|
|
79
|
+
entry = DLQEntry(
|
|
80
|
+
message=failed_message,
|
|
81
|
+
reason=DLQReason.HANDLER_ERROR,
|
|
82
|
+
error_message="Connection timeout"
|
|
83
|
+
)
|
|
84
|
+
await dlq.add(entry)
|
|
85
|
+
|
|
86
|
+
# Get entries for investigation
|
|
87
|
+
entries = await dlq.get_entries(topic="fraud.alerts")
|
|
88
|
+
|
|
89
|
+
# Retry a message
|
|
90
|
+
await dlq.retry(entry.message.id, retry_handler)
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
max_size: int = 10000,
|
|
96
|
+
max_retries: int = 3,
|
|
97
|
+
on_entry_added: Optional[DLQHandler] = None
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Initialize the Dead Letter Queue.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
max_size: Maximum number of entries to store
|
|
104
|
+
max_retries: Maximum retry attempts before permanent failure
|
|
105
|
+
on_entry_added: Optional callback when entry is added
|
|
106
|
+
"""
|
|
107
|
+
self._entries: Dict[str, DLQEntry] = {} # message_id -> entry
|
|
108
|
+
self._topic_index: Dict[str, List[str]] = defaultdict(list) # topic -> [message_ids]
|
|
109
|
+
self._max_size = max_size
|
|
110
|
+
self._max_retries = max_retries
|
|
111
|
+
self._on_entry_added = on_entry_added
|
|
112
|
+
self._lock = asyncio.Lock()
|
|
113
|
+
|
|
114
|
+
async def add(self, entry: DLQEntry) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Add an entry to the DLQ.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
entry: DLQ entry to add
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if added, False if DLQ is full and oldest entry was evicted
|
|
123
|
+
"""
|
|
124
|
+
async with self._lock:
|
|
125
|
+
message_id = entry.message.id
|
|
126
|
+
evicted = False
|
|
127
|
+
|
|
128
|
+
# Evict oldest if at capacity
|
|
129
|
+
if len(self._entries) >= self._max_size and message_id not in self._entries:
|
|
130
|
+
oldest_id = next(iter(self._entries))
|
|
131
|
+
oldest_entry = self._entries[oldest_id]
|
|
132
|
+
del self._entries[oldest_id]
|
|
133
|
+
if oldest_id in self._topic_index.get(oldest_entry.original_topic, []):
|
|
134
|
+
self._topic_index[oldest_entry.original_topic].remove(oldest_id)
|
|
135
|
+
evicted = True
|
|
136
|
+
|
|
137
|
+
# Add or update entry
|
|
138
|
+
self._entries[message_id] = entry
|
|
139
|
+
if message_id not in self._topic_index[entry.original_topic]:
|
|
140
|
+
self._topic_index[entry.original_topic].append(message_id)
|
|
141
|
+
|
|
142
|
+
# Call handler outside lock
|
|
143
|
+
if self._on_entry_added:
|
|
144
|
+
try:
|
|
145
|
+
await self._on_entry_added(entry)
|
|
146
|
+
except Exception:
|
|
147
|
+
pass # Don't fail on handler errors
|
|
148
|
+
|
|
149
|
+
return not evicted
|
|
150
|
+
|
|
151
|
+
async def get(self, message_id: str) -> Optional[DLQEntry]:
|
|
152
|
+
"""
|
|
153
|
+
Get a specific DLQ entry by message ID.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
message_id: The message ID to look up
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
DLQ entry or None if not found
|
|
160
|
+
"""
|
|
161
|
+
return self._entries.get(message_id)
|
|
162
|
+
|
|
163
|
+
async def get_entries(
|
|
164
|
+
self,
|
|
165
|
+
topic: Optional[str] = None,
|
|
166
|
+
reason: Optional[DLQReason] = None,
|
|
167
|
+
limit: int = 100,
|
|
168
|
+
offset: int = 0
|
|
169
|
+
) -> List[DLQEntry]:
|
|
170
|
+
"""
|
|
171
|
+
Get DLQ entries with optional filtering.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
topic: Filter by original topic
|
|
175
|
+
reason: Filter by failure reason
|
|
176
|
+
limit: Maximum entries to return
|
|
177
|
+
offset: Number of entries to skip
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of DLQ entries
|
|
181
|
+
"""
|
|
182
|
+
if topic:
|
|
183
|
+
message_ids = self._topic_index.get(topic, [])
|
|
184
|
+
entries = [self._entries[mid] for mid in message_ids if mid in self._entries]
|
|
185
|
+
else:
|
|
186
|
+
entries = list(self._entries.values())
|
|
187
|
+
|
|
188
|
+
# Filter by reason if specified
|
|
189
|
+
if reason:
|
|
190
|
+
entries = [e for e in entries if e.reason == reason]
|
|
191
|
+
|
|
192
|
+
# Sort by timestamp (newest first)
|
|
193
|
+
entries.sort(key=lambda e: e.timestamp, reverse=True)
|
|
194
|
+
|
|
195
|
+
# Apply pagination
|
|
196
|
+
return entries[offset:offset + limit]
|
|
197
|
+
|
|
198
|
+
async def remove(self, message_id: str) -> bool:
|
|
199
|
+
"""
|
|
200
|
+
Remove an entry from the DLQ.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
message_id: Message ID to remove
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
True if removed, False if not found
|
|
207
|
+
"""
|
|
208
|
+
async with self._lock:
|
|
209
|
+
if message_id not in self._entries:
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
entry = self._entries[message_id]
|
|
213
|
+
del self._entries[message_id]
|
|
214
|
+
|
|
215
|
+
if message_id in self._topic_index.get(entry.original_topic, []):
|
|
216
|
+
self._topic_index[entry.original_topic].remove(message_id)
|
|
217
|
+
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
async def retry(
|
|
221
|
+
self,
|
|
222
|
+
message_id: str,
|
|
223
|
+
handler: Callable[[Message], Awaitable[None]]
|
|
224
|
+
) -> bool:
|
|
225
|
+
"""
|
|
226
|
+
Retry processing a failed message.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
message_id: Message ID to retry
|
|
230
|
+
handler: Handler function to process the message
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if successful, False if failed or not found
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
ValueError: If max retries exceeded
|
|
237
|
+
"""
|
|
238
|
+
entry = await self.get(message_id)
|
|
239
|
+
if not entry:
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
if entry.retry_count >= self._max_retries:
|
|
243
|
+
raise ValueError(
|
|
244
|
+
f"Message {message_id} has exceeded max retries ({self._max_retries})"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
await handler(entry.message)
|
|
249
|
+
await self.remove(message_id)
|
|
250
|
+
return True
|
|
251
|
+
except Exception as e:
|
|
252
|
+
# Update retry count
|
|
253
|
+
entry.retry_count += 1
|
|
254
|
+
entry.error_message = str(e)
|
|
255
|
+
if entry.retry_count >= self._max_retries:
|
|
256
|
+
entry.reason = DLQReason.MAX_RETRIES
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
async def retry_all(
|
|
260
|
+
self,
|
|
261
|
+
handler: Callable[[Message], Awaitable[None]],
|
|
262
|
+
topic: Optional[str] = None
|
|
263
|
+
) -> Dict[str, bool]:
|
|
264
|
+
"""
|
|
265
|
+
Retry all messages in the DLQ.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
handler: Handler function to process messages
|
|
269
|
+
topic: Optional topic filter
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dict mapping message IDs to success/failure
|
|
273
|
+
"""
|
|
274
|
+
entries = await self.get_entries(topic=topic, limit=self._max_size)
|
|
275
|
+
results = {}
|
|
276
|
+
|
|
277
|
+
for entry in entries:
|
|
278
|
+
try:
|
|
279
|
+
results[entry.message.id] = await self.retry(entry.message.id, handler)
|
|
280
|
+
except ValueError:
|
|
281
|
+
results[entry.message.id] = False
|
|
282
|
+
|
|
283
|
+
return results
|
|
284
|
+
|
|
285
|
+
async def clear(self, topic: Optional[str] = None) -> int:
|
|
286
|
+
"""
|
|
287
|
+
Clear entries from the DLQ.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
topic: Optional topic filter (clears all if None)
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Number of entries cleared
|
|
294
|
+
"""
|
|
295
|
+
async with self._lock:
|
|
296
|
+
if topic:
|
|
297
|
+
message_ids = self._topic_index.get(topic, []).copy()
|
|
298
|
+
for mid in message_ids:
|
|
299
|
+
if mid in self._entries:
|
|
300
|
+
del self._entries[mid]
|
|
301
|
+
self._topic_index[topic].clear()
|
|
302
|
+
return len(message_ids)
|
|
303
|
+
else:
|
|
304
|
+
count = len(self._entries)
|
|
305
|
+
self._entries.clear()
|
|
306
|
+
self._topic_index.clear()
|
|
307
|
+
return count
|
|
308
|
+
|
|
309
|
+
def __len__(self) -> int:
|
|
310
|
+
"""Get number of entries in DLQ."""
|
|
311
|
+
return len(self._entries)
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def size(self) -> int:
|
|
315
|
+
"""Get current size of DLQ."""
|
|
316
|
+
return len(self._entries)
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def max_size(self) -> int:
|
|
320
|
+
"""Get maximum size of DLQ."""
|
|
321
|
+
return self._max_size
|
|
322
|
+
|
|
323
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
324
|
+
"""
|
|
325
|
+
Get DLQ statistics.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dict with DLQ statistics
|
|
329
|
+
"""
|
|
330
|
+
entries = list(self._entries.values())
|
|
331
|
+
|
|
332
|
+
reason_counts = defaultdict(int)
|
|
333
|
+
topic_counts = defaultdict(int)
|
|
334
|
+
|
|
335
|
+
for entry in entries:
|
|
336
|
+
reason_counts[entry.reason.value] += 1
|
|
337
|
+
topic_counts[entry.original_topic] += 1
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
"total_entries": len(entries),
|
|
341
|
+
"max_size": self._max_size,
|
|
342
|
+
"by_reason": dict(reason_counts),
|
|
343
|
+
"by_topic": dict(topic_counts),
|
|
344
|
+
"max_retries_setting": self._max_retries
|
|
345
|
+
}
|