Skip to content

Commit 533028d

Browse files
committed
feat(tool_safety): add safety_wrapper decorator and Skill runner wrapper
1 parent dbb0c00 commit 533028d

2 files changed

Lines changed: 20 additions & 18 deletions

File tree

examples/tool_safety/safety/wrapper.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def SafeCodeExecutor(inner, policy: PolicyConfig, *, audit_path: Optional[str] =
5050
docker) only happens when this wrapper is actually used.
5151
"""
5252
from trpc_agent_sdk.code_executors import BaseCodeExecutor
53-
from trpc_agent_sdk.code_executors import CodeExecutionResult as CER
53+
from trpc_agent_sdk.code_executors import create_code_execution_result
5454
from .audit import AuditLogger
5555

5656
scanner = SafetyScanner(policy=policy)
@@ -62,7 +62,9 @@ async def execute_code(self, invocation_context, input_data):
6262
report = scanner.scan(ScanInput(script=code, language="python", tool_name="code_executor"))
6363
audit.log(report, intercepted=report.blocked)
6464
if report.blocked:
65-
return CER(stderr=f"TOOL_SAFETY_DENY: {report.rule_ids}", exit_code=126, stdout="")
65+
return create_code_execution_result(
66+
stderr=f"TOOL_SAFETY_DENY: {report.rule_ids}"
67+
)
6668
return await inner.execute_code(invocation_context, input_data)
6769

6870
return _SafeCodeExecutor()

tests/tool_safety/test_wrapper.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ def test_wrap_tool_allows_safe_bash(tmp_path: Path):
122122

123123

124124
def _try_import_code_executors():
125-
"""Return (CodeExecutionInput, CodeExecutionResult) or (None, None)."""
125+
"""Return (CodeExecutionInput, create_code_execution_result) or (None, None)."""
126126
try:
127127
from trpc_agent_sdk.code_executors import CodeExecutionInput
128-
from trpc_agent_sdk.code_executors import CodeExecutionResult as CER
129-
return CodeExecutionInput, CER
130-
except Exception: # pylint: disable=broad-except
128+
from trpc_agent_sdk.code_executors import create_code_execution_result
129+
return CodeExecutionInput, create_code_execution_result
130+
except Exception: # pylint: disable=broad-expect
131131
return None, None
132132

133133

@@ -139,22 +139,22 @@ class _FakeInnerExecutor:
139139
logic. ``calls`` records whether delegation happened.
140140
"""
141141

142-
def __init__(self, cer_cls: Any) -> None:
143-
self._cer_cls = cer_cls
142+
def __init__(self, create_fn: Any) -> None:
143+
self._create_fn = create_fn
144144
self.calls: list[Any] = []
145145

146146
async def execute_code(self, invocation_context, input_data):
147147
self.calls.append(input_data)
148-
return self._cer_cls(stdout="ok", stderr="", exit_code=0)
148+
return self._create_fn(stdout="ok")
149149

150150

151151
def test_safe_code_executor_blocks_dangerous_python(tmp_path: Path):
152152
"""SafeCodeExecutor must block `os.system('rm -rf /')` before delegation."""
153-
CodeExecutionInput, CER = _try_import_code_executors()
153+
CodeExecutionInput, create_fn = _try_import_code_executors()
154154
if CodeExecutionInput is None:
155155
pytest.skip("trpc_agent_sdk.code_executors not importable (docker optional dep missing)")
156156

157-
inner = _FakeInnerExecutor(CER)
157+
inner = _FakeInnerExecutor(create_fn)
158158
safe = SafeCodeExecutor(inner, _policy(tmp_path), audit_path=str(tmp_path / "audit.jsonl"))
159159

160160
code = "import os\nos.system('rm -rf /')"
@@ -163,9 +163,9 @@ def test_safe_code_executor_blocks_dangerous_python(tmp_path: Path):
163163

164164
# Blocked: inner must NOT have been called.
165165
assert inner.calls == []
166-
# Result carries the deny marker.
167-
assert "TOOL_SAFETY_DENY" in result.stderr
168-
assert result.exit_code == 126
166+
# create_code_execution_result packs stderr into `output` with FAILED outcome.
167+
assert "TOOL_SAFETY_DENY" in result.output
168+
assert result.outcome.name == "OUTCOME_FAILED"
169169

170170
# Audit record must be written.
171171
audit_path = tmp_path / "audit.jsonl"
@@ -177,11 +177,11 @@ def test_safe_code_executor_blocks_dangerous_python(tmp_path: Path):
177177

178178
def test_safe_code_executor_allows_safe_python(tmp_path: Path):
179179
"""SafeCodeExecutor must delegate safe code to the inner executor."""
180-
CodeExecutionInput, CER = _try_import_code_executors()
180+
CodeExecutionInput, create_fn = _try_import_code_executors()
181181
if CodeExecutionInput is None:
182182
pytest.skip("trpc_agent_sdk.code_executors not importable (docker optional dep missing)")
183183

184-
inner = _FakeInnerExecutor(CER)
184+
inner = _FakeInnerExecutor(create_fn)
185185
safe = SafeCodeExecutor(inner, _policy(tmp_path))
186186

187187
code = "print('hello world')"
@@ -190,8 +190,8 @@ def test_safe_code_executor_allows_safe_python(tmp_path: Path):
190190

191191
# Delegated: inner must have been called exactly once.
192192
assert len(inner.calls) == 1
193-
assert result.stdout == "ok"
194-
assert result.exit_code == 0
193+
assert "ok" in result.output
194+
assert result.outcome.name == "OUTCOME_OK"
195195

196196

197197
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)