agent-guard-python 0.2.1__cp310-abi3-win_amd64.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.
@@ -0,0 +1,43 @@
1
+ from ._agent_guard import (
2
+ Guard,
3
+ Decision,
4
+ ExecuteResult,
5
+ HandoffResult,
6
+ PolicyVerification,
7
+ RuntimeDecision,
8
+ RuntimeOutcome,
9
+ SandboxOutput,
10
+ GuardError,
11
+ __version__,
12
+ init_tracing,
13
+ )
14
+ from .adapters import (
15
+ AgentGuardAdapterError,
16
+ AgentGuardAskRequiredError,
17
+ AgentGuardDeniedError,
18
+ AgentGuardExecutionError,
19
+ AgentGuardSecurityError,
20
+ )
21
+ from .langchain import wrap_langchain_tool
22
+ from .openai import wrap_openai_tool
23
+
24
+ __all__ = [
25
+ "Guard",
26
+ "Decision",
27
+ "ExecuteResult",
28
+ "HandoffResult",
29
+ "PolicyVerification",
30
+ "RuntimeDecision",
31
+ "RuntimeOutcome",
32
+ "SandboxOutput",
33
+ "GuardError",
34
+ "__version__",
35
+ "init_tracing",
36
+ "AgentGuardAdapterError",
37
+ "AgentGuardDeniedError",
38
+ "AgentGuardAskRequiredError",
39
+ "AgentGuardExecutionError",
40
+ "wrap_langchain_tool",
41
+ "wrap_openai_tool",
42
+ "AgentGuardSecurityError",
43
+ ]
Binary file
@@ -0,0 +1,410 @@
1
+ import asyncio
2
+ import json
3
+ import time
4
+ from typing import Any, Callable, Optional
5
+
6
+
7
+ DEFAULT_MODE = "enforce"
8
+ DEFAULT_TRUST_LEVEL = "untrusted"
9
+ MAX_PAYLOAD_BYTES = 1024 * 1024
10
+ SHELL_TOOL_NAMES = {"bash", "shell", "terminal", "sh", "zsh", "cmd", "powershell", "pwsh"}
11
+
12
+
13
+ class AgentGuardAdapterError(Exception):
14
+ def __init__(self, message: str, *, decision: Any = None, status: Optional[str] = None,
15
+ policy_version: Optional[str] = None,
16
+ policy_verification_status: Optional[str] = None,
17
+ policy_verification_error: Optional[str] = None,
18
+ sandbox_type: Optional[str] = None, receipt: Optional[str] = None,
19
+ code: Optional[str] = None, matched_rule: Optional[str] = None,
20
+ ask_prompt: Optional[str] = None,
21
+ cause: Optional[BaseException] = None):
22
+ super().__init__(message)
23
+ self.decision = decision
24
+ self.status = status
25
+ self.policy_version = policy_version
26
+ self.policy_verification_status = policy_verification_status
27
+ self.policy_verification_error = policy_verification_error
28
+ self.sandbox_type = sandbox_type
29
+ self.receipt = receipt
30
+ self.code = code
31
+ self.matched_rule = matched_rule
32
+ self.ask_prompt = ask_prompt
33
+ self.cause = cause
34
+
35
+
36
+ class AgentGuardSecurityError(AgentGuardAdapterError):
37
+ pass
38
+
39
+
40
+ class AgentGuardDeniedError(AgentGuardSecurityError):
41
+ pass
42
+
43
+
44
+ class AgentGuardAskRequiredError(AgentGuardSecurityError):
45
+ pass
46
+
47
+
48
+ class AgentGuardExecutionError(AgentGuardAdapterError):
49
+ pass
50
+
51
+
52
+ def validate_mode(mode: str) -> str:
53
+ resolved = mode or DEFAULT_MODE
54
+ if resolved not in {"check", "enforce", "auto"}:
55
+ raise AgentGuardExecutionError(f"Unsupported adapter mode {resolved!r}", status="error")
56
+ return resolved
57
+
58
+
59
+ def is_shell_tool_name(name: str) -> bool:
60
+ return str(name or "").lower() in SHELL_TOOL_NAMES
61
+
62
+
63
+ def resolve_mode(tool_name: str, mode: str) -> str:
64
+ """
65
+ Resolve the requested adapter mode to one of "enforce", "check", or "run".
66
+
67
+ - "enforce" → always go through ``Guard.execute`` (sandboxed run).
68
+ - "check" → always go through ``Guard.check`` (policy-only, host runs original).
69
+ - "auto" → for shell tools, behave like "enforce"; for non-shell tools,
70
+ prefer ``Guard.run`` (the unified runtime API) when the binding
71
+ exposes it, falling back to "check" for older bindings.
72
+
73
+ The returned token "run" is a private contract between this helper and the
74
+ adapter dispatch path; it is only produced when the host's ``Guard`` actually
75
+ advertises a ``run`` method, so adapters can safely call ``guard.run`` after
76
+ seeing it.
77
+ """
78
+ resolved = validate_mode(mode)
79
+ if resolved != "auto":
80
+ return resolved
81
+ if is_shell_tool_name(tool_name):
82
+ return "enforce"
83
+ # Non-shell auto: use the runtime API when available, else fall back to check.
84
+ return "run"
85
+
86
+
87
+ def has_runtime_api(guard: Any) -> bool:
88
+ """True iff this Guard binding exposes the unified runtime API used by mode=auto."""
89
+ return callable(getattr(guard, "run", None))
90
+
91
+
92
+ def prepare_payload(tool_name: str, raw_input: Any) -> str:
93
+ shell_tool = is_shell_tool_name(tool_name)
94
+ if shell_tool:
95
+ if isinstance(raw_input, str):
96
+ payload = {"command": raw_input}
97
+ elif isinstance(raw_input, dict) and "command" in raw_input:
98
+ payload = raw_input
99
+ else:
100
+ payload = {"command": str(raw_input)}
101
+ elif isinstance(raw_input, (str, bytes, int, float, bool)):
102
+ payload = {"input": raw_input}
103
+ else:
104
+ payload = raw_input
105
+
106
+ payload_json = json.dumps(payload)
107
+ if len(payload_json.encode("utf-8")) > MAX_PAYLOAD_BYTES:
108
+ raise ValueError("Tool payload too large (max 1MB)")
109
+ return payload_json
110
+
111
+
112
+ # ── Unified error-attribute extraction ───────────────────────────────────────
113
+ #
114
+ # All four error sites — Decision objects from check(), decisions embedded in
115
+ # ExecuteResult, RuntimeOutcome objects from run(), and synthetic policy-
116
+ # verification decisions — now flow through a single attribute extractor so
117
+ # the surfaced AgentGuardSecurityError / AgentGuardExecutionError instances
118
+ # carry identical fields regardless of which Guard API produced them.
119
+
120
+
121
+ def _decision_to_error_attrs(decision: Any) -> dict:
122
+ """Pull the canonical error-shaping attributes off a Decision-like object.
123
+
124
+ Works for: Decision (from Guard.check), decisions embedded in ExecuteResult,
125
+ RuntimeOutcome variants from Guard.run, and the synthetic decision built in
126
+ ``ensure_verified_policy``. Any of these may be missing fields; we always
127
+ produce the full attribute set with ``None`` for absent values.
128
+
129
+ For ``RuntimeOutcome`` shapes, ``code`` / ``matched_rule`` / ``ask_prompt``
130
+ live on the embedded ``decision`` child rather than the outcome itself, so
131
+ we look there first and fall back to the outer object for legacy shapes.
132
+ """
133
+ inner = getattr(decision, "decision", None)
134
+ code_source = inner if inner is not None else decision
135
+ return {
136
+ "policy_version": getattr(decision, "policy_version", None),
137
+ "policy_verification_status": getattr(decision, "policy_verification_status", None),
138
+ "policy_verification_error": getattr(decision, "policy_verification_error", None),
139
+ "code": getattr(code_source, "code", None),
140
+ "matched_rule": getattr(code_source, "matched_rule", None),
141
+ "ask_prompt": getattr(code_source, "ask_prompt", None),
142
+ }
143
+
144
+
145
+ def build_security_error(decision: Any, *, fallback_message: Optional[str] = None) -> AgentGuardSecurityError:
146
+ outcome = getattr(decision, "outcome", "deny")
147
+ is_ask = outcome in ("ask_user", "ask_for_approval")
148
+ status = "ask_required" if is_ask else "denied"
149
+ message = (
150
+ getattr(decision, "ask_prompt", None)
151
+ or getattr(decision, "message", None)
152
+ or fallback_message
153
+ or ("agent-guard requires user approval before tool execution" if is_ask
154
+ else "agent-guard denied tool execution")
155
+ )
156
+ error_type = AgentGuardAskRequiredError if is_ask else AgentGuardDeniedError
157
+ return error_type(
158
+ message,
159
+ decision=decision,
160
+ status=status,
161
+ **_decision_to_error_attrs(decision),
162
+ )
163
+
164
+
165
+ def ensure_verified_policy(decision: Any) -> None:
166
+ if getattr(decision, "policy_verification_status", None) != "invalid":
167
+ return
168
+
169
+ synthetic_decision = type("PolicyDecision", (), {
170
+ "outcome": "deny",
171
+ "message": getattr(decision, "policy_verification_error", None)
172
+ or "agent-guard refuses to continue with an invalid policy signature",
173
+ "code": "PolicyVerificationFailed",
174
+ "matched_rule": None,
175
+ "ask_prompt": None,
176
+ "policy_version": getattr(decision, "policy_version", None),
177
+ "policy_verification_status": getattr(decision, "policy_verification_status", None),
178
+ "policy_verification_error": getattr(decision, "policy_verification_error", None),
179
+ })()
180
+ raise build_security_error(synthetic_decision)
181
+
182
+
183
+ def handle_execute_result(result: Any, *, result_mapper: Optional[Callable[[Any, Any], Any]], original_input: Any) -> Any:
184
+ if result.status == "executed":
185
+ if callable(result_mapper):
186
+ return result_mapper(result, original_input)
187
+ return result
188
+
189
+ if result.decision is not None:
190
+ raise build_security_error(result.decision)
191
+
192
+ raise AgentGuardExecutionError(
193
+ "agent-guard returned an unknown execution status",
194
+ status=result.status,
195
+ policy_version=getattr(result, "policy_version", None),
196
+ policy_verification_status=getattr(result, "policy_verification_status", None),
197
+ policy_verification_error=getattr(result, "policy_verification_error", None),
198
+ sandbox_type=getattr(result, "sandbox_type", None),
199
+ receipt=getattr(result, "receipt", None),
200
+ )
201
+
202
+
203
+ # ── Runtime-API dispatch (Guard.run) ─────────────────────────────────────────
204
+
205
+
206
+ def _is_handoff_outcome(outcome: Any) -> bool:
207
+ name = getattr(outcome, "outcome", None) or getattr(outcome, "status", None)
208
+ return name == "handoff"
209
+
210
+
211
+ def _is_executed_outcome(outcome: Any) -> bool:
212
+ name = getattr(outcome, "outcome", None) or getattr(outcome, "status", None)
213
+ return name in ("executed", "execute")
214
+
215
+
216
+ def _is_denied_outcome(outcome: Any) -> bool:
217
+ name = getattr(outcome, "outcome", None) or getattr(outcome, "status", None)
218
+ return name in ("denied", "deny")
219
+
220
+
221
+ def _is_ask_outcome(outcome: Any) -> bool:
222
+ name = getattr(outcome, "outcome", None) or getattr(outcome, "status", None)
223
+ return name in ("ask_for_approval", "ask_user", "ask_required")
224
+
225
+
226
+ def _build_handoff_result(guard: Any, *, exit_code: int, duration_ms: int,
227
+ stderr: Optional[str] = None) -> Any:
228
+ """Construct a ``HandoffResult`` value the binding accepts.
229
+
230
+ Prefer the binding's exported ``HandoffResult`` class when present; fall
231
+ back to a duck-typed object so tests with mock guards remain decoupled
232
+ from the PyO3 layout.
233
+ """
234
+ try:
235
+ from . import HandoffResult as _HandoffResult # type: ignore
236
+ return _HandoffResult(exit_code=exit_code, duration_ms=duration_ms, stderr=stderr)
237
+ except Exception:
238
+ return type("HandoffResult", (), {
239
+ "exit_code": exit_code,
240
+ "duration_ms": duration_ms,
241
+ "stderr": stderr,
242
+ })()
243
+
244
+
245
+ def dispatch_via_run(
246
+ guard: Any,
247
+ *,
248
+ tool: str,
249
+ payload: str,
250
+ guard_options: dict,
251
+ handler: Callable[..., Any],
252
+ handler_args: tuple = (),
253
+ handler_kwargs: Optional[dict] = None,
254
+ ) -> Any:
255
+ """
256
+ Drive an ``auto``-mode (non-shell) tool through the unified ``Guard.run``
257
+ API and close the audit loop on Handoff.
258
+
259
+ Behaviour by ``RuntimeOutcome`` variant:
260
+
261
+ - ``Executed`` — return the sandbox output mapped through the standard
262
+ execute-result path. (Currently this only happens for shell-shaped
263
+ tools, but the branch is here for robustness.)
264
+ - ``Handoff`` — invoke ``handler`` to actually perform the action, time
265
+ it, and report the outcome back via ``Guard.report_handoff_result``
266
+ (exit_code 0 on clean return, 1 if the handler raised). The host
267
+ exception, if any, is re-raised AFTER the audit record is emitted so
268
+ the audit loop closes either way.
269
+ - ``Denied`` / ``AskForApproval`` — raise the appropriate
270
+ ``AgentGuardSecurityError`` subclass via ``build_security_error``.
271
+ """
272
+ handler_kwargs = handler_kwargs or {}
273
+ try:
274
+ outcome = guard.run(tool=tool, payload=payload, **guard_options)
275
+ except Exception as exc:
276
+ raise AgentGuardExecutionError(
277
+ f"agent-guard run failed: {exc}",
278
+ status="error",
279
+ cause=exc,
280
+ ) from exc
281
+
282
+ if _is_handoff_outcome(outcome):
283
+ request_id = getattr(outcome, "request_id", "")
284
+ start = time.monotonic()
285
+ try:
286
+ result = handler(*handler_args, **handler_kwargs)
287
+ except BaseException as host_exc: # noqa: BLE001 — propagate after audit
288
+ duration_ms = int((time.monotonic() - start) * 1000)
289
+ handoff_result = _build_handoff_result(
290
+ guard,
291
+ exit_code=1,
292
+ duration_ms=duration_ms,
293
+ stderr=str(host_exc),
294
+ )
295
+ try:
296
+ guard.report_handoff_result(request_id, handoff_result)
297
+ except Exception:
298
+ # Reporting must never mask the host failure; swallow audit
299
+ # errors and let the original exception propagate.
300
+ pass
301
+ raise
302
+ duration_ms = int((time.monotonic() - start) * 1000)
303
+ handoff_result = _build_handoff_result(
304
+ guard,
305
+ exit_code=0,
306
+ duration_ms=duration_ms,
307
+ stderr=None,
308
+ )
309
+ try:
310
+ guard.report_handoff_result(request_id, handoff_result)
311
+ except Exception:
312
+ # Same rationale as the failure branch: audit reporting must not
313
+ # silently corrupt a successful host execution.
314
+ pass
315
+ return result
316
+
317
+ if _is_executed_outcome(outcome):
318
+ # Outcome carries an embedded sandbox output; return it raw — non-shell
319
+ # auto callers don't supply a result_mapper here, so the host receives
320
+ # the runtime outcome and can introspect output.stdout itself.
321
+ return outcome
322
+
323
+ if _is_denied_outcome(outcome) or _is_ask_outcome(outcome):
324
+ raise build_security_error(outcome)
325
+
326
+ raise AgentGuardExecutionError(
327
+ "agent-guard run returned an unknown outcome",
328
+ status=getattr(outcome, "outcome", None) or getattr(outcome, "status", "unknown"),
329
+ )
330
+
331
+
332
+ async def run_check_async(guard: Any, *, tool: str, payload: str, guard_options: dict[str, Any]) -> Any:
333
+ return await asyncio.to_thread(guard.check, tool=tool, payload=payload, **guard_options)
334
+
335
+
336
+ async def run_execute_async(guard: Any, *, tool: str, payload: str, guard_options: dict[str, Any]) -> Any:
337
+ return await asyncio.to_thread(guard.execute, tool=tool, payload=payload, **guard_options)
338
+
339
+
340
+ async def dispatch_via_run_async(
341
+ guard: Any,
342
+ *,
343
+ tool: str,
344
+ payload: str,
345
+ guard_options: dict,
346
+ handler: Callable[..., Any],
347
+ handler_args: tuple = (),
348
+ handler_kwargs: Optional[dict] = None,
349
+ async_handler: Optional[Callable[..., Any]] = None,
350
+ ) -> Any:
351
+ """Async variant of :func:`dispatch_via_run`. ``async_handler``, when set,
352
+ is awaited on the Handoff path; otherwise ``handler`` is invoked on a
353
+ worker thread."""
354
+ handler_kwargs = handler_kwargs or {}
355
+
356
+ try:
357
+ outcome = await asyncio.to_thread(
358
+ guard.run, tool=tool, payload=payload, **guard_options
359
+ )
360
+ except Exception as exc:
361
+ raise AgentGuardExecutionError(
362
+ f"agent-guard run failed: {exc}",
363
+ status="error",
364
+ cause=exc,
365
+ ) from exc
366
+
367
+ if _is_handoff_outcome(outcome):
368
+ request_id = getattr(outcome, "request_id", "")
369
+ start = time.monotonic()
370
+ try:
371
+ if async_handler is not None:
372
+ result = await async_handler(*handler_args, **handler_kwargs)
373
+ else:
374
+ result = await asyncio.to_thread(handler, *handler_args, **handler_kwargs)
375
+ except BaseException as host_exc: # noqa: BLE001
376
+ duration_ms = int((time.monotonic() - start) * 1000)
377
+ handoff_result = _build_handoff_result(
378
+ guard,
379
+ exit_code=1,
380
+ duration_ms=duration_ms,
381
+ stderr=str(host_exc),
382
+ )
383
+ try:
384
+ guard.report_handoff_result(request_id, handoff_result)
385
+ except Exception:
386
+ pass
387
+ raise
388
+ duration_ms = int((time.monotonic() - start) * 1000)
389
+ handoff_result = _build_handoff_result(
390
+ guard,
391
+ exit_code=0,
392
+ duration_ms=duration_ms,
393
+ stderr=None,
394
+ )
395
+ try:
396
+ guard.report_handoff_result(request_id, handoff_result)
397
+ except Exception:
398
+ pass
399
+ return result
400
+
401
+ if _is_executed_outcome(outcome):
402
+ return outcome
403
+
404
+ if _is_denied_outcome(outcome) or _is_ask_outcome(outcome):
405
+ raise build_security_error(outcome)
406
+
407
+ raise AgentGuardExecutionError(
408
+ "agent-guard run returned an unknown outcome",
409
+ status=getattr(outcome, "outcome", None) or getattr(outcome, "status", "unknown"),
410
+ )
@@ -0,0 +1,160 @@
1
+ import asyncio
2
+ from typing import Any, Optional
3
+ from ._agent_guard import Guard
4
+ from .adapters import (
5
+ AgentGuardSecurityError,
6
+ build_security_error,
7
+ dispatch_via_run,
8
+ dispatch_via_run_async,
9
+ ensure_verified_policy,
10
+ handle_execute_result,
11
+ has_runtime_api,
12
+ prepare_payload,
13
+ resolve_mode,
14
+ run_check_async,
15
+ run_execute_async,
16
+ )
17
+
18
+ def wrap_langchain_tool(
19
+ guard: Guard,
20
+ tool: Any,
21
+ agent_id: Optional[str] = None,
22
+ actor: Optional[str] = None,
23
+ trust_level: str = "untrusted",
24
+ mode: str = "auto"
25
+ ) -> Any:
26
+ """
27
+ Wraps a LangChain tool with agent-guard security enforcement.
28
+
29
+ Args:
30
+ guard: The initialized agent-guard.Guard instance.
31
+ tool: A LangChain tool instance (BaseTool).
32
+ agent_id: Optional ID for the agent.
33
+ actor: Optional ID for the human actor.
34
+ trust_level: Trust level for the execution ("untrusted", "trusted", "admin").
35
+ mode: One of:
36
+
37
+ - ``"enforce"`` — always sandbox the call via ``Guard.execute``.
38
+ - ``"check"`` — always go through ``Guard.check`` (policy-only;
39
+ the original tool runs in-process when allowed). Fail-closed on
40
+ invalid policy signatures.
41
+ - ``"auto"`` (default) — for shell-like tools, behave like
42
+ ``enforce``. For non-shell tools, dispatch through the unified
43
+ ``Guard.run`` runtime API when the binding exposes it. The
44
+ ``RuntimeOutcome::Handoff`` variant means the host runs the
45
+ original tool itself and the adapter then closes the audit loop
46
+ via ``Guard.report_handoff_result``. When the binding does not
47
+ expose ``run`` (older builds), ``auto`` for non-shell tools
48
+ degrades to ``check`` semantics.
49
+
50
+ Returns:
51
+ The same tool instance with guarded methods.
52
+ """
53
+ if not hasattr(tool, "_run"):
54
+ raise ValueError("Provided object does not look like a LangChain BaseTool (missing _run).")
55
+
56
+ original_run = tool._run
57
+ original_arun = getattr(tool, "_arun", None)
58
+ resolved_mode = resolve_mode(tool.name, mode)
59
+
60
+ # Defensive fallback: if the binding doesn't expose Guard.run, downgrade
61
+ # the auto/non-shell path to "check" so this adapter still works against
62
+ # older PyO3 bindings.
63
+ if resolved_mode == "run" and not has_runtime_api(guard):
64
+ resolved_mode = "check"
65
+
66
+ def _raw_input(*args, **kwargs) -> Any:
67
+ if args and len(args) == 1 and not kwargs:
68
+ return args[0]
69
+ if kwargs and not args:
70
+ return kwargs
71
+ return {"args": args, "kwargs": kwargs}
72
+
73
+ guard_options = {
74
+ "agent_id": agent_id,
75
+ "actor": actor,
76
+ "trust_level": trust_level,
77
+ }
78
+
79
+ def guarded_run(*args, **kwargs) -> Any:
80
+ raw_input = _raw_input(*args, **kwargs)
81
+ payload_str = prepare_payload(tool.name, raw_input)
82
+
83
+ if resolved_mode == "enforce":
84
+ result = guard.execute(tool=tool.name, payload=payload_str, **guard_options)
85
+ return handle_execute_result(
86
+ result,
87
+ result_mapper=lambda outcome, _original: outcome.output.stdout if outcome.output else "",
88
+ original_input=raw_input,
89
+ )
90
+
91
+ if resolved_mode == "run":
92
+ return dispatch_via_run(
93
+ guard,
94
+ tool=tool.name,
95
+ payload=payload_str,
96
+ guard_options=guard_options,
97
+ handler=original_run,
98
+ handler_args=args,
99
+ handler_kwargs=kwargs,
100
+ )
101
+
102
+ decision = guard.check(tool=tool.name, payload=payload_str, **guard_options)
103
+ if decision.outcome != "allow":
104
+ raise build_security_error(decision)
105
+ ensure_verified_policy(decision)
106
+ return original_run(*args, **kwargs)
107
+
108
+ async def guarded_arun(*args, **kwargs) -> Any:
109
+ raw_input = _raw_input(*args, **kwargs)
110
+ payload_str = prepare_payload(tool.name, raw_input)
111
+
112
+ if resolved_mode == "enforce":
113
+ result = await run_execute_async(
114
+ guard,
115
+ tool=tool.name,
116
+ payload=payload_str,
117
+ guard_options=guard_options,
118
+ )
119
+ return handle_execute_result(
120
+ result,
121
+ result_mapper=lambda outcome, _original: outcome.output.stdout if outcome.output else "",
122
+ original_input=raw_input,
123
+ )
124
+
125
+ if resolved_mode == "run":
126
+ return await dispatch_via_run_async(
127
+ guard,
128
+ tool=tool.name,
129
+ payload=payload_str,
130
+ guard_options=guard_options,
131
+ handler=original_run,
132
+ handler_args=args,
133
+ handler_kwargs=kwargs,
134
+ async_handler=original_arun,
135
+ )
136
+
137
+ decision = await run_check_async(
138
+ guard,
139
+ tool=tool.name,
140
+ payload=payload_str,
141
+ guard_options=guard_options,
142
+ )
143
+ if decision.outcome != "allow":
144
+ raise build_security_error(decision)
145
+ ensure_verified_policy(decision)
146
+ if original_arun:
147
+ return await original_arun(*args, **kwargs)
148
+ return await asyncio.to_thread(original_run, *args, **kwargs)
149
+
150
+ # Patch the tool internals.
151
+ try:
152
+ tool.__dict__["_run"] = guarded_run
153
+ if original_arun or hasattr(tool, "_arun"):
154
+ tool.__dict__["_arun"] = guarded_arun
155
+ except (AttributeError, TypeError):
156
+ tool._run = guarded_run
157
+ if original_arun or hasattr(tool, "_arun"):
158
+ tool._arun = guarded_arun
159
+
160
+ return tool