4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
88 lines
3.2 KiB
Python
88 lines
3.2 KiB
Python
"""Helpers for safe tool-call tracing in CLI and reports."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from typing import Any
|
|
|
|
_SENSITIVE_KEY_RE = re.compile(
|
|
r"(api[_-]?key|token|secret|password|credential|authorization|auth[_-]?header)",
|
|
re.IGNORECASE,
|
|
)
|
|
_RUNTIME_KEY_RE = re.compile(r"(^_|backend$|_backend$)", re.IGNORECASE)
|
|
|
|
_REDACTED_PLACEHOLDER = "[redacted]"
|
|
_RUNTIME_OBJECT_PLACEHOLDER = "[runtime object]"
|
|
|
|
#: Default bound for pretty-printed JSON previews in the terminal.
|
|
DEFAULT_JSON_PREVIEW_MAX_CHARS = 4000
|
|
#: Default bound for tool-result previews inside report lines.
|
|
DEFAULT_TOOL_TRACE_OUTPUT_MAX_CHARS = 1200
|
|
#: Bound for tool-argument previews (kept shorter than output).
|
|
_TOOL_TRACE_ARGS_MAX_CHARS = 500
|
|
#: Indent width for ``json.dumps`` previews.
|
|
_JSON_PREVIEW_INDENT = 2
|
|
#: Marker appended when a preview is truncated to ``max_chars``.
|
|
_JSON_TRUNCATION_SUFFIX = "\n... [truncated]"
|
|
#: ``loop_iteration`` value meaning "seed / pre-loop" tool evidence.
|
|
_SEED_LOOP_ITERATION = -1
|
|
|
|
|
|
def redact_sensitive(value: Any) -> Any:
|
|
"""Return a copy of ``value`` with credentials and runtime objects hidden."""
|
|
if isinstance(value, dict):
|
|
redacted: dict[str, Any] = {}
|
|
for key, item in value.items():
|
|
key_str = str(key)
|
|
if _SENSITIVE_KEY_RE.search(key_str):
|
|
redacted[key_str] = _REDACTED_PLACEHOLDER
|
|
elif _RUNTIME_KEY_RE.search(key_str):
|
|
redacted[key_str] = _RUNTIME_OBJECT_PLACEHOLDER
|
|
else:
|
|
redacted[key_str] = redact_sensitive(item)
|
|
return redacted
|
|
if isinstance(value, list):
|
|
return [redact_sensitive(item) for item in value]
|
|
if isinstance(value, tuple):
|
|
return [redact_sensitive(item) for item in value]
|
|
return value
|
|
|
|
|
|
def format_json_preview(value: Any, *, max_chars: int = DEFAULT_JSON_PREVIEW_MAX_CHARS) -> str:
|
|
"""Pretty-print a redacted JSON-ish value, bounded for terminal output."""
|
|
redacted = redact_sensitive(value)
|
|
try:
|
|
text = json.dumps(redacted, indent=_JSON_PREVIEW_INDENT, default=str)
|
|
except TypeError:
|
|
text = str(redacted)
|
|
if len(text) <= max_chars:
|
|
return text
|
|
keep = max(0, max_chars - len(_JSON_TRUNCATION_SUFFIX))
|
|
return text[:keep].rstrip() + _JSON_TRUNCATION_SUFFIX
|
|
|
|
|
|
def format_tool_trace_entry(
|
|
entry: dict[str, Any], *, max_output_chars: int = DEFAULT_TOOL_TRACE_OUTPUT_MAX_CHARS
|
|
) -> str:
|
|
"""Format one evidence entry as a compact report line."""
|
|
tool_name = str(entry.get("tool_name") or entry.get("key") or "tool")
|
|
loop = entry.get("loop_iteration")
|
|
loop_label = "seed" if loop == _SEED_LOOP_ITERATION else f"iteration {loop}"
|
|
args = format_json_preview(entry.get("tool_args") or {}, max_chars=_TOOL_TRACE_ARGS_MAX_CHARS)
|
|
output = format_json_preview(entry.get("data"), max_chars=max_output_chars)
|
|
return f"- `{tool_name}` ({loop_label})\n input: `{_one_line(args)}`\n output: `{_one_line(output)}`"
|
|
|
|
|
|
def _one_line(value: str) -> str:
|
|
return " ".join(value.split())
|
|
|
|
|
|
__all__ = [
|
|
"DEFAULT_JSON_PREVIEW_MAX_CHARS",
|
|
"DEFAULT_TOOL_TRACE_OUTPUT_MAX_CHARS",
|
|
"format_json_preview",
|
|
"format_tool_trace_entry",
|
|
"redact_sensitive",
|
|
]
|