agentrust-py 0.0.3__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.
- agentrust/__init__.py +72 -0
- agentrust_py-0.0.3.dist-info/METADATA +193 -0
- agentrust_py-0.0.3.dist-info/RECORD +29 -0
- agentrust_py-0.0.3.dist-info/WHEEL +4 -0
- agentrust_py-0.0.3.dist-info/entry_points.txt +2 -0
- agentrust_py-0.0.3.dist-info/licenses/LICENSE +177 -0
- agentrust_sdk/__init__.py +124 -0
- agentrust_sdk/adapters/__init__.py +1 -0
- agentrust_sdk/adapters/autogen.py +235 -0
- agentrust_sdk/adapters/claude_agents.py +225 -0
- agentrust_sdk/adapters/crewai.py +98 -0
- agentrust_sdk/adapters/langgraph.py +109 -0
- agentrust_sdk/adapters/mcp.py +193 -0
- agentrust_sdk/adapters/openai_agents.py +263 -0
- agentrust_sdk/auth.py +192 -0
- agentrust_sdk/auto.py +397 -0
- agentrust_sdk/autoload.py +95 -0
- agentrust_sdk/cli.py +736 -0
- agentrust_sdk/client.py +790 -0
- agentrust_sdk/config.py +192 -0
- agentrust_sdk/decorator.py +276 -0
- agentrust_sdk/embedded.py +428 -0
- agentrust_sdk/hooks.py +461 -0
- agentrust_sdk/models.py +81 -0
- agentrust_sdk/py.typed +0 -0
- agentrust_sdk/queue_replay.py +204 -0
- agentrust_sdk/tiers.py +180 -0
- agentrust_sdk/version_negotiation.py +290 -0
- agentrust_sdk/webhooks.py +782 -0
agentrust_sdk/config.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentTrust SDK — centralized configuration.
|
|
3
|
+
|
|
4
|
+
Config is resolved lazily on first access so that env-var injection at
|
|
5
|
+
runtime (secrets managers, test fixtures) takes effect. The singleton
|
|
6
|
+
SDK_CONFIG is still the primary entry point; call SDK_CONFIG.reload() after
|
|
7
|
+
mutating os.environ in tests or container entrypoints.
|
|
8
|
+
|
|
9
|
+
Environment variables
|
|
10
|
+
---------------------
|
|
11
|
+
AGENTRUST_ENABLED "true"/"false" — master kill-switch (default true)
|
|
12
|
+
AGENTRUST_GATEWAY_URL Gateway base URL (default http://localhost:8000)
|
|
13
|
+
AGENTRUST_KEY API key (overrides ~/.agentrust/config.yaml)
|
|
14
|
+
AGENTRUST_TIMEOUT_SEC Per-request HTTP timeout in seconds (default 10)
|
|
15
|
+
AGENTRUST_RETRY_ATTEMPTS Max retry attempts on transient failure (default 3)
|
|
16
|
+
AGENTRUST_RETRY_BACKOFF Initial backoff in seconds for exponential retry (default 0.5)
|
|
17
|
+
AGENTRUST_FAILURE_MODE open | closed | queue (default open)
|
|
18
|
+
open — log and continue on gateway unreachable
|
|
19
|
+
closed — raise BlockedError on gateway unreachable
|
|
20
|
+
queue — buffer locally and replay when gateway returns
|
|
21
|
+
AGENTRUST_QUEUE_DB SQLite path for failure-mode=queue buffer
|
|
22
|
+
(default ~/.agentrust/queue.db)
|
|
23
|
+
AGENTRUST_SDK_VERSION Set automatically; override only in tests
|
|
24
|
+
|
|
25
|
+
Webhook environment variables (Team tier and above)
|
|
26
|
+
----------------------------------------------------
|
|
27
|
+
AGENTRUST_WEBHOOK_URL Single webhook URL to register on SDK init.
|
|
28
|
+
Supports Discord (https://discord.com/api/webhooks/...)
|
|
29
|
+
and any generic HTTPS endpoint.
|
|
30
|
+
AGENTRUST_WEBHOOK_EVENTS Comma-separated decision filter for the env-var webhook.
|
|
31
|
+
Valid values: all | approve | block | escalate | request_evidence
|
|
32
|
+
Defaults to "all" when AGENTRUST_WEBHOOK_URL is set.
|
|
33
|
+
AGENTRUST_WEBHOOK_DB SQLite path for the webhook registry
|
|
34
|
+
(default ~/.agentrust/webhooks.db)
|
|
35
|
+
AGENTRUST_WEBHOOK_TIMEOUT Per-dispatch HTTP timeout in seconds (default 5)
|
|
36
|
+
AGENTRUST_WEBHOOK_AUTO_DISPATCH
|
|
37
|
+
"true"/"false" — fire webhooks automatically after
|
|
38
|
+
every validate() call when a dispatcher is attached
|
|
39
|
+
to the client (default true)
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import logging
|
|
44
|
+
import os
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
_VALID_FAILURE_MODES = frozenset({"open", "closed", "queue"})
|
|
50
|
+
_SDK_VERSION = "0.0.1a1"
|
|
51
|
+
_MIN_GATEWAY_VERSION = "0.0.1a1"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _bool(key: str, default: bool) -> bool:
|
|
55
|
+
val = os.environ.get(key, "").lower()
|
|
56
|
+
if val in ("1", "true", "yes"):
|
|
57
|
+
return True
|
|
58
|
+
if val in ("0", "false", "no"):
|
|
59
|
+
return False
|
|
60
|
+
return default
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _float(key: str, default: float) -> float:
|
|
64
|
+
try:
|
|
65
|
+
return float(os.environ[key])
|
|
66
|
+
except (KeyError, ValueError):
|
|
67
|
+
return default
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _int(key: str, default: int) -> int:
|
|
71
|
+
try:
|
|
72
|
+
return int(os.environ[key])
|
|
73
|
+
except (KeyError, ValueError):
|
|
74
|
+
return default
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _failure_mode(key: str, default: str) -> str:
|
|
78
|
+
val = os.environ.get(key, default).lower()
|
|
79
|
+
if val not in _VALID_FAILURE_MODES:
|
|
80
|
+
logger.warning(
|
|
81
|
+
"[AgentTrust] Invalid %s=%r — must be one of %s. Falling back to 'open'.",
|
|
82
|
+
key, val, sorted(_VALID_FAILURE_MODES),
|
|
83
|
+
)
|
|
84
|
+
return "open"
|
|
85
|
+
return val
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class _SDKConfig:
|
|
89
|
+
"""
|
|
90
|
+
Lazily-resolved env-var config. All properties are read from os.environ
|
|
91
|
+
on each attribute access so runtime mutations (secrets managers, test
|
|
92
|
+
fixtures) take effect without a process restart.
|
|
93
|
+
|
|
94
|
+
Call reload() to invalidate any cached state (currently a no-op since
|
|
95
|
+
nothing is cached, but provided for forward-compat and test ergonomics).
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# Immutable constants — never come from env
|
|
99
|
+
sdk_version: str = _SDK_VERSION
|
|
100
|
+
min_gateway_version: str = _MIN_GATEWAY_VERSION
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def enabled(self) -> bool:
|
|
104
|
+
return _bool("AGENTRUST_ENABLED", True)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def gateway_url(self) -> str:
|
|
108
|
+
return os.environ.get("AGENTRUST_GATEWAY_URL", "http://localhost:8000")
|
|
109
|
+
|
|
110
|
+
@gateway_url.setter
|
|
111
|
+
def gateway_url(self, value: str) -> None:
|
|
112
|
+
# Allow embed_gateway() to override after the fact.
|
|
113
|
+
os.environ["AGENTRUST_GATEWAY_URL"] = value
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def api_key(self) -> str | None:
|
|
117
|
+
return os.environ.get("AGENTRUST_KEY") or None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def timeout_sec(self) -> float:
|
|
121
|
+
return _float("AGENTRUST_TIMEOUT_SEC", 10.0)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def retry_attempts(self) -> int:
|
|
125
|
+
return _int("AGENTRUST_RETRY_ATTEMPTS", 3)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def retry_backoff_sec(self) -> float:
|
|
129
|
+
return _float("AGENTRUST_RETRY_BACKOFF", 0.5)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def failure_mode(self) -> str:
|
|
133
|
+
return _failure_mode("AGENTRUST_FAILURE_MODE", "open")
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def queue_db(self) -> Path:
|
|
137
|
+
return Path(
|
|
138
|
+
os.environ.get("AGENTRUST_QUEUE_DB",
|
|
139
|
+
str(Path.home() / ".agentrust" / "queue.db"))
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# ── Webhook configuration (Team tier and above) ──────────────────────────
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def webhook_url(self) -> str | None:
|
|
146
|
+
"""Single webhook URL bootstrapped from AGENTRUST_WEBHOOK_URL (optional)."""
|
|
147
|
+
return os.environ.get("AGENTRUST_WEBHOOK_URL") or None
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def webhook_events(self) -> list[str]:
|
|
151
|
+
"""
|
|
152
|
+
Decision filter for the env-var webhook.
|
|
153
|
+
|
|
154
|
+
Parsed from AGENTRUST_WEBHOOK_EVENTS (comma-separated).
|
|
155
|
+
Defaults to ["all"] when the env var is absent or empty.
|
|
156
|
+
"""
|
|
157
|
+
raw = os.environ.get("AGENTRUST_WEBHOOK_EVENTS", "all").strip()
|
|
158
|
+
parts = [e.strip() for e in raw.split(",") if e.strip()]
|
|
159
|
+
return parts if parts else ["all"]
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def webhook_db(self) -> Path:
|
|
163
|
+
"""SQLite path for the webhook registry (default ~/.agentrust/webhooks.db)."""
|
|
164
|
+
return Path(
|
|
165
|
+
os.environ.get(
|
|
166
|
+
"AGENTRUST_WEBHOOK_DB",
|
|
167
|
+
str(Path.home() / ".agentrust" / "webhooks.db"),
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def webhook_timeout(self) -> float:
|
|
173
|
+
"""Per-dispatch HTTP timeout in seconds (default 5)."""
|
|
174
|
+
return _float("AGENTRUST_WEBHOOK_TIMEOUT", 5.0)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def webhook_auto_dispatch(self) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Whether the client should fire webhooks automatically after every
|
|
180
|
+
validate() call when a dispatcher is attached (default True).
|
|
181
|
+
"""
|
|
182
|
+
return _bool("AGENTRUST_WEBHOOK_AUTO_DISPATCH", True)
|
|
183
|
+
|
|
184
|
+
def is_failure_mode(self, mode: str) -> bool:
|
|
185
|
+
return self.failure_mode == mode
|
|
186
|
+
|
|
187
|
+
def reload(self) -> None:
|
|
188
|
+
"""No-op — all properties are already live. Provided for test ergonomics."""
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Module-level singleton — import as `from .config import SDK_CONFIG`
|
|
192
|
+
SDK_CONFIG = _SDKConfig()
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@harness — the single-line AgentTrust integration for any agent function.
|
|
3
|
+
|
|
4
|
+
Replaces the old @validate decorator. Reads config automatically from
|
|
5
|
+
~/.agentrust/config.yaml so no arguments are required in most cases.
|
|
6
|
+
|
|
7
|
+
Usage (zero config after `agentrust init`)::
|
|
8
|
+
|
|
9
|
+
from agentrust import harness
|
|
10
|
+
|
|
11
|
+
@harness
|
|
12
|
+
def run_payment(user: str, input: str) -> dict:
|
|
13
|
+
return {"amount": 500, "status": "processed"}
|
|
14
|
+
|
|
15
|
+
Advanced usage::
|
|
16
|
+
|
|
17
|
+
@harness(
|
|
18
|
+
agent_id="payment-agent", # override auto-detected name
|
|
19
|
+
block_on_block=True, # raise BlockedError when blocked
|
|
20
|
+
block_on_review=False,
|
|
21
|
+
raise_on_tier_gate=False, # don't raise when capability above tier
|
|
22
|
+
)
|
|
23
|
+
async def run_async_agent(user: str, input: str) -> dict:
|
|
24
|
+
return {"result": "done"}
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
import functools
|
|
30
|
+
import inspect
|
|
31
|
+
import logging
|
|
32
|
+
import time
|
|
33
|
+
from typing import Any, Callable
|
|
34
|
+
|
|
35
|
+
from .auth import read_config, resolve_key
|
|
36
|
+
from .config import SDK_CONFIG
|
|
37
|
+
from .tiers import Capability, Tier, is_allowed
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# BlockedError
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
class BlockedError(RuntimeError):
|
|
47
|
+
"""Raised when AgentTrust blocks or escalates an execution."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, outcome: str, reason: str, envelope_id: str) -> None:
|
|
50
|
+
super().__init__(f"AgentTrust [{outcome}]: {reason}")
|
|
51
|
+
self.outcome = outcome
|
|
52
|
+
self.reason = reason
|
|
53
|
+
self.envelope_id = envelope_id
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# @harness — supports both @harness and @harness(...) usage
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def harness(
|
|
61
|
+
fn: Callable | None = None,
|
|
62
|
+
*,
|
|
63
|
+
agent_id: str | None = None,
|
|
64
|
+
user_kwarg: str = "user",
|
|
65
|
+
input_kwarg: str = "input",
|
|
66
|
+
base_url: str | None = None,
|
|
67
|
+
api_key: str | None = None,
|
|
68
|
+
block_on_block: bool = True,
|
|
69
|
+
block_on_review: bool = False,
|
|
70
|
+
raise_on_tier_gate: bool = False,
|
|
71
|
+
framework: str | None = None,
|
|
72
|
+
raise_on_error: bool = False,
|
|
73
|
+
) -> Any:
|
|
74
|
+
"""
|
|
75
|
+
Decorator that validates agent output via AgentTrust after the function returns.
|
|
76
|
+
|
|
77
|
+
Can be used bare (@harness) or with arguments (@harness(...)).
|
|
78
|
+
Config is auto-resolved from ~/.agentrust/config.yaml if not provided.
|
|
79
|
+
"""
|
|
80
|
+
def _decorator(f: Callable) -> Callable:
|
|
81
|
+
# ── kill-switch: AGENTRUST_ENABLED=false → return original function unchanged ──
|
|
82
|
+
if not SDK_CONFIG.enabled:
|
|
83
|
+
return f
|
|
84
|
+
|
|
85
|
+
_agent_id = agent_id or f.__name__
|
|
86
|
+
_framework = framework or _detect_framework()
|
|
87
|
+
_base_url = base_url or _resolve_base_url()
|
|
88
|
+
_api_key = api_key
|
|
89
|
+
|
|
90
|
+
if inspect.iscoroutinefunction(f):
|
|
91
|
+
return _async_wrapper(
|
|
92
|
+
f, _agent_id, user_kwarg, input_kwarg,
|
|
93
|
+
_base_url, _api_key, block_on_block, block_on_review,
|
|
94
|
+
raise_on_tier_gate, _framework, raise_on_error,
|
|
95
|
+
)
|
|
96
|
+
return _sync_wrapper(
|
|
97
|
+
f, _agent_id, user_kwarg, input_kwarg,
|
|
98
|
+
_base_url, _api_key, block_on_block, block_on_review,
|
|
99
|
+
raise_on_tier_gate, _framework, raise_on_error,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if fn is not None:
|
|
103
|
+
if isinstance(fn, str):
|
|
104
|
+
# Used as @validate("agent-id") — legacy positional agent_id arg
|
|
105
|
+
_positional_id = fn
|
|
106
|
+
def _decorator_with_id(f: Callable) -> Callable:
|
|
107
|
+
_aid = _positional_id
|
|
108
|
+
_framework = framework or _detect_framework()
|
|
109
|
+
_base_url = base_url or _resolve_base_url()
|
|
110
|
+
if inspect.iscoroutinefunction(f):
|
|
111
|
+
return _async_wrapper(
|
|
112
|
+
f, _aid, user_kwarg, input_kwarg,
|
|
113
|
+
_base_url, api_key, block_on_block, block_on_review,
|
|
114
|
+
raise_on_tier_gate, _framework, raise_on_error,
|
|
115
|
+
)
|
|
116
|
+
return _sync_wrapper(
|
|
117
|
+
f, _aid, user_kwarg, input_kwarg,
|
|
118
|
+
_base_url, api_key, block_on_block, block_on_review,
|
|
119
|
+
raise_on_tier_gate, _framework, raise_on_error,
|
|
120
|
+
)
|
|
121
|
+
return _decorator_with_id
|
|
122
|
+
# Used as @harness (no parentheses)
|
|
123
|
+
return _decorator(fn)
|
|
124
|
+
|
|
125
|
+
# Used as @harness(...) (with arguments)
|
|
126
|
+
return _decorator
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Internal sync/async wrappers
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def _sync_wrapper(
|
|
134
|
+
fn: Callable,
|
|
135
|
+
agent_id: str,
|
|
136
|
+
user_kwarg: str,
|
|
137
|
+
input_kwarg: str,
|
|
138
|
+
base_url: str,
|
|
139
|
+
api_key: str | None,
|
|
140
|
+
block_on_block: bool,
|
|
141
|
+
block_on_review: bool,
|
|
142
|
+
raise_on_tier_gate: bool,
|
|
143
|
+
framework: str,
|
|
144
|
+
raise_on_error: bool,
|
|
145
|
+
) -> Callable:
|
|
146
|
+
from .client import AgentTrustClient
|
|
147
|
+
|
|
148
|
+
@functools.wraps(fn)
|
|
149
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
150
|
+
t0 = time.perf_counter()
|
|
151
|
+
result = fn(*args, **kwargs)
|
|
152
|
+
latency_ms = (time.perf_counter() - t0) * 1000
|
|
153
|
+
|
|
154
|
+
user = kwargs.get(user_kwarg, "unknown")
|
|
155
|
+
inp = kwargs.get(input_kwarg, "")
|
|
156
|
+
output = result if isinstance(result, dict) else {"result": str(result)}
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
with AgentTrustClient(
|
|
160
|
+
base_url=base_url,
|
|
161
|
+
api_key=api_key,
|
|
162
|
+
raise_on_tier_gate=raise_on_tier_gate,
|
|
163
|
+
) as client:
|
|
164
|
+
resp = client.validate(
|
|
165
|
+
agent_id=agent_id,
|
|
166
|
+
user=str(user),
|
|
167
|
+
input=str(inp),
|
|
168
|
+
output=output,
|
|
169
|
+
framework=framework,
|
|
170
|
+
latency_ms=latency_ms,
|
|
171
|
+
)
|
|
172
|
+
_check_decision(resp, block_on_block, block_on_review)
|
|
173
|
+
except BlockedError:
|
|
174
|
+
raise
|
|
175
|
+
except Exception as exc:
|
|
176
|
+
if raise_on_error:
|
|
177
|
+
raise
|
|
178
|
+
logger.warning("[AgentTrust] Validation failed (non-fatal): %s", exc)
|
|
179
|
+
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
return wrapper
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _async_wrapper(
|
|
186
|
+
fn: Callable,
|
|
187
|
+
agent_id: str,
|
|
188
|
+
user_kwarg: str,
|
|
189
|
+
input_kwarg: str,
|
|
190
|
+
base_url: str,
|
|
191
|
+
api_key: str | None,
|
|
192
|
+
block_on_block: bool,
|
|
193
|
+
block_on_review: bool,
|
|
194
|
+
raise_on_tier_gate: bool,
|
|
195
|
+
framework: str,
|
|
196
|
+
raise_on_error: bool,
|
|
197
|
+
) -> Callable:
|
|
198
|
+
from .client import AsyncAgentTrustClient
|
|
199
|
+
|
|
200
|
+
@functools.wraps(fn)
|
|
201
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
202
|
+
t0 = time.perf_counter()
|
|
203
|
+
result = await fn(*args, **kwargs)
|
|
204
|
+
latency_ms = (time.perf_counter() - t0) * 1000
|
|
205
|
+
|
|
206
|
+
user = kwargs.get(user_kwarg, "unknown")
|
|
207
|
+
inp = kwargs.get(input_kwarg, "")
|
|
208
|
+
output = result if isinstance(result, dict) else {"result": str(result)}
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
async with AsyncAgentTrustClient(
|
|
212
|
+
base_url=base_url,
|
|
213
|
+
api_key=api_key,
|
|
214
|
+
raise_on_tier_gate=raise_on_tier_gate,
|
|
215
|
+
) as client:
|
|
216
|
+
resp = await client.validate(
|
|
217
|
+
agent_id=agent_id,
|
|
218
|
+
user=str(user),
|
|
219
|
+
input=str(inp),
|
|
220
|
+
output=output,
|
|
221
|
+
framework=framework,
|
|
222
|
+
latency_ms=latency_ms,
|
|
223
|
+
)
|
|
224
|
+
_check_decision(resp, block_on_block, block_on_review)
|
|
225
|
+
except BlockedError:
|
|
226
|
+
raise
|
|
227
|
+
except Exception as exc:
|
|
228
|
+
if raise_on_error:
|
|
229
|
+
raise
|
|
230
|
+
logger.warning("[AgentTrust] Async validation failed (non-fatal): %s", exc)
|
|
231
|
+
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
return wrapper
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ---------------------------------------------------------------------------
|
|
238
|
+
# Helpers
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def _check_decision(resp: Any, block_on_block: bool, block_on_review: bool) -> None:
|
|
242
|
+
outcome = resp.decision.outcome
|
|
243
|
+
if block_on_block and outcome == "block":
|
|
244
|
+
raise BlockedError(outcome, resp.decision.reason, resp.envelope_id)
|
|
245
|
+
if block_on_review and outcome in ("escalate", "request_evidence"):
|
|
246
|
+
raise BlockedError(outcome, resp.decision.reason, resp.envelope_id)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _detect_framework() -> str:
|
|
250
|
+
"""Best-effort framework detection from installed packages."""
|
|
251
|
+
try:
|
|
252
|
+
import importlib
|
|
253
|
+
for name, label in [
|
|
254
|
+
("langgraph", "LangGraph"),
|
|
255
|
+
("crewai", "CrewAI"),
|
|
256
|
+
("langchain", "LangChain"),
|
|
257
|
+
("autogen", "AutoGen"),
|
|
258
|
+
("llama_index", "LlamaIndex"),
|
|
259
|
+
]:
|
|
260
|
+
if importlib.util.find_spec(name):
|
|
261
|
+
return label
|
|
262
|
+
except Exception:
|
|
263
|
+
pass
|
|
264
|
+
return "REST"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _resolve_base_url() -> str:
|
|
268
|
+
# Env var takes priority, then config file, then default
|
|
269
|
+
if SDK_CONFIG.gateway_url != "http://localhost:8000":
|
|
270
|
+
return SDK_CONFIG.gateway_url
|
|
271
|
+
cfg = read_config("project") or read_config("global")
|
|
272
|
+
return cfg.get("control_plane_url", SDK_CONFIG.gateway_url)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Keep old name as alias for backward compat
|
|
276
|
+
validate = harness
|