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,316 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Safe HTTP Client Tool.
5
+
6
+ Provides HTTP request capabilities with security controls:
7
+ - URL whitelisting
8
+ - Rate limiting
9
+ - Request timeout enforcement
10
+ - Response size limits
11
+ - No redirect following to external domains
12
+ """
13
+
14
+ import asyncio
15
+ import time
16
+ from collections import deque
17
+ from typing import Any, Dict, List, Optional, Set
18
+ from urllib.parse import urlparse
19
+
20
+ from atr.decorator import tool
21
+
22
+
23
+ class RateLimiter:
24
+ """Simple rate limiter using sliding window."""
25
+
26
+ def __init__(self, max_requests: int, window_seconds: float = 60.0):
27
+ self.max_requests = max_requests
28
+ self.window_seconds = window_seconds
29
+ self._requests: deque = deque()
30
+
31
+ def check(self) -> bool:
32
+ """Check if request is allowed."""
33
+ now = time.monotonic()
34
+
35
+ # Remove old requests
36
+ while self._requests and self._requests[0] < now - self.window_seconds:
37
+ self._requests.popleft()
38
+
39
+ return len(self._requests) < self.max_requests
40
+
41
+ def record(self):
42
+ """Record a request."""
43
+ self._requests.append(time.monotonic())
44
+
45
+
46
+ class HttpClientTool:
47
+ """
48
+ Safe HTTP client with security controls.
49
+
50
+ Features:
51
+ - Domain whitelisting (only specified domains allowed)
52
+ - Rate limiting (prevent abuse)
53
+ - Timeout enforcement (prevent hanging)
54
+ - Response size limits (prevent memory exhaustion)
55
+ - Safe redirect handling (no external redirects)
56
+
57
+ Example:
58
+ ```python
59
+ http = HttpClientTool(
60
+ allowed_domains=["api.github.com", "httpbin.org"],
61
+ rate_limit=30, # 30 requests per minute
62
+ timeout=10.0,
63
+ max_response_size=1_000_000 # 1MB
64
+ )
65
+
66
+ # Register with ATR
67
+ registry.register(http.get)
68
+ registry.register(http.post)
69
+
70
+ # Use from agent
71
+ response = await http.get("https://api.github.com/users/octocat")
72
+ ```
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ allowed_domains: Optional[List[str]] = None,
78
+ blocked_domains: Optional[List[str]] = None,
79
+ rate_limit: int = 60,
80
+ timeout: float = 30.0,
81
+ max_response_size: int = 10_000_000, # 10MB
82
+ allow_redirects: bool = True,
83
+ max_redirects: int = 5
84
+ ):
85
+ """
86
+ Initialize HTTP client tool.
87
+
88
+ Args:
89
+ allowed_domains: Whitelist of allowed domains (if set, only these allowed)
90
+ blocked_domains: Blacklist of blocked domains
91
+ rate_limit: Maximum requests per minute
92
+ timeout: Request timeout in seconds
93
+ max_response_size: Maximum response size in bytes
94
+ allow_redirects: Whether to follow redirects
95
+ max_redirects: Maximum number of redirects to follow
96
+ """
97
+ self.allowed_domains: Set[str] = set(allowed_domains or [])
98
+ self.blocked_domains: Set[str] = set(blocked_domains or [
99
+ "localhost",
100
+ "127.0.0.1",
101
+ "0.0.0.0",
102
+ "169.254.169.254", # AWS metadata
103
+ "metadata.google.internal", # GCP metadata
104
+ ])
105
+ self.timeout = timeout
106
+ self.max_response_size = max_response_size
107
+ self.allow_redirects = allow_redirects
108
+ self.max_redirects = max_redirects
109
+ self._rate_limiter = RateLimiter(rate_limit)
110
+
111
+ def _validate_url(self, url: str) -> str:
112
+ """Validate and normalize URL."""
113
+ parsed = urlparse(url)
114
+
115
+ # Must have scheme
116
+ if not parsed.scheme:
117
+ raise ValueError("URL must include scheme (http:// or https://)")
118
+
119
+ # Only allow http/https
120
+ if parsed.scheme not in ("http", "https"):
121
+ raise ValueError(f"Scheme '{parsed.scheme}' not allowed. Use http or https.")
122
+
123
+ # Extract domain
124
+ domain = parsed.netloc.lower()
125
+ if ":" in domain:
126
+ domain = domain.split(":")[0] # Remove port
127
+
128
+ # Check blocked domains
129
+ if domain in self.blocked_domains:
130
+ raise ValueError(f"Domain '{domain}' is blocked")
131
+
132
+ # Check for private IP ranges (basic check)
133
+ if self._is_private_domain(domain):
134
+ raise ValueError(f"Private/internal domains not allowed: {domain}")
135
+
136
+ # Check allowed domains (if whitelist set)
137
+ if self.allowed_domains:
138
+ if not any(domain.endswith(allowed) for allowed in self.allowed_domains):
139
+ raise ValueError(
140
+ f"Domain '{domain}' not in allowed list. "
141
+ f"Allowed: {', '.join(self.allowed_domains)}"
142
+ )
143
+
144
+ return url
145
+
146
+ def _is_private_domain(self, domain: str) -> bool:
147
+ """Check if domain resolves to private IP (basic check)."""
148
+ # Check common private patterns
149
+ private_patterns = [
150
+ "10.",
151
+ "192.168.",
152
+ "172.16.", "172.17.", "172.18.", "172.19.",
153
+ "172.20.", "172.21.", "172.22.", "172.23.",
154
+ "172.24.", "172.25.", "172.26.", "172.27.",
155
+ "172.28.", "172.29.", "172.30.", "172.31.",
156
+ "internal",
157
+ ".local",
158
+ ]
159
+ return any(domain.startswith(p) or domain.endswith(p) for p in private_patterns)
160
+
161
+ def _check_rate_limit(self):
162
+ """Check and enforce rate limit."""
163
+ if not self._rate_limiter.check():
164
+ raise RuntimeError("Rate limit exceeded. Please wait before making more requests.")
165
+ self._rate_limiter.record()
166
+
167
+ @tool(
168
+ name="http_get",
169
+ description="Make a safe HTTP GET request to retrieve data from a URL",
170
+ tags=["http", "network", "safe"]
171
+ )
172
+ async def get(
173
+ self,
174
+ url: str,
175
+ headers: Optional[Dict[str, str]] = None
176
+ ) -> Dict[str, Any]:
177
+ """
178
+ Make a GET request.
179
+
180
+ Args:
181
+ url: URL to request (must be in allowed domains)
182
+ headers: Optional request headers
183
+
184
+ Returns:
185
+ Dict with status_code, headers, and body
186
+ """
187
+ try:
188
+ import aiohttp
189
+ except ImportError:
190
+ raise ImportError("aiohttp required. Install with: pip install aiohttp")
191
+
192
+ # Validate
193
+ url = self._validate_url(url)
194
+ self._check_rate_limit()
195
+
196
+ # Make request
197
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
198
+
199
+ async with aiohttp.ClientSession(timeout=timeout) as session:
200
+ async with session.get(
201
+ url,
202
+ headers=headers,
203
+ allow_redirects=self.allow_redirects,
204
+ max_redirects=self.max_redirects
205
+ ) as response:
206
+ # Check response size
207
+ content_length = response.headers.get("Content-Length")
208
+ if content_length and int(content_length) > self.max_response_size:
209
+ raise ValueError(f"Response too large: {content_length} bytes")
210
+
211
+ # Read response (with size limit)
212
+ body = await response.text()
213
+ if len(body) > self.max_response_size:
214
+ body = body[:self.max_response_size] + "... [truncated]"
215
+
216
+ return {
217
+ "status_code": response.status,
218
+ "headers": dict(response.headers),
219
+ "body": body,
220
+ "url": str(response.url)
221
+ }
222
+
223
+ @tool(
224
+ name="http_post",
225
+ description="Make a safe HTTP POST request to send data to a URL",
226
+ tags=["http", "network", "safe"]
227
+ )
228
+ async def post(
229
+ self,
230
+ url: str,
231
+ data: Optional[Dict[str, Any]] = None,
232
+ json_body: Optional[Dict[str, Any]] = None,
233
+ headers: Optional[Dict[str, str]] = None
234
+ ) -> Dict[str, Any]:
235
+ """
236
+ Make a POST request.
237
+
238
+ Args:
239
+ url: URL to request (must be in allowed domains)
240
+ data: Form data to send
241
+ json_body: JSON body to send
242
+ headers: Optional request headers
243
+
244
+ Returns:
245
+ Dict with status_code, headers, and body
246
+ """
247
+ try:
248
+ import aiohttp
249
+ except ImportError:
250
+ raise ImportError("aiohttp required. Install with: pip install aiohttp")
251
+
252
+ # Validate
253
+ url = self._validate_url(url)
254
+ self._check_rate_limit()
255
+
256
+ # Make request
257
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
258
+
259
+ async with aiohttp.ClientSession(timeout=timeout) as session:
260
+ async with session.post(
261
+ url,
262
+ data=data,
263
+ json=json_body,
264
+ headers=headers,
265
+ allow_redirects=self.allow_redirects,
266
+ max_redirects=self.max_redirects
267
+ ) as response:
268
+ # Check response size
269
+ content_length = response.headers.get("Content-Length")
270
+ if content_length and int(content_length) > self.max_response_size:
271
+ raise ValueError(f"Response too large: {content_length} bytes")
272
+
273
+ # Read response
274
+ body = await response.text()
275
+ if len(body) > self.max_response_size:
276
+ body = body[:self.max_response_size] + "... [truncated]"
277
+
278
+ return {
279
+ "status_code": response.status,
280
+ "headers": dict(response.headers),
281
+ "body": body,
282
+ "url": str(response.url)
283
+ }
284
+
285
+ @tool(
286
+ name="http_head",
287
+ description="Make a safe HTTP HEAD request to check if a URL exists",
288
+ tags=["http", "network", "safe"]
289
+ )
290
+ async def head(self, url: str) -> Dict[str, Any]:
291
+ """
292
+ Make a HEAD request (useful for checking if resource exists).
293
+
294
+ Args:
295
+ url: URL to check
296
+
297
+ Returns:
298
+ Dict with status_code and headers
299
+ """
300
+ try:
301
+ import aiohttp
302
+ except ImportError:
303
+ raise ImportError("aiohttp required. Install with: pip install aiohttp")
304
+
305
+ url = self._validate_url(url)
306
+ self._check_rate_limit()
307
+
308
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
309
+
310
+ async with aiohttp.ClientSession(timeout=timeout) as session:
311
+ async with session.head(url, allow_redirects=self.allow_redirects) as response:
312
+ return {
313
+ "status_code": response.status,
314
+ "headers": dict(response.headers),
315
+ "url": str(response.url)
316
+ }
@@ -0,0 +1,374 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Safe JSON/YAML Parser Tool.
5
+
6
+ Provides safe parsing of JSON and YAML with:
7
+ - Size limits to prevent memory exhaustion
8
+ - Depth limits to prevent stack overflow
9
+ - Safe YAML loading (no arbitrary code execution)
10
+ - Schema validation support
11
+ """
12
+
13
+ import json
14
+ from typing import Any, Dict, List, Optional, Union
15
+
16
+ from atr.decorator import tool
17
+
18
+
19
+ class JsonParserTool:
20
+ """
21
+ Safe JSON and YAML parser.
22
+
23
+ Features:
24
+ - Input size limits
25
+ - Nesting depth limits
26
+ - Safe YAML loading (yaml.safe_load)
27
+ - Schema validation (optional)
28
+ - Pretty printing
29
+
30
+ Example:
31
+ ```python
32
+ parser = JsonParserTool(
33
+ max_size=1_000_000, # 1MB
34
+ max_depth=50
35
+ )
36
+
37
+ # Parse JSON
38
+ data = parser.parse_json('{"key": "value"}')
39
+
40
+ # Parse YAML
41
+ data = parser.parse_yaml("key: value")
42
+
43
+ # Validate schema
44
+ parser.validate(data, schema={"type": "object"})
45
+ ```
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ max_size: int = 10_000_000, # 10MB
51
+ max_depth: int = 100,
52
+ max_keys: int = 10000
53
+ ):
54
+ """
55
+ Initialize parser tool.
56
+
57
+ Args:
58
+ max_size: Maximum input size in characters
59
+ max_depth: Maximum nesting depth
60
+ max_keys: Maximum number of keys in objects
61
+ """
62
+ self.max_size = max_size
63
+ self.max_depth = max_depth
64
+ self.max_keys = max_keys
65
+
66
+ def _check_size(self, data: str, name: str = "input"):
67
+ """Check input size."""
68
+ if len(data) > self.max_size:
69
+ raise ValueError(
70
+ f"{name} too large: {len(data)} chars. Maximum: {self.max_size}"
71
+ )
72
+
73
+ def _check_depth(self, obj: Any, current_depth: int = 0):
74
+ """Check nesting depth recursively."""
75
+ if current_depth > self.max_depth:
76
+ raise ValueError(f"Nesting too deep. Maximum depth: {self.max_depth}")
77
+
78
+ if isinstance(obj, dict):
79
+ if len(obj) > self.max_keys:
80
+ raise ValueError(f"Too many keys: {len(obj)}. Maximum: {self.max_keys}")
81
+ for value in obj.values():
82
+ self._check_depth(value, current_depth + 1)
83
+ elif isinstance(obj, list):
84
+ for item in obj:
85
+ self._check_depth(item, current_depth + 1)
86
+
87
+ @tool(
88
+ name="parse_json",
89
+ description="Safely parse a JSON string into a Python object",
90
+ tags=["json", "parse", "safe"]
91
+ )
92
+ def parse_json(self, data: str) -> Dict[str, Any]:
93
+ """
94
+ Parse JSON string.
95
+
96
+ Args:
97
+ data: JSON string to parse
98
+
99
+ Returns:
100
+ Dict with parsed data and metadata
101
+ """
102
+ self._check_size(data, "JSON")
103
+
104
+ try:
105
+ parsed = json.loads(data)
106
+ except json.JSONDecodeError as e:
107
+ return {
108
+ "success": False,
109
+ "error": f"Invalid JSON: {e.msg} at line {e.lineno}, column {e.colno}",
110
+ "data": None
111
+ }
112
+
113
+ # Check depth
114
+ try:
115
+ self._check_depth(parsed)
116
+ except ValueError as e:
117
+ return {
118
+ "success": False,
119
+ "error": str(e),
120
+ "data": None
121
+ }
122
+
123
+ return {
124
+ "success": True,
125
+ "data": parsed,
126
+ "type": type(parsed).__name__
127
+ }
128
+
129
+ @tool(
130
+ name="parse_yaml",
131
+ description="Safely parse a YAML string into a Python object",
132
+ tags=["yaml", "parse", "safe"]
133
+ )
134
+ def parse_yaml(self, data: str) -> Dict[str, Any]:
135
+ """
136
+ Parse YAML string safely.
137
+
138
+ Uses yaml.safe_load to prevent arbitrary code execution.
139
+
140
+ Args:
141
+ data: YAML string to parse
142
+
143
+ Returns:
144
+ Dict with parsed data and metadata
145
+ """
146
+ try:
147
+ import yaml
148
+ except ImportError:
149
+ return {
150
+ "success": False,
151
+ "error": "PyYAML not installed. Install with: pip install pyyaml",
152
+ "data": None
153
+ }
154
+
155
+ self._check_size(data, "YAML")
156
+
157
+ try:
158
+ # Always use safe_load to prevent code execution
159
+ parsed = yaml.safe_load(data)
160
+ except yaml.YAMLError as e:
161
+ return {
162
+ "success": False,
163
+ "error": f"Invalid YAML: {e}",
164
+ "data": None
165
+ }
166
+
167
+ # Check depth
168
+ try:
169
+ self._check_depth(parsed)
170
+ except ValueError as e:
171
+ return {
172
+ "success": False,
173
+ "error": str(e),
174
+ "data": None
175
+ }
176
+
177
+ return {
178
+ "success": True,
179
+ "data": parsed,
180
+ "type": type(parsed).__name__ if parsed is not None else "null"
181
+ }
182
+
183
+ @tool(
184
+ name="to_json",
185
+ description="Convert a Python object to a JSON string",
186
+ tags=["json", "serialize", "safe"]
187
+ )
188
+ def to_json(
189
+ self,
190
+ data: Any,
191
+ indent: Optional[int] = 2,
192
+ sort_keys: bool = False
193
+ ) -> Dict[str, Any]:
194
+ """
195
+ Convert object to JSON string.
196
+
197
+ Args:
198
+ data: Object to serialize
199
+ indent: Indentation level (None for compact)
200
+ sort_keys: Whether to sort dictionary keys
201
+
202
+ Returns:
203
+ Dict with JSON string
204
+ """
205
+ try:
206
+ result = json.dumps(data, indent=indent, sort_keys=sort_keys, ensure_ascii=False)
207
+
208
+ if len(result) > self.max_size:
209
+ return {
210
+ "success": False,
211
+ "error": f"Output too large: {len(result)} chars",
212
+ "json": None
213
+ }
214
+
215
+ return {
216
+ "success": True,
217
+ "json": result,
218
+ "size": len(result)
219
+ }
220
+ except (TypeError, ValueError) as e:
221
+ return {
222
+ "success": False,
223
+ "error": f"Cannot serialize: {e}",
224
+ "json": None
225
+ }
226
+
227
+ @tool(
228
+ name="to_yaml",
229
+ description="Convert a Python object to a YAML string",
230
+ tags=["yaml", "serialize", "safe"]
231
+ )
232
+ def to_yaml(
233
+ self,
234
+ data: Any,
235
+ default_flow_style: bool = False
236
+ ) -> Dict[str, Any]:
237
+ """
238
+ Convert object to YAML string.
239
+
240
+ Args:
241
+ data: Object to serialize
242
+ default_flow_style: Use flow style (compact) formatting
243
+
244
+ Returns:
245
+ Dict with YAML string
246
+ """
247
+ try:
248
+ import yaml
249
+ except ImportError:
250
+ return {
251
+ "success": False,
252
+ "error": "PyYAML not installed. Install with: pip install pyyaml",
253
+ "yaml": None
254
+ }
255
+
256
+ try:
257
+ result = yaml.safe_dump(
258
+ data,
259
+ default_flow_style=default_flow_style,
260
+ allow_unicode=True
261
+ )
262
+
263
+ if len(result) > self.max_size:
264
+ return {
265
+ "success": False,
266
+ "error": f"Output too large: {len(result)} chars",
267
+ "yaml": None
268
+ }
269
+
270
+ return {
271
+ "success": True,
272
+ "yaml": result,
273
+ "size": len(result)
274
+ }
275
+ except yaml.YAMLError as e:
276
+ return {
277
+ "success": False,
278
+ "error": f"Cannot serialize: {e}",
279
+ "yaml": None
280
+ }
281
+
282
+ @tool(
283
+ name="validate_json_schema",
284
+ description="Validate data against a JSON schema",
285
+ tags=["json", "validate", "schema", "safe"]
286
+ )
287
+ def validate_schema(
288
+ self,
289
+ data: Any,
290
+ schema: Dict[str, Any]
291
+ ) -> Dict[str, Any]:
292
+ """
293
+ Validate data against JSON schema.
294
+
295
+ Args:
296
+ data: Data to validate
297
+ schema: JSON schema
298
+
299
+ Returns:
300
+ Dict with validation result
301
+ """
302
+ try:
303
+ import jsonschema
304
+ except ImportError:
305
+ return {
306
+ "valid": False,
307
+ "error": "jsonschema not installed. Install with: pip install jsonschema"
308
+ }
309
+
310
+ try:
311
+ jsonschema.validate(instance=data, schema=schema)
312
+ return {
313
+ "valid": True,
314
+ "error": None
315
+ }
316
+ except jsonschema.ValidationError as e:
317
+ return {
318
+ "valid": False,
319
+ "error": e.message,
320
+ "path": list(e.absolute_path)
321
+ }
322
+ except jsonschema.SchemaError as e:
323
+ return {
324
+ "valid": False,
325
+ "error": f"Invalid schema: {e.message}"
326
+ }
327
+
328
+ @tool(
329
+ name="json_query",
330
+ description="Query JSON data using JSONPath or simple dot notation",
331
+ tags=["json", "query", "safe"]
332
+ )
333
+ def query(
334
+ self,
335
+ data: Any,
336
+ path: str
337
+ ) -> Dict[str, Any]:
338
+ """
339
+ Query JSON data using dot notation.
340
+
341
+ Args:
342
+ data: JSON data to query
343
+ path: Dot-notation path (e.g., "users.0.name")
344
+
345
+ Returns:
346
+ Dict with query result
347
+ """
348
+ parts = path.split(".")
349
+ current = data
350
+
351
+ try:
352
+ for part in parts:
353
+ if not part:
354
+ continue
355
+
356
+ if isinstance(current, dict):
357
+ current = current[part]
358
+ elif isinstance(current, list):
359
+ idx = int(part)
360
+ current = current[idx]
361
+ else:
362
+ raise KeyError(f"Cannot access '{part}' on {type(current).__name__}")
363
+
364
+ return {
365
+ "success": True,
366
+ "value": current,
367
+ "path": path
368
+ }
369
+ except (KeyError, IndexError, ValueError) as e:
370
+ return {
371
+ "success": False,
372
+ "error": f"Path not found: {e}",
373
+ "value": None
374
+ }