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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. nexus/tests/test_schemas.py +295 -0
@@ -0,0 +1,285 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """NATS broker adapter for AMB.
4
+
5
+ NATS is a lightweight, cloud-native messaging system ideal for
6
+ microservices, IoT, and edge computing scenarios.
7
+ """
8
+
9
+ import asyncio
10
+ import json
11
+ import uuid
12
+ from typing import Dict, List, Optional, Callable
13
+
14
+ from amb_core.broker import BrokerAdapter, MessageHandler
15
+ from amb_core.models import Message
16
+
17
+ try:
18
+ import nats
19
+ from nats.aio.client import Client as NATS
20
+ from nats.js.api import StreamConfig, ConsumerConfig
21
+ except ImportError:
22
+ raise ImportError(
23
+ "NATS adapter requires 'nats-py' package. "
24
+ "Install it with: pip install amb-core[nats]"
25
+ )
26
+
27
+
28
+ class NATSBroker(BrokerAdapter):
29
+ """
30
+ NATS-based broker adapter.
31
+
32
+ This adapter uses NATS for lightweight, high-performance messaging.
33
+ Supports both core NATS (fire-and-forget) and JetStream (persistent).
34
+
35
+ Example:
36
+ ```python
37
+ from amb_core.adapters import NATSBroker
38
+
39
+ broker = NATSBroker(servers=["nats://localhost:4222"])
40
+ await broker.connect()
41
+
42
+ # Subscribe
43
+ async def handler(msg):
44
+ print(f"Received: {msg.payload}")
45
+
46
+ await broker.subscribe("agent.tasks", handler)
47
+
48
+ # Publish
49
+ await broker.publish(Message(topic="agent.tasks", payload={"task": "analyze"}))
50
+ ```
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ servers: List[str] = None,
56
+ use_jetstream: bool = False,
57
+ stream_name: str = "AMB_STREAM"
58
+ ):
59
+ """
60
+ Initialize NATS broker.
61
+
62
+ Args:
63
+ servers: List of NATS server URLs (default: ["nats://localhost:4222"])
64
+ use_jetstream: Enable JetStream for persistence (default: False)
65
+ stream_name: JetStream stream name (default: "AMB_STREAM")
66
+ """
67
+ self.servers = servers or ["nats://localhost:4222"]
68
+ self.use_jetstream = use_jetstream
69
+ self.stream_name = stream_name
70
+
71
+ self._nc: Optional[NATS] = None
72
+ self._js = None # JetStream context
73
+ self._subscriptions: Dict[str, object] = {} # subscription_id -> subscription
74
+ self._handlers: Dict[str, MessageHandler] = {} # topic -> handler
75
+ self._running = False
76
+
77
+ async def connect(self) -> None:
78
+ """Connect to NATS server(s)."""
79
+ self._nc = await nats.connect(servers=self.servers)
80
+ self._running = True
81
+
82
+ if self.use_jetstream:
83
+ self._js = self._nc.jetstream()
84
+ # Create stream if it doesn't exist
85
+ try:
86
+ await self._js.add_stream(
87
+ name=self.stream_name,
88
+ subjects=["amb.>"], # All AMB subjects
89
+ retention="limits",
90
+ max_msgs=100000,
91
+ max_bytes=100 * 1024 * 1024 # 100MB
92
+ )
93
+ except Exception:
94
+ # Stream may already exist
95
+ pass
96
+
97
+ async def disconnect(self) -> None:
98
+ """Disconnect from NATS."""
99
+ self._running = False
100
+
101
+ # Unsubscribe all
102
+ for sub_id in list(self._subscriptions.keys()):
103
+ await self.unsubscribe(sub_id)
104
+
105
+ if self._nc:
106
+ await self._nc.drain()
107
+ await self._nc.close()
108
+
109
+ async def publish(self, message: Message, wait_for_confirmation: bool = False) -> Optional[str]:
110
+ """
111
+ Publish message to NATS subject.
112
+
113
+ Args:
114
+ message: Message to publish
115
+ wait_for_confirmation: Wait for acknowledgment (JetStream only)
116
+
117
+ Returns:
118
+ Message ID
119
+ """
120
+ if not self._nc:
121
+ raise ConnectionError("Not connected to NATS")
122
+
123
+ # Convert topic to NATS subject format (replace / with .)
124
+ subject = f"amb.{message.topic.replace('/', '.')}"
125
+
126
+ # Serialize message
127
+ message_bytes = message.model_dump_json().encode()
128
+
129
+ if self.use_jetstream and self._js:
130
+ # Publish to JetStream for persistence
131
+ ack = await self._js.publish(subject, message_bytes)
132
+ if wait_for_confirmation:
133
+ return message.id
134
+ else:
135
+ # Core NATS publish (fire-and-forget)
136
+ await self._nc.publish(subject, message_bytes)
137
+
138
+ return message.id
139
+
140
+ async def subscribe(self, topic: str, handler: MessageHandler) -> str:
141
+ """
142
+ Subscribe to a NATS subject.
143
+
144
+ Args:
145
+ topic: Topic to subscribe to
146
+ handler: Message handler
147
+
148
+ Returns:
149
+ Subscription ID
150
+ """
151
+ if not self._nc:
152
+ raise ConnectionError("Not connected to NATS")
153
+
154
+ subscription_id = str(uuid.uuid4())
155
+
156
+ # Convert topic to NATS subject format
157
+ subject = f"amb.{topic.replace('/', '.')}"
158
+
159
+ # Create message callback
160
+ async def message_callback(msg):
161
+ try:
162
+ # Parse message
163
+ data = msg.data.decode('utf-8')
164
+ amb_message = Message.model_validate_json(data)
165
+
166
+ # Call handler
167
+ await handler(amb_message)
168
+
169
+ except Exception as e:
170
+ print(f"Error handling NATS message: {e}")
171
+
172
+ # Subscribe
173
+ if self.use_jetstream and self._js:
174
+ # JetStream subscription with durable consumer
175
+ sub = await self._js.subscribe(
176
+ subject,
177
+ cb=message_callback,
178
+ durable=f"amb-{subscription_id[:8]}"
179
+ )
180
+ else:
181
+ # Core NATS subscription
182
+ sub = await self._nc.subscribe(subject, cb=message_callback)
183
+
184
+ self._subscriptions[subscription_id] = sub
185
+ self._handlers[topic] = handler
186
+
187
+ return subscription_id
188
+
189
+ async def unsubscribe(self, subscription_id: str) -> None:
190
+ """
191
+ Unsubscribe from a subject.
192
+
193
+ Args:
194
+ subscription_id: Subscription ID
195
+ """
196
+ if subscription_id in self._subscriptions:
197
+ sub = self._subscriptions[subscription_id]
198
+ await sub.unsubscribe()
199
+ del self._subscriptions[subscription_id]
200
+
201
+ async def request(self, message: Message, timeout: float = 30.0) -> Message:
202
+ """
203
+ Send request and wait for response using NATS request-reply.
204
+
205
+ NATS has native request-reply support which makes this efficient.
206
+
207
+ Args:
208
+ message: Request message
209
+ timeout: Timeout in seconds
210
+
211
+ Returns:
212
+ Response message
213
+ """
214
+ if not self._nc:
215
+ raise ConnectionError("Not connected to NATS")
216
+
217
+ # Generate correlation ID
218
+ if not message.correlation_id:
219
+ message.correlation_id = str(uuid.uuid4())
220
+
221
+ # Convert topic to NATS subject
222
+ subject = f"amb.{message.topic.replace('/', '.')}"
223
+
224
+ # Serialize message
225
+ message_bytes = message.model_dump_json().encode()
226
+
227
+ try:
228
+ # NATS native request-reply
229
+ response = await self._nc.request(
230
+ subject,
231
+ message_bytes,
232
+ timeout=timeout
233
+ )
234
+
235
+ # Parse response
236
+ response_data = response.data.decode('utf-8')
237
+ return Message.model_validate_json(response_data)
238
+
239
+ except asyncio.TimeoutError:
240
+ raise TimeoutError(f"No response received within {timeout} seconds")
241
+
242
+ async def get_pending_messages(self, topic: str, limit: int = 10) -> List[Message]:
243
+ """
244
+ Get pending messages (JetStream only).
245
+
246
+ Args:
247
+ topic: Topic to get messages from
248
+ limit: Maximum messages to retrieve
249
+
250
+ Returns:
251
+ List of messages
252
+ """
253
+ if not self.use_jetstream or not self._js:
254
+ return [] # Core NATS doesn't persist messages
255
+
256
+ subject = f"amb.{topic.replace('/', '.')}"
257
+ messages = []
258
+
259
+ try:
260
+ # Create pull consumer
261
+ psub = await self._js.pull_subscribe(
262
+ subject,
263
+ durable=f"pending-{uuid.uuid4().hex[:8]}"
264
+ )
265
+
266
+ # Fetch messages
267
+ try:
268
+ fetched = await psub.fetch(limit, timeout=1.0)
269
+ for msg in fetched:
270
+ data = msg.data.decode('utf-8')
271
+ amb_message = Message.model_validate_json(data)
272
+ messages.append(amb_message)
273
+ except asyncio.TimeoutError:
274
+ pass # No more messages
275
+
276
+ await psub.unsubscribe()
277
+
278
+ except Exception as e:
279
+ print(f"Error fetching pending messages: {e}")
280
+
281
+ return messages
282
+
283
+ async def health_check(self) -> bool:
284
+ """Check if connected to NATS."""
285
+ return self._nc is not None and self._nc.is_connected
@@ -0,0 +1,235 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """RabbitMQ broker adapter for AMB."""
4
+
5
+ import asyncio
6
+ import uuid
7
+ from typing import Dict, List, Optional
8
+
9
+ from amb_core.broker import BrokerAdapter, MessageHandler
10
+ from amb_core.models import Message
11
+
12
+ try:
13
+ from aio_pika import ExchangeType, connect_robust
14
+ from aio_pika import Message as PikaMessage
15
+ from aio_pika.abc import AbstractChannel, AbstractConnection, AbstractQueue
16
+ except ImportError:
17
+ raise ImportError(
18
+ "RabbitMQ adapter requires 'aio-pika' package. "
19
+ "Install it with: pip install amb-core[rabbitmq]"
20
+ )
21
+
22
+
23
+ class RabbitMQBroker(BrokerAdapter):
24
+ """
25
+ RabbitMQ-based broker adapter.
26
+
27
+ This adapter uses RabbitMQ's topic exchanges for flexible routing
28
+ and direct exchanges for request-response patterns.
29
+ """
30
+
31
+ def __init__(self, url: str = "amqp://guest:guest@localhost/"):
32
+ """
33
+ Initialize RabbitMQ broker.
34
+
35
+ Args:
36
+ url: RabbitMQ connection URL
37
+ """
38
+ self.url = url
39
+ self._connection: Optional[AbstractConnection] = None
40
+ self._channel: Optional[AbstractChannel] = None
41
+ self._subscriptions: Dict[str, AbstractQueue] = {}
42
+ self._response_queues: Dict[str, asyncio.Queue] = {}
43
+ self._tasks: set = set()
44
+
45
+ async def connect(self) -> None:
46
+ """Connect to RabbitMQ."""
47
+ self._connection = await connect_robust(self.url)
48
+ self._channel = await self._connection.channel()
49
+
50
+ # Declare topic exchange for pub/sub
51
+ await self._channel.declare_exchange(
52
+ "amb.topic",
53
+ ExchangeType.TOPIC,
54
+ durable=True
55
+ )
56
+
57
+ async def disconnect(self) -> None:
58
+ """Disconnect from RabbitMQ."""
59
+ # Cancel all tasks
60
+ for task in self._tasks:
61
+ if not task.done():
62
+ task.cancel()
63
+
64
+ if self._tasks:
65
+ await asyncio.gather(*self._tasks, return_exceptions=True)
66
+
67
+ self._tasks.clear()
68
+
69
+ if self._channel:
70
+ await self._channel.close()
71
+
72
+ if self._connection:
73
+ await self._connection.close()
74
+
75
+ async def publish(self, message: Message, wait_for_confirmation: bool = False) -> Optional[str]:
76
+ """
77
+ Publish message to RabbitMQ exchange.
78
+
79
+ Args:
80
+ message: Message to publish
81
+ wait_for_confirmation: Wait for broker confirmation
82
+
83
+ Returns:
84
+ Message ID
85
+ """
86
+ if not self._channel:
87
+ raise ConnectionError("Not connected to RabbitMQ")
88
+
89
+ exchange = await self._channel.get_exchange("amb.topic")
90
+
91
+ # Serialize message
92
+ message_json = message.model_dump_json()
93
+
94
+ # Create AMQP message
95
+ amqp_message = PikaMessage(
96
+ body=message_json.encode(),
97
+ content_type="application/json",
98
+ message_id=message.id,
99
+ correlation_id=message.correlation_id,
100
+ )
101
+
102
+ # Publish
103
+ await exchange.publish(
104
+ amqp_message,
105
+ routing_key=message.topic,
106
+ mandatory=wait_for_confirmation
107
+ )
108
+
109
+ return message.id
110
+
111
+ async def subscribe(self, topic: str, handler: MessageHandler) -> str:
112
+ """
113
+ Subscribe to a RabbitMQ topic.
114
+
115
+ Args:
116
+ topic: Topic pattern to subscribe to (supports wildcards: * and #)
117
+ handler: Message handler
118
+
119
+ Returns:
120
+ Subscription ID
121
+ """
122
+ if not self._channel:
123
+ raise ConnectionError("Not connected to RabbitMQ")
124
+
125
+ subscription_id = str(uuid.uuid4())
126
+
127
+ # Declare queue
128
+ queue = await self._channel.declare_queue(
129
+ f"amb.queue.{subscription_id}",
130
+ auto_delete=True
131
+ )
132
+
133
+ # Bind to exchange
134
+ exchange = await self._channel.get_exchange("amb.topic")
135
+ await queue.bind(exchange, routing_key=topic)
136
+
137
+ # Start consuming
138
+ async def on_message(message):
139
+ async with message.process():
140
+ # Parse message
141
+ msg = Message.model_validate_json(message.body.decode())
142
+
143
+ # Call handler
144
+ await handler(msg)
145
+
146
+ await queue.consume(on_message)
147
+
148
+ self._subscriptions[subscription_id] = queue
149
+
150
+ return subscription_id
151
+
152
+ async def unsubscribe(self, subscription_id: str) -> None:
153
+ """
154
+ Unsubscribe from a topic.
155
+
156
+ Args:
157
+ subscription_id: Subscription ID
158
+ """
159
+ if subscription_id in self._subscriptions:
160
+ queue = self._subscriptions[subscription_id]
161
+ await queue.delete()
162
+ del self._subscriptions[subscription_id]
163
+
164
+ async def request(self, message: Message, timeout: float = 30.0) -> Message:
165
+ """
166
+ Send request and wait for response using RabbitMQ RPC pattern.
167
+
168
+ Args:
169
+ message: Request message
170
+ timeout: Timeout in seconds
171
+
172
+ Returns:
173
+ Response message
174
+ """
175
+ if not self._channel:
176
+ raise ConnectionError("Not connected to RabbitMQ")
177
+
178
+ # Generate correlation ID
179
+ if not message.correlation_id:
180
+ message.correlation_id = str(uuid.uuid4())
181
+
182
+ # Declare callback queue
183
+ callback_queue = await self._channel.declare_queue(
184
+ exclusive=True,
185
+ auto_delete=True
186
+ )
187
+
188
+ # Set up response queue
189
+ response_queue: asyncio.Queue = asyncio.Queue()
190
+ self._response_queues[message.correlation_id] = response_queue
191
+
192
+ # Start consuming responses
193
+ async def on_response(response_msg):
194
+ async with response_msg.process():
195
+ if response_msg.correlation_id in self._response_queues:
196
+ msg = Message.model_validate_json(response_msg.body.decode())
197
+ await self._response_queues[response_msg.correlation_id].put(msg)
198
+
199
+ await callback_queue.consume(on_response)
200
+
201
+ try:
202
+ # Set reply_to
203
+ message.reply_to = callback_queue.name
204
+
205
+ # Publish request
206
+ await self.publish(message, wait_for_confirmation=False)
207
+
208
+ # Wait for response
209
+ try:
210
+ response = await asyncio.wait_for(response_queue.get(), timeout=timeout)
211
+ return response
212
+ except asyncio.TimeoutError:
213
+ raise TimeoutError(f"No response received within {timeout} seconds")
214
+
215
+ finally:
216
+ # Clean up
217
+ if message.correlation_id in self._response_queues:
218
+ del self._response_queues[message.correlation_id]
219
+
220
+ await callback_queue.delete()
221
+
222
+ async def get_pending_messages(self, topic: str, limit: int = 10) -> List[Message]:
223
+ """
224
+ Get pending messages (limited support in RabbitMQ).
225
+
226
+ Args:
227
+ topic: Topic to get messages from
228
+ limit: Maximum messages to retrieve
229
+
230
+ Returns:
231
+ Empty list (RabbitMQ doesn't support this easily)
232
+ """
233
+ # RabbitMQ doesn't support getting pending messages easily
234
+ # Would need to implement message persistence differently
235
+ return []