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
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""AWS SQS broker adapter for AMB.
|
|
4
|
+
|
|
5
|
+
Amazon SQS provides fully managed message queues for microservices,
|
|
6
|
+
distributed systems, and serverless applications.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from amb_core.broker import BrokerAdapter, MessageHandler
|
|
15
|
+
from amb_core.models import Message
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import aioboto3
|
|
19
|
+
from botocore.exceptions import ClientError
|
|
20
|
+
except ImportError:
|
|
21
|
+
raise ImportError(
|
|
22
|
+
"AWS SQS adapter requires 'aioboto3' package. "
|
|
23
|
+
"Install it with: pip install amb-core[aws]"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AWSSQSBroker(BrokerAdapter):
|
|
28
|
+
"""
|
|
29
|
+
AWS SQS broker adapter.
|
|
30
|
+
|
|
31
|
+
This adapter uses AWS SQS queues for reliable message delivery.
|
|
32
|
+
Supports both standard queues and FIFO queues for ordered processing.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```python
|
|
36
|
+
from amb_core.adapters import AWSSQSBroker
|
|
37
|
+
|
|
38
|
+
broker = AWSSQSBroker(
|
|
39
|
+
region_name="us-east-1",
|
|
40
|
+
queue_url="https://sqs.us-east-1.amazonaws.com/123456789/my-queue"
|
|
41
|
+
)
|
|
42
|
+
await broker.connect()
|
|
43
|
+
|
|
44
|
+
# Subscribe
|
|
45
|
+
async def handler(msg):
|
|
46
|
+
print(f"Received: {msg.payload}")
|
|
47
|
+
|
|
48
|
+
await broker.subscribe("agent.tasks", handler)
|
|
49
|
+
|
|
50
|
+
# Publish
|
|
51
|
+
await broker.publish(Message(topic="agent.tasks", payload={"task": "analyze"}))
|
|
52
|
+
```
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
region_name: str = "us-east-1",
|
|
58
|
+
queue_url: Optional[str] = None,
|
|
59
|
+
queue_name: str = "amb-messages",
|
|
60
|
+
use_fifo: bool = False,
|
|
61
|
+
aws_access_key_id: Optional[str] = None,
|
|
62
|
+
aws_secret_access_key: Optional[str] = None
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Initialize AWS SQS broker.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
region_name: AWS region
|
|
69
|
+
queue_url: Existing queue URL (optional)
|
|
70
|
+
queue_name: Queue name to create/use
|
|
71
|
+
use_fifo: Use FIFO queue for ordered delivery
|
|
72
|
+
aws_access_key_id: AWS access key (uses env vars if not provided)
|
|
73
|
+
aws_secret_access_key: AWS secret key (uses env vars if not provided)
|
|
74
|
+
"""
|
|
75
|
+
self.region_name = region_name
|
|
76
|
+
self.queue_url = queue_url
|
|
77
|
+
self.queue_name = queue_name
|
|
78
|
+
self.use_fifo = use_fifo
|
|
79
|
+
self.aws_access_key_id = aws_access_key_id
|
|
80
|
+
self.aws_secret_access_key = aws_secret_access_key
|
|
81
|
+
|
|
82
|
+
self._session = None
|
|
83
|
+
self._sqs = None
|
|
84
|
+
self._subscriptions: Dict[str, str] = {} # subscription_id -> topic
|
|
85
|
+
self._handlers: Dict[str, MessageHandler] = {}
|
|
86
|
+
self._tasks: set = set()
|
|
87
|
+
self._running = False
|
|
88
|
+
self._topic_queues: Dict[str, str] = {} # topic -> queue_url
|
|
89
|
+
|
|
90
|
+
async def connect(self) -> None:
|
|
91
|
+
"""Connect to AWS SQS."""
|
|
92
|
+
self._session = aioboto3.Session(
|
|
93
|
+
aws_access_key_id=self.aws_access_key_id,
|
|
94
|
+
aws_secret_access_key=self.aws_secret_access_key,
|
|
95
|
+
region_name=self.region_name
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self._running = True
|
|
99
|
+
|
|
100
|
+
# Get or create main queue
|
|
101
|
+
async with self._session.client('sqs') as sqs:
|
|
102
|
+
if not self.queue_url:
|
|
103
|
+
queue_name = f"{self.queue_name}.fifo" if self.use_fifo else self.queue_name
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# Try to get existing queue
|
|
107
|
+
response = await sqs.get_queue_url(QueueName=queue_name)
|
|
108
|
+
self.queue_url = response['QueueUrl']
|
|
109
|
+
except ClientError:
|
|
110
|
+
# Create new queue
|
|
111
|
+
attributes = {}
|
|
112
|
+
if self.use_fifo:
|
|
113
|
+
attributes['FifoQueue'] = 'true'
|
|
114
|
+
attributes['ContentBasedDeduplication'] = 'true'
|
|
115
|
+
|
|
116
|
+
response = await sqs.create_queue(
|
|
117
|
+
QueueName=queue_name,
|
|
118
|
+
Attributes=attributes
|
|
119
|
+
)
|
|
120
|
+
self.queue_url = response['QueueUrl']
|
|
121
|
+
|
|
122
|
+
self._topic_queues['default'] = self.queue_url
|
|
123
|
+
|
|
124
|
+
async def disconnect(self) -> None:
|
|
125
|
+
"""Disconnect from AWS SQS."""
|
|
126
|
+
self._running = False
|
|
127
|
+
|
|
128
|
+
# Cancel all tasks
|
|
129
|
+
for task in self._tasks:
|
|
130
|
+
if not task.done():
|
|
131
|
+
task.cancel()
|
|
132
|
+
|
|
133
|
+
if self._tasks:
|
|
134
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
135
|
+
|
|
136
|
+
self._tasks.clear()
|
|
137
|
+
self._session = None
|
|
138
|
+
|
|
139
|
+
async def publish(self, message: Message, wait_for_confirmation: bool = False) -> Optional[str]:
|
|
140
|
+
"""
|
|
141
|
+
Publish message to SQS queue.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
message: Message to publish
|
|
145
|
+
wait_for_confirmation: Wait for SQS acknowledgment (always true for SQS)
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Message ID
|
|
149
|
+
"""
|
|
150
|
+
if not self._session:
|
|
151
|
+
raise ConnectionError("Not connected to AWS SQS")
|
|
152
|
+
|
|
153
|
+
async with self._session.client('sqs') as sqs:
|
|
154
|
+
# Serialize message
|
|
155
|
+
message_body = message.model_dump_json()
|
|
156
|
+
|
|
157
|
+
# Build send parameters
|
|
158
|
+
params = {
|
|
159
|
+
'QueueUrl': self.queue_url,
|
|
160
|
+
'MessageBody': message_body,
|
|
161
|
+
'MessageAttributes': {
|
|
162
|
+
'topic': {
|
|
163
|
+
'DataType': 'String',
|
|
164
|
+
'StringValue': message.topic
|
|
165
|
+
},
|
|
166
|
+
'source': {
|
|
167
|
+
'DataType': 'String',
|
|
168
|
+
'StringValue': message.source
|
|
169
|
+
},
|
|
170
|
+
'message_id': {
|
|
171
|
+
'DataType': 'String',
|
|
172
|
+
'StringValue': message.id
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# FIFO queues require message group ID
|
|
178
|
+
if self.use_fifo:
|
|
179
|
+
params['MessageGroupId'] = message.topic.replace('/', '-')
|
|
180
|
+
params['MessageDeduplicationId'] = message.id
|
|
181
|
+
|
|
182
|
+
# Send message
|
|
183
|
+
response = await sqs.send_message(**params)
|
|
184
|
+
|
|
185
|
+
return response.get('MessageId', message.id)
|
|
186
|
+
|
|
187
|
+
async def subscribe(self, topic: str, handler: MessageHandler) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Subscribe to messages on a topic.
|
|
190
|
+
|
|
191
|
+
Starts polling the SQS queue for messages matching the topic.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
topic: Topic to filter messages by
|
|
195
|
+
handler: Message handler
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Subscription ID
|
|
199
|
+
"""
|
|
200
|
+
subscription_id = str(uuid.uuid4())
|
|
201
|
+
|
|
202
|
+
self._subscriptions[subscription_id] = topic
|
|
203
|
+
self._handlers[topic] = handler
|
|
204
|
+
|
|
205
|
+
# Start polling task
|
|
206
|
+
task = asyncio.create_task(self._poll_task(subscription_id, topic, handler))
|
|
207
|
+
self._tasks.add(task)
|
|
208
|
+
task.add_done_callback(self._tasks.discard)
|
|
209
|
+
|
|
210
|
+
return subscription_id
|
|
211
|
+
|
|
212
|
+
async def _poll_task(self, subscription_id: str, topic: str, handler: MessageHandler):
|
|
213
|
+
"""Poll SQS queue for messages."""
|
|
214
|
+
while self._running and subscription_id in self._subscriptions:
|
|
215
|
+
try:
|
|
216
|
+
async with self._session.client('sqs') as sqs:
|
|
217
|
+
# Receive messages
|
|
218
|
+
response = await sqs.receive_message(
|
|
219
|
+
QueueUrl=self.queue_url,
|
|
220
|
+
MaxNumberOfMessages=10,
|
|
221
|
+
WaitTimeSeconds=20, # Long polling
|
|
222
|
+
MessageAttributeNames=['All']
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
messages = response.get('Messages', [])
|
|
226
|
+
|
|
227
|
+
for sqs_message in messages:
|
|
228
|
+
try:
|
|
229
|
+
# Parse message
|
|
230
|
+
body = sqs_message['Body']
|
|
231
|
+
amb_message = Message.model_validate_json(body)
|
|
232
|
+
|
|
233
|
+
# Check topic filter
|
|
234
|
+
if amb_message.topic == topic or topic == "*":
|
|
235
|
+
await handler(amb_message)
|
|
236
|
+
|
|
237
|
+
# Delete message after processing
|
|
238
|
+
await sqs.delete_message(
|
|
239
|
+
QueueUrl=self.queue_url,
|
|
240
|
+
ReceiptHandle=sqs_message['ReceiptHandle']
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
print(f"Error handling SQS message: {e}")
|
|
245
|
+
# Leave message in queue for retry
|
|
246
|
+
|
|
247
|
+
except asyncio.CancelledError:
|
|
248
|
+
break
|
|
249
|
+
except Exception as e:
|
|
250
|
+
print(f"Error polling SQS: {e}")
|
|
251
|
+
await asyncio.sleep(1.0)
|
|
252
|
+
|
|
253
|
+
async def unsubscribe(self, subscription_id: str) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Unsubscribe from messages.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
subscription_id: Subscription ID
|
|
259
|
+
"""
|
|
260
|
+
if subscription_id in self._subscriptions:
|
|
261
|
+
topic = self._subscriptions[subscription_id]
|
|
262
|
+
del self._subscriptions[subscription_id]
|
|
263
|
+
|
|
264
|
+
if topic in self._handlers:
|
|
265
|
+
del self._handlers[topic]
|
|
266
|
+
|
|
267
|
+
async def request(self, message: Message, timeout: float = 30.0) -> Message:
|
|
268
|
+
"""
|
|
269
|
+
Send request and wait for response.
|
|
270
|
+
|
|
271
|
+
Uses a temporary response queue for the request-response pattern.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
message: Request message
|
|
275
|
+
timeout: Timeout in seconds
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Response message
|
|
279
|
+
"""
|
|
280
|
+
if not self._session:
|
|
281
|
+
raise ConnectionError("Not connected to AWS SQS")
|
|
282
|
+
|
|
283
|
+
# Generate correlation ID
|
|
284
|
+
if not message.correlation_id:
|
|
285
|
+
message.correlation_id = str(uuid.uuid4())
|
|
286
|
+
|
|
287
|
+
# Create response queue
|
|
288
|
+
response_queue: asyncio.Queue = asyncio.Queue()
|
|
289
|
+
|
|
290
|
+
async def response_handler(msg: Message):
|
|
291
|
+
if msg.correlation_id == message.correlation_id:
|
|
292
|
+
await response_queue.put(msg)
|
|
293
|
+
|
|
294
|
+
# Subscribe to responses (using correlation ID as topic filter)
|
|
295
|
+
response_topic = f"response.{message.correlation_id}"
|
|
296
|
+
sub_id = await self.subscribe(response_topic, response_handler)
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
# Set reply-to
|
|
300
|
+
message.reply_to = response_topic
|
|
301
|
+
|
|
302
|
+
# Publish request
|
|
303
|
+
await self.publish(message, wait_for_confirmation=False)
|
|
304
|
+
|
|
305
|
+
# Wait for response
|
|
306
|
+
try:
|
|
307
|
+
response = await asyncio.wait_for(response_queue.get(), timeout=timeout)
|
|
308
|
+
return response
|
|
309
|
+
except asyncio.TimeoutError:
|
|
310
|
+
raise TimeoutError(f"No response received within {timeout} seconds")
|
|
311
|
+
|
|
312
|
+
finally:
|
|
313
|
+
await self.unsubscribe(sub_id)
|
|
314
|
+
|
|
315
|
+
async def get_pending_messages(self, topic: str, limit: int = 10) -> List[Message]:
|
|
316
|
+
"""
|
|
317
|
+
Peek at pending messages in the queue.
|
|
318
|
+
|
|
319
|
+
Note: SQS doesn't support true peeking, so this uses visibility timeout
|
|
320
|
+
and then returns messages to the queue.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
topic: Topic to filter by
|
|
324
|
+
limit: Maximum messages to retrieve
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List of messages
|
|
328
|
+
"""
|
|
329
|
+
if not self._session:
|
|
330
|
+
raise ConnectionError("Not connected to AWS SQS")
|
|
331
|
+
|
|
332
|
+
messages = []
|
|
333
|
+
|
|
334
|
+
async with self._session.client('sqs') as sqs:
|
|
335
|
+
# Receive with short visibility timeout
|
|
336
|
+
response = await sqs.receive_message(
|
|
337
|
+
QueueUrl=self.queue_url,
|
|
338
|
+
MaxNumberOfMessages=min(limit, 10),
|
|
339
|
+
VisibilityTimeout=1, # Short timeout so messages return to queue
|
|
340
|
+
MessageAttributeNames=['All']
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
for sqs_message in response.get('Messages', []):
|
|
344
|
+
try:
|
|
345
|
+
body = sqs_message['Body']
|
|
346
|
+
amb_message = Message.model_validate_json(body)
|
|
347
|
+
|
|
348
|
+
if amb_message.topic == topic or topic == "*":
|
|
349
|
+
messages.append(amb_message)
|
|
350
|
+
except Exception:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
return messages
|
|
354
|
+
|
|
355
|
+
async def health_check(self) -> bool:
|
|
356
|
+
"""Check if connected to AWS SQS."""
|
|
357
|
+
if not self._session:
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
async with self._session.client('sqs') as sqs:
|
|
362
|
+
await sqs.get_queue_attributes(
|
|
363
|
+
QueueUrl=self.queue_url,
|
|
364
|
+
AttributeNames=['QueueArn']
|
|
365
|
+
)
|
|
366
|
+
return True
|
|
367
|
+
except Exception:
|
|
368
|
+
return False
|
|
369
|
+
|
|
370
|
+
async def purge_queue(self) -> None:
|
|
371
|
+
"""Purge all messages from the queue."""
|
|
372
|
+
if not self._session:
|
|
373
|
+
raise ConnectionError("Not connected to AWS SQS")
|
|
374
|
+
|
|
375
|
+
async with self._session.client('sqs') as sqs:
|
|
376
|
+
await sqs.purge_queue(QueueUrl=self.queue_url)
|