Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

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",
]