agenticdome-python-sdk 1.2.6__tar.gz → 1.2.7__tar.gz
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.
- {agenticdome_python_sdk-1.2.6/agenticdome_python_sdk.egg-info → agenticdome_python_sdk-1.2.7}/PKG-INFO +1 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7/agenticdome_python_sdk.egg-info}/PKG-INFO +1 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/_framework_firewall.py +37 -2
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/client.py +127 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/pyproject.toml +1 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_autogen_integration.py +22 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_client.py +79 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/LICENSE +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/MANIFEST.in +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/README.md +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_python_sdk.egg-info/SOURCES.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_python_sdk.egg-info/dependency_links.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_python_sdk.egg-info/requires.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_python_sdk.egg-info/top_level.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/__init__.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/agno.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/autogen.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/aws_bedrock.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/claude.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/crewai.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/google_adk.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/identity.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/langgraph.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/llamaindex.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/mcp_host.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/microsoft_agent_framework.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/microsoft_ai_foundry.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/openai_agents.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/proof.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/pydantic.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/smolagents.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/examples/attack_demo.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/examples/crewai_agenticdome_firewall.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/setup.cfg +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/setup.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_agno_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_attack_demo.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_aws_bedrock_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_claude_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_crewai_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_google_adk_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_identity_context.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_langgraph_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_live_tenant.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_llamaindex_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_mcp_host_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_microsoft_agent_framework_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_microsoft_ai_foundry_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_openai_agents_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_pydanticai_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_smolagents_integration.py +0 -0
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/_framework_firewall.py
RENAMED
|
@@ -63,6 +63,8 @@ class DecisionTokenRecord:
|
|
|
63
63
|
source_agent_id: str
|
|
64
64
|
created_at: float
|
|
65
65
|
token_hmac: str = ""
|
|
66
|
+
tool_version: str = ""
|
|
67
|
+
tool_digest: str = ""
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
def _stable_json(value: Any) -> str:
|
|
@@ -213,6 +215,22 @@ class FrameworkFirewallBase:
|
|
|
213
215
|
except Exception:
|
|
214
216
|
pass
|
|
215
217
|
|
|
218
|
+
def register_tool_provenance(
|
|
219
|
+
self,
|
|
220
|
+
tool_name: str,
|
|
221
|
+
*,
|
|
222
|
+
tool_version: Optional[str] = None,
|
|
223
|
+
tool_digest: Optional[str] = None,
|
|
224
|
+
tool_platform: Optional[str] = None,
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Cache precomputed provenance once for this firewall's client."""
|
|
227
|
+
self.client.register_tool_provenance(
|
|
228
|
+
tool_name,
|
|
229
|
+
tool_version=tool_version,
|
|
230
|
+
tool_digest=tool_digest,
|
|
231
|
+
tool_platform=tool_platform or self.config.default_tool_platform,
|
|
232
|
+
)
|
|
233
|
+
|
|
216
234
|
@staticmethod
|
|
217
235
|
def serialize(value: Any) -> str:
|
|
218
236
|
return value if isinstance(value, str) else _stable_json(value)
|
|
@@ -372,6 +390,8 @@ class FrameworkFirewallBase:
|
|
|
372
390
|
tool_args: Dict[str, Any],
|
|
373
391
|
text: str = "",
|
|
374
392
|
tool_platform: Optional[str] = None,
|
|
393
|
+
tool_version: Optional[str] = None,
|
|
394
|
+
tool_digest: Optional[str] = None,
|
|
375
395
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
376
396
|
source_agent_id: Optional[str] = None,
|
|
377
397
|
) -> Dict[str, Any]:
|
|
@@ -392,6 +412,8 @@ class FrameworkFirewallBase:
|
|
|
392
412
|
tool_platform=tool_platform or self.config.default_tool_platform,
|
|
393
413
|
tool_name=tool_name,
|
|
394
414
|
tool_args=clean_args,
|
|
415
|
+
tool_version=tool_version,
|
|
416
|
+
tool_digest=tool_digest,
|
|
395
417
|
policy_context=self._policy_context(session_id, f"{self.config.platform}_tool_call", policy_context, tool_name=tool_name),
|
|
396
418
|
)
|
|
397
419
|
if self.verdict(response) == "BLOCKED":
|
|
@@ -421,6 +443,8 @@ class FrameworkFirewallBase:
|
|
|
421
443
|
tool_args: Dict[str, Any],
|
|
422
444
|
text: str = "",
|
|
423
445
|
tool_platform: Optional[str] = None,
|
|
446
|
+
tool_version: Optional[str] = None,
|
|
447
|
+
tool_digest: Optional[str] = None,
|
|
424
448
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
425
449
|
) -> Dict[str, Any]:
|
|
426
450
|
clean_args = self.strip_private_args(tool_args)
|
|
@@ -435,6 +459,8 @@ class FrameworkFirewallBase:
|
|
|
435
459
|
tool_platform=tool_platform or self.config.default_tool_platform,
|
|
436
460
|
tool_name=tool_name,
|
|
437
461
|
tool_args=clean_args,
|
|
462
|
+
tool_version=tool_version,
|
|
463
|
+
tool_digest=tool_digest,
|
|
438
464
|
session_id=session_id,
|
|
439
465
|
direction="outbound",
|
|
440
466
|
policy_context=self._policy_context(
|
|
@@ -456,7 +482,7 @@ class FrameworkFirewallBase:
|
|
|
456
482
|
target_agent_id=specialist_agent_id,
|
|
457
483
|
tool_name=tool_name,
|
|
458
484
|
tool_args=clean_args,
|
|
459
|
-
record=DecisionTokenRecord(token, manager_agent_id, time.time(), self._token_hmac(token)),
|
|
485
|
+
record=DecisionTokenRecord(token, manager_agent_id, time.time(), self._token_hmac(token), tool_version or "", tool_digest or ""),
|
|
460
486
|
ttl_s=self.config.handoff_token_ttl_s,
|
|
461
487
|
)
|
|
462
488
|
return env
|
|
@@ -475,6 +501,8 @@ class FrameworkFirewallBase:
|
|
|
475
501
|
tool_args: Dict[str, Any],
|
|
476
502
|
decision_token: Optional[str] = None,
|
|
477
503
|
source_agent_id: Optional[str] = None,
|
|
504
|
+
tool_version: Optional[str] = None,
|
|
505
|
+
tool_digest: Optional[str] = None,
|
|
478
506
|
) -> Dict[str, Any]:
|
|
479
507
|
clean_args = self.strip_private_args(tool_args)
|
|
480
508
|
record = None
|
|
@@ -488,6 +516,12 @@ class FrameworkFirewallBase:
|
|
|
488
516
|
if record:
|
|
489
517
|
decision_token = record.decision_token
|
|
490
518
|
source_agent_id = source_agent_id or record.source_agent_id
|
|
519
|
+
if tool_version and record.tool_version and tool_version != record.tool_version:
|
|
520
|
+
raise self.denied_error("AgenticDome delegated tool version differs from the authorized version.")
|
|
521
|
+
if tool_digest and record.tool_digest and tool_digest != record.tool_digest:
|
|
522
|
+
raise self.denied_error("AgenticDome delegated tool digest differs from the authorized digest.")
|
|
523
|
+
tool_version = tool_version or record.tool_version or None
|
|
524
|
+
tool_digest = tool_digest or record.tool_digest or None
|
|
491
525
|
if record and self.config.token_hmac_secret:
|
|
492
526
|
expected = self._token_hmac(record.decision_token)
|
|
493
527
|
if not record.token_hmac or not hmac.compare_digest(record.token_hmac, expected):
|
|
@@ -500,6 +534,8 @@ class FrameworkFirewallBase:
|
|
|
500
534
|
decision_token,
|
|
501
535
|
tool_name=tool_name,
|
|
502
536
|
tool_args=clean_args,
|
|
537
|
+
tool_version=tool_version,
|
|
538
|
+
tool_digest=tool_digest,
|
|
503
539
|
agent_id=specialist_agent_id,
|
|
504
540
|
source_agent_id=source_agent_id,
|
|
505
541
|
platform=self.config.platform,
|
|
@@ -611,4 +647,3 @@ class FrameworkFirewallBase:
|
|
|
611
647
|
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
612
648
|
return self.wrap_tool_handler(tool_name=tool_name or fn.__name__, handler=fn, **options)
|
|
613
649
|
return decorator
|
|
614
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import os
|
|
3
|
-
|
|
3
|
+
import re
|
|
4
|
+
from typing import Optional, Dict, List, Any, Tuple, Union, Mapping
|
|
4
5
|
|
|
5
6
|
import requests
|
|
6
7
|
from requests.adapters import HTTPAdapter
|
|
@@ -58,6 +59,7 @@ class AgentGuardClient:
|
|
|
58
59
|
pool_maxsize: int = 100,
|
|
59
60
|
pool_block: bool = False,
|
|
60
61
|
service_token: Optional[str] = None,
|
|
62
|
+
tool_provenance: Optional[Mapping[str, Mapping[str, str]]] = None,
|
|
61
63
|
):
|
|
62
64
|
self.api_base = self._require_nonempty("api_base", api_base).rstrip("/")
|
|
63
65
|
self.api_key = self._require_nonempty(
|
|
@@ -84,6 +86,13 @@ class AgentGuardClient:
|
|
|
84
86
|
default=5.0,
|
|
85
87
|
)
|
|
86
88
|
self.user_agent = user_agent
|
|
89
|
+
self._tool_provenance: Dict[str, Dict[str, str]] = {}
|
|
90
|
+
for registered_name, provenance in dict(tool_provenance or {}).items():
|
|
91
|
+
self.register_tool_provenance(
|
|
92
|
+
registered_name,
|
|
93
|
+
tool_version=(provenance or {}).get("tool_version") or (provenance or {}).get("version"),
|
|
94
|
+
tool_digest=(provenance or {}).get("tool_digest") or (provenance or {}).get("digest"),
|
|
95
|
+
)
|
|
87
96
|
|
|
88
97
|
self.session = requests.Session()
|
|
89
98
|
retry = Retry(
|
|
@@ -158,6 +167,55 @@ class AgentGuardClient:
|
|
|
158
167
|
s = str(value).strip()
|
|
159
168
|
return s or None
|
|
160
169
|
|
|
170
|
+
def register_tool_provenance(
|
|
171
|
+
self,
|
|
172
|
+
tool_name: str,
|
|
173
|
+
*,
|
|
174
|
+
tool_version: Optional[str] = None,
|
|
175
|
+
tool_digest: Optional[str] = None,
|
|
176
|
+
tool_platform: Optional[str] = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Register precomputed provenance once; request-time lookup is local O(1)."""
|
|
179
|
+
name = self._require_nonempty("tool_name", tool_name)
|
|
180
|
+
version = self._normalize_optional_string(tool_version)
|
|
181
|
+
digest = self._normalize_optional_string(tool_digest)
|
|
182
|
+
if digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", digest):
|
|
183
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
184
|
+
if version is None and digest is None:
|
|
185
|
+
raise ValueError("tool_version or tool_digest is required")
|
|
186
|
+
key = f"{self._normalize_optional_string(tool_platform)}:{name}" if tool_platform else name
|
|
187
|
+
self._tool_provenance[key] = {"tool_version": version or "", "tool_digest": digest or ""}
|
|
188
|
+
|
|
189
|
+
def unregister_tool_provenance(self, tool_name: str, *, tool_platform: Optional[str] = None) -> None:
|
|
190
|
+
name = self._require_nonempty("tool_name", tool_name)
|
|
191
|
+
key = f"{self._normalize_optional_string(tool_platform)}:{name}" if tool_platform else name
|
|
192
|
+
self._tool_provenance.pop(key, None)
|
|
193
|
+
|
|
194
|
+
def _resolve_tool_provenance(
|
|
195
|
+
self,
|
|
196
|
+
*,
|
|
197
|
+
tool_name: Optional[str],
|
|
198
|
+
tool_version: Optional[str],
|
|
199
|
+
tool_digest: Optional[str],
|
|
200
|
+
tool_platform: Optional[str] = None,
|
|
201
|
+
policy_context: Optional[Dict[str, Any]] = None,
|
|
202
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
203
|
+
if not tool_name:
|
|
204
|
+
return tool_version, tool_digest
|
|
205
|
+
context = policy_context or {}
|
|
206
|
+
context_provenance = context.get("tool_provenance") if isinstance(context.get("tool_provenance"), dict) else {}
|
|
207
|
+
key = f"{tool_platform}:{tool_name}" if tool_platform else ""
|
|
208
|
+
registered = self._tool_provenance.get(key) or self._tool_provenance.get(str(tool_name)) or {}
|
|
209
|
+
version = self._normalize_optional_string(tool_version) or self._normalize_optional_string(
|
|
210
|
+
context.get("tool_version") or context_provenance.get("tool_version") or context_provenance.get("version") or registered.get("tool_version")
|
|
211
|
+
)
|
|
212
|
+
digest = self._normalize_optional_string(tool_digest) or self._normalize_optional_string(
|
|
213
|
+
context.get("tool_digest") or context_provenance.get("tool_digest") or context_provenance.get("digest") or registered.get("tool_digest")
|
|
214
|
+
)
|
|
215
|
+
if digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", digest):
|
|
216
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
217
|
+
return version, digest
|
|
218
|
+
|
|
161
219
|
def _positive_float(self, name: str, value: Any, *, default: float) -> float:
|
|
162
220
|
try:
|
|
163
221
|
parsed = float(value)
|
|
@@ -257,6 +315,7 @@ class AgentGuardClient:
|
|
|
257
315
|
token: str,
|
|
258
316
|
tool_name: Optional[str] = None,
|
|
259
317
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
318
|
+
tool_digest: Optional[str] = None,
|
|
260
319
|
) -> None:
|
|
261
320
|
"""
|
|
262
321
|
Validate decision-token verification inputs.
|
|
@@ -268,6 +327,8 @@ class AgentGuardClient:
|
|
|
268
327
|
raise ValueError("'tool_args' is required when 'tool_name' is provided")
|
|
269
328
|
if tool_args is not None and not tool_name:
|
|
270
329
|
raise ValueError("'tool_name' is required when 'tool_args' is provided")
|
|
330
|
+
if tool_digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", str(tool_digest)):
|
|
331
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
271
332
|
|
|
272
333
|
def _request(
|
|
273
334
|
self,
|
|
@@ -453,6 +514,8 @@ class AgentGuardClient:
|
|
|
453
514
|
tool_platform: Optional[str] = None,
|
|
454
515
|
tool_name: Optional[str] = None,
|
|
455
516
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
517
|
+
tool_version: Optional[str] = None,
|
|
518
|
+
tool_digest: Optional[str] = None,
|
|
456
519
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
457
520
|
reasoning_trace: Optional[str] = None,
|
|
458
521
|
agent_instance_id: Optional[str] = None,
|
|
@@ -491,6 +554,13 @@ class AgentGuardClient:
|
|
|
491
554
|
Delegation rule:
|
|
492
555
|
- source_agent_id requires source_platform
|
|
493
556
|
"""
|
|
557
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
558
|
+
tool_name=tool_name,
|
|
559
|
+
tool_version=tool_version,
|
|
560
|
+
tool_digest=tool_digest,
|
|
561
|
+
tool_platform=tool_platform,
|
|
562
|
+
policy_context=policy_context,
|
|
563
|
+
)
|
|
494
564
|
normalized_direction = self._validate_guardrail_args(
|
|
495
565
|
text=text,
|
|
496
566
|
agent_id=agent_id,
|
|
@@ -502,6 +572,8 @@ class AgentGuardClient:
|
|
|
502
572
|
source_platform=source_platform,
|
|
503
573
|
user_id=user_id,
|
|
504
574
|
)
|
|
575
|
+
if tool_digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", str(tool_digest)):
|
|
576
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
505
577
|
|
|
506
578
|
merged_policy_context = self._merge_policy_context(
|
|
507
579
|
policy_context,
|
|
@@ -511,6 +583,8 @@ class AgentGuardClient:
|
|
|
511
583
|
tool_platform=tool_platform,
|
|
512
584
|
tool_name=tool_name,
|
|
513
585
|
tool_args=tool_args,
|
|
586
|
+
tool_version=tool_version,
|
|
587
|
+
tool_digest=tool_digest,
|
|
514
588
|
reasoning_trace=reasoning_trace,
|
|
515
589
|
agent_instance_id=agent_instance_id,
|
|
516
590
|
user_id=user_id,
|
|
@@ -540,6 +614,8 @@ class AgentGuardClient:
|
|
|
540
614
|
"tool_platform": tool_platform,
|
|
541
615
|
"tool_name": tool_name,
|
|
542
616
|
"tool_args": tool_args,
|
|
617
|
+
"tool_version": tool_version,
|
|
618
|
+
"tool_digest": tool_digest,
|
|
543
619
|
"policy_context": merged_policy_context,
|
|
544
620
|
"reasoning_trace": reasoning_trace,
|
|
545
621
|
"agent_instance_id": agent_instance_id,
|
|
@@ -780,6 +856,8 @@ class AgentGuardClient:
|
|
|
780
856
|
source_platform: str,
|
|
781
857
|
tool_name: str,
|
|
782
858
|
tool_args: Dict[str, Any],
|
|
859
|
+
tool_version: Optional[str] = None,
|
|
860
|
+
tool_digest: Optional[str] = None,
|
|
783
861
|
tool_platform: Optional[str] = None,
|
|
784
862
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
785
863
|
session_id: Optional[str] = None,
|
|
@@ -830,6 +908,15 @@ class AgentGuardClient:
|
|
|
830
908
|
self._require_nonempty("source_agent_id", source_agent_id)
|
|
831
909
|
self._require_nonempty("source_platform", source_platform)
|
|
832
910
|
self._require_nonempty("tool_name", tool_name)
|
|
911
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
912
|
+
tool_name=tool_name,
|
|
913
|
+
tool_version=tool_version,
|
|
914
|
+
tool_digest=tool_digest,
|
|
915
|
+
tool_platform=tool_platform,
|
|
916
|
+
policy_context=policy_context,
|
|
917
|
+
)
|
|
918
|
+
if tool_digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", str(tool_digest)):
|
|
919
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
833
920
|
|
|
834
921
|
normalized_direction = self._validate_guardrail_args(
|
|
835
922
|
text=text,
|
|
@@ -851,6 +938,8 @@ class AgentGuardClient:
|
|
|
851
938
|
source_agent_id=source_agent_id,
|
|
852
939
|
tool_name=tool_name,
|
|
853
940
|
tool_args=tool_args,
|
|
941
|
+
tool_version=tool_version,
|
|
942
|
+
tool_digest=tool_digest,
|
|
854
943
|
request_purpose=request_purpose,
|
|
855
944
|
purpose=purpose,
|
|
856
945
|
intent=intent,
|
|
@@ -887,6 +976,8 @@ class AgentGuardClient:
|
|
|
887
976
|
"tool_platform": tool_platform,
|
|
888
977
|
"tool_name": tool_name,
|
|
889
978
|
"tool_args": tool_args,
|
|
979
|
+
"tool_version": tool_version,
|
|
980
|
+
"tool_digest": tool_digest,
|
|
890
981
|
"policy_context": merged_policy_context,
|
|
891
982
|
"source_agent_id": source_agent_id,
|
|
892
983
|
"request_purpose": request_purpose,
|
|
@@ -937,6 +1028,8 @@ class AgentGuardClient:
|
|
|
937
1028
|
*,
|
|
938
1029
|
tool_name: Optional[str] = None,
|
|
939
1030
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
1031
|
+
tool_version: Optional[str] = None,
|
|
1032
|
+
tool_digest: Optional[str] = None,
|
|
940
1033
|
agent_id: Optional[str] = None,
|
|
941
1034
|
source_agent_id: Optional[str] = None,
|
|
942
1035
|
platform: Optional[str] = None,
|
|
@@ -958,10 +1051,17 @@ class AgentGuardClient:
|
|
|
958
1051
|
- platform
|
|
959
1052
|
- source_agent_id (for delegated sensitive flows)
|
|
960
1053
|
"""
|
|
1054
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
1055
|
+
tool_name=tool_name,
|
|
1056
|
+
tool_version=tool_version,
|
|
1057
|
+
tool_digest=tool_digest,
|
|
1058
|
+
tool_platform=platform,
|
|
1059
|
+
)
|
|
961
1060
|
self._validate_decision_verify_args(
|
|
962
1061
|
token=token,
|
|
963
1062
|
tool_name=tool_name,
|
|
964
1063
|
tool_args=tool_args,
|
|
1064
|
+
tool_digest=tool_digest,
|
|
965
1065
|
)
|
|
966
1066
|
|
|
967
1067
|
payload = self._drop_none(
|
|
@@ -969,6 +1069,8 @@ class AgentGuardClient:
|
|
|
969
1069
|
"token": token,
|
|
970
1070
|
"tool_name": tool_name,
|
|
971
1071
|
"tool_args": tool_args,
|
|
1072
|
+
"tool_version": tool_version,
|
|
1073
|
+
"tool_digest": tool_digest,
|
|
972
1074
|
"agent_id": agent_id,
|
|
973
1075
|
"source_agent_id": source_agent_id,
|
|
974
1076
|
"platform": platform,
|
|
@@ -989,6 +1091,8 @@ class AgentGuardClient:
|
|
|
989
1091
|
*,
|
|
990
1092
|
tool_name: Optional[str] = None,
|
|
991
1093
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
1094
|
+
tool_version: Optional[str] = None,
|
|
1095
|
+
tool_digest: Optional[str] = None,
|
|
992
1096
|
agent_id: Optional[str] = None,
|
|
993
1097
|
source_agent_id: Optional[str] = None,
|
|
994
1098
|
platform: Optional[str] = None,
|
|
@@ -1011,10 +1115,17 @@ class AgentGuardClient:
|
|
|
1011
1115
|
- platform
|
|
1012
1116
|
- source_agent_id (for delegated sensitive flows)
|
|
1013
1117
|
"""
|
|
1118
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
1119
|
+
tool_name=tool_name,
|
|
1120
|
+
tool_version=tool_version,
|
|
1121
|
+
tool_digest=tool_digest,
|
|
1122
|
+
tool_platform=platform,
|
|
1123
|
+
)
|
|
1014
1124
|
self._validate_decision_verify_args(
|
|
1015
1125
|
token=token,
|
|
1016
1126
|
tool_name=tool_name,
|
|
1017
1127
|
tool_args=tool_args,
|
|
1128
|
+
tool_digest=tool_digest,
|
|
1018
1129
|
)
|
|
1019
1130
|
|
|
1020
1131
|
arguments = self._drop_none(
|
|
@@ -1022,6 +1133,8 @@ class AgentGuardClient:
|
|
|
1022
1133
|
"token": token,
|
|
1023
1134
|
"tool_name": tool_name,
|
|
1024
1135
|
"tool_args": tool_args,
|
|
1136
|
+
"tool_version": tool_version,
|
|
1137
|
+
"tool_digest": tool_digest,
|
|
1025
1138
|
"agent_id": agent_id,
|
|
1026
1139
|
"source_agent_id": source_agent_id,
|
|
1027
1140
|
"platform": platform,
|
|
@@ -1111,6 +1224,8 @@ class AgentGuardClient:
|
|
|
1111
1224
|
tool_platform: Optional[str] = None,
|
|
1112
1225
|
tool_name: Optional[str] = None,
|
|
1113
1226
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
1227
|
+
tool_version: Optional[str] = None,
|
|
1228
|
+
tool_digest: Optional[str] = None,
|
|
1114
1229
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
1115
1230
|
direction: str = "outbound",
|
|
1116
1231
|
source_agent_id: Optional[str] = None,
|
|
@@ -1149,6 +1264,13 @@ class AgentGuardClient:
|
|
|
1149
1264
|
Delegation rule:
|
|
1150
1265
|
- source_agent_id requires source_platform
|
|
1151
1266
|
"""
|
|
1267
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
1268
|
+
tool_name=tool_name,
|
|
1269
|
+
tool_version=tool_version,
|
|
1270
|
+
tool_digest=tool_digest,
|
|
1271
|
+
tool_platform=tool_platform,
|
|
1272
|
+
policy_context=policy_context,
|
|
1273
|
+
)
|
|
1152
1274
|
normalized_direction = self._validate_guardrail_args(
|
|
1153
1275
|
text=text,
|
|
1154
1276
|
agent_id=agent_id,
|
|
@@ -1170,6 +1292,8 @@ class AgentGuardClient:
|
|
|
1170
1292
|
user_id=user_id,
|
|
1171
1293
|
tool_name=tool_name,
|
|
1172
1294
|
tool_args=tool_args,
|
|
1295
|
+
tool_version=tool_version,
|
|
1296
|
+
tool_digest=tool_digest,
|
|
1173
1297
|
reasoning_trace=reasoning_trace,
|
|
1174
1298
|
request_purpose=request_purpose,
|
|
1175
1299
|
purpose=purpose,
|
|
@@ -1195,6 +1319,8 @@ class AgentGuardClient:
|
|
|
1195
1319
|
"tool_platform": tool_platform,
|
|
1196
1320
|
"tool_name": tool_name,
|
|
1197
1321
|
"tool_args": tool_args,
|
|
1322
|
+
"tool_version": tool_version,
|
|
1323
|
+
"tool_digest": tool_digest,
|
|
1198
1324
|
"policy_context": merged_policy_context,
|
|
1199
1325
|
"source_agent_id": source_agent_id,
|
|
1200
1326
|
"user_id": user_id,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agenticdome-python-sdk"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.7"
|
|
8
8
|
description = "Official Python SDK for AgenticDome security guardrails, DLP, tool authorization, and multi-agent delegation enforcement."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_autogen_integration.py
RENAMED
|
@@ -20,6 +20,10 @@ class FakeClient:
|
|
|
20
20
|
self.mesh_calls = []
|
|
21
21
|
self.incidents = []
|
|
22
22
|
self.revocations = []
|
|
23
|
+
self.provenance = {}
|
|
24
|
+
|
|
25
|
+
def register_tool_provenance(self, tool_name, **kwargs):
|
|
26
|
+
self.provenance[tool_name] = kwargs
|
|
23
27
|
|
|
24
28
|
def guardrail_validate(self, **kwargs):
|
|
25
29
|
self.guardrail_calls.append(kwargs)
|
|
@@ -69,6 +73,24 @@ def test_autogen_requires_agenticdome_runtime_configuration():
|
|
|
69
73
|
AgenticDomeAutoGenFirewall(FirewallConfig(api_base="", api_key="", tenant_id=""))
|
|
70
74
|
|
|
71
75
|
|
|
76
|
+
def test_autogen_forwards_explicit_tool_provenance_and_supports_cached_registration():
|
|
77
|
+
fw, client = firewall()
|
|
78
|
+
digest = "sha256:" + "c" * 64
|
|
79
|
+
fw.register_tool_provenance("crm.lookup", tool_version="3.0.0", tool_digest=digest)
|
|
80
|
+
fw.authorize_tool_call(
|
|
81
|
+
session_id="tool-provenance",
|
|
82
|
+
agent_id="planner",
|
|
83
|
+
tool_name="crm.lookup",
|
|
84
|
+
tool_args={"customer_id": "42"},
|
|
85
|
+
tool_version="3.0.0",
|
|
86
|
+
tool_digest=digest,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert client.provenance["crm.lookup"]["tool_digest"] == digest
|
|
90
|
+
assert client.guardrail_calls[-1]["tool_version"] == "3.0.0"
|
|
91
|
+
assert client.guardrail_calls[-1]["tool_digest"] == digest
|
|
92
|
+
|
|
93
|
+
|
|
72
94
|
def test_conversation_window_sends_family_two_metrics_and_redacts_output():
|
|
73
95
|
fw, client = firewall()
|
|
74
96
|
|
|
@@ -90,6 +90,65 @@ def test_guardrail_validate_success(mock_request, client):
|
|
|
90
90
|
assert payload["platform"] == "microsoft"
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
94
|
+
def test_registered_tool_provenance_is_forwarded_without_runtime_hashing(mock_request, client):
|
|
95
|
+
digest = "sha256:" + "a" * 64
|
|
96
|
+
mock_request.return_value = make_response(200, {"verdict": "ALLOWED"})
|
|
97
|
+
client.register_tool_provenance("crm.lookup", tool_version="4.2.1", tool_digest=digest, tool_platform="mcp")
|
|
98
|
+
|
|
99
|
+
client.guardrail_validate(
|
|
100
|
+
text="lookup customer",
|
|
101
|
+
agent_id="support-agent",
|
|
102
|
+
direction="outbound",
|
|
103
|
+
platform="langgraph",
|
|
104
|
+
tool_platform="mcp",
|
|
105
|
+
tool_name="crm.lookup",
|
|
106
|
+
tool_args={"customer_id": "42"},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
payload = mock_request.call_args.kwargs["json"]
|
|
110
|
+
assert payload["tool_version"] == "4.2.1"
|
|
111
|
+
assert payload["tool_digest"] == digest
|
|
112
|
+
assert payload["policy_context"]["tool_digest"] == digest
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
116
|
+
def test_registered_provenance_is_bound_to_authorize_and_verify(mock_request, client):
|
|
117
|
+
digest = "sha256:" + "b" * 64
|
|
118
|
+
mock_request.return_value = make_response(200, {"jsonrpc": "2.0", "id": "1", "result": {"verdict": "ALLOWED", "valid": True}})
|
|
119
|
+
client.register_tool_provenance("payments.refund", tool_version="2.0.0", tool_digest=digest)
|
|
120
|
+
|
|
121
|
+
client.a2a_authorize_tool(
|
|
122
|
+
text="delegate refund",
|
|
123
|
+
agent_id="billing-agent",
|
|
124
|
+
platform="autogen",
|
|
125
|
+
source_agent_id="manager-agent",
|
|
126
|
+
source_platform="autogen",
|
|
127
|
+
tool_name="payments.refund",
|
|
128
|
+
tool_args={"amount": 25},
|
|
129
|
+
)
|
|
130
|
+
authorize = mock_request.call_args.kwargs["json"]["params"]["arguments"]
|
|
131
|
+
assert authorize["tool_version"] == "2.0.0"
|
|
132
|
+
assert authorize["tool_digest"] == digest
|
|
133
|
+
|
|
134
|
+
client.a2a_verify_decision_token_rpc(
|
|
135
|
+
"decision-token",
|
|
136
|
+
tool_name="payments.refund",
|
|
137
|
+
tool_args={"amount": 25},
|
|
138
|
+
agent_id="billing-agent",
|
|
139
|
+
source_agent_id="manager-agent",
|
|
140
|
+
platform="autogen",
|
|
141
|
+
)
|
|
142
|
+
verify = mock_request.call_args.kwargs["json"]["params"]["arguments"]
|
|
143
|
+
assert verify["tool_version"] == "2.0.0"
|
|
144
|
+
assert verify["tool_digest"] == digest
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_invalid_registered_tool_digest_fails_before_network(client):
|
|
148
|
+
with pytest.raises(ValueError, match="tool_digest"):
|
|
149
|
+
client.register_tool_provenance("unsafe.tool", tool_digest="sha256:not-a-real-digest")
|
|
150
|
+
|
|
151
|
+
|
|
93
152
|
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
94
153
|
def test_guardrail_validate_blocks_invalid_tool_pairing(mock_request, client):
|
|
95
154
|
with pytest.raises(ValueError, match="'tool_args' is required"):
|
|
@@ -297,6 +356,26 @@ def test_mcp_guardrail_validate(mock_request, client):
|
|
|
297
356
|
assert arguments["tool_name"] == "mcp.execute_remote_patch"
|
|
298
357
|
|
|
299
358
|
|
|
359
|
+
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
360
|
+
def test_mcp_guardrail_uses_registered_tool_provenance(mock_request, client):
|
|
361
|
+
digest = "sha256:" + "d" * 64
|
|
362
|
+
mock_request.return_value = make_response(200, {"jsonrpc": "2.0", "id": "1", "result": {"verdict": "ALLOWED"}})
|
|
363
|
+
client.register_tool_provenance("mcp.customer.lookup", tool_version="1.8.0", tool_digest=digest, tool_platform="mcp")
|
|
364
|
+
|
|
365
|
+
client.mcp_guardrail_validate(
|
|
366
|
+
text="lookup customer",
|
|
367
|
+
agent_id="mcp-agent",
|
|
368
|
+
platform="mcp",
|
|
369
|
+
tool_platform="mcp",
|
|
370
|
+
tool_name="mcp.customer.lookup",
|
|
371
|
+
tool_args={"customer_id": "42"},
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
arguments = mock_request.call_args.kwargs["json"]["params"]["arguments"]
|
|
375
|
+
assert arguments["tool_version"] == "1.8.0"
|
|
376
|
+
assert arguments["tool_digest"] == digest
|
|
377
|
+
|
|
378
|
+
|
|
300
379
|
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
301
380
|
def test_mesh_validate(mock_request, client):
|
|
302
381
|
mock_request.return_value = make_response(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/aws_bedrock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/agenticdome_sdk/openai_agents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_agno_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_aws_bedrock_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_claude_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_crewai_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_google_adk_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_identity_context.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_langgraph_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_llamaindex_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_mcp_host_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_pydanticai_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.7}/tests/test_smolagents_integration.py
RENAMED
|
File without changes
|