Initial commit
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"""Shared helpers for recoverable tool-output previews."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from opc.core.config import get_opc_home
|
||||
from opc.layer4_tools.execution_context import resolve_task_execution_context
|
||||
|
||||
|
||||
DEFAULT_TRUNCATION_MARKER = "truncated"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TextClip:
|
||||
text: str
|
||||
truncated: bool
|
||||
omitted_chars: int
|
||||
original_chars: int
|
||||
kept_chars: int
|
||||
|
||||
|
||||
def clip_text(
|
||||
value: Any,
|
||||
*,
|
||||
limit: int,
|
||||
marker: str = DEFAULT_TRUNCATION_MARKER,
|
||||
prefer_newline: bool = True,
|
||||
) -> TextClip:
|
||||
"""Return a marked preview of ``value`` within a character budget."""
|
||||
text = str(value or "")
|
||||
original_chars = len(text)
|
||||
limit = max(0, int(limit or 0))
|
||||
if original_chars <= limit:
|
||||
return TextClip(
|
||||
text=text,
|
||||
truncated=False,
|
||||
omitted_chars=0,
|
||||
original_chars=original_chars,
|
||||
kept_chars=original_chars,
|
||||
)
|
||||
if limit <= 0:
|
||||
kept = ""
|
||||
else:
|
||||
kept = text[:limit]
|
||||
if prefer_newline and "\n" in kept:
|
||||
floor = max(1, int(limit * 0.80))
|
||||
boundary = kept.rfind("\n", floor)
|
||||
if boundary > 0:
|
||||
kept = kept[:boundary]
|
||||
kept = kept.rstrip()
|
||||
omitted = original_chars - len(kept)
|
||||
marker_text = marker.strip("[]") or DEFAULT_TRUNCATION_MARKER
|
||||
suffix = f"[{marker_text}: {omitted} chars omitted]"
|
||||
rendered = f"{kept}\n{suffix}" if kept else suffix
|
||||
return TextClip(
|
||||
text=rendered,
|
||||
truncated=True,
|
||||
omitted_chars=omitted,
|
||||
original_chars=original_chars,
|
||||
kept_chars=len(kept),
|
||||
)
|
||||
|
||||
|
||||
def truncation_metadata(clip: TextClip, *, prefix: str = "") -> dict[str, Any]:
|
||||
"""Render stable metadata keys for a text clip."""
|
||||
return {
|
||||
f"{prefix}truncated": clip.truncated,
|
||||
f"{prefix}omitted_chars": clip.omitted_chars,
|
||||
f"{prefix}original_chars": clip.original_chars,
|
||||
}
|
||||
|
||||
|
||||
def _safe_segment(value: str, fallback: str) -> str:
|
||||
segment = re.sub(r"[^A-Za-z0-9_.-]+", "-", str(value or "").strip()).strip(".-")
|
||||
return segment[:80] or fallback
|
||||
|
||||
|
||||
def _tool_results_root(task: Any | None) -> Path:
|
||||
context = resolve_task_execution_context(task)
|
||||
comms_root = str(context.get("comms_root", "") or "").strip()
|
||||
if comms_root:
|
||||
return Path(comms_root).expanduser().resolve() / "tool-results"
|
||||
return get_opc_home() / "artifacts" / "tool-results"
|
||||
|
||||
|
||||
def persist_tool_result(
|
||||
content: Any,
|
||||
*,
|
||||
tool_name: str,
|
||||
task: Any | None = None,
|
||||
extension: str = "json",
|
||||
) -> dict[str, Any]:
|
||||
"""Persist full tool output and return path/size metadata."""
|
||||
if isinstance(content, str):
|
||||
text = content
|
||||
extension = extension or "txt"
|
||||
else:
|
||||
text = json.dumps(content, ensure_ascii=False, indent=2, default=str)
|
||||
extension = extension or "json"
|
||||
|
||||
task_id = str(getattr(task, "id", "") or "").strip()
|
||||
session_id = str(getattr(task, "session_id", "") or getattr(task, "parent_session_id", "") or "").strip()
|
||||
bucket = _safe_segment(task_id or session_id or "global", "global")
|
||||
root = _tool_results_root(task) / bucket
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
stamp = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
|
||||
digest = sha256(text.encode("utf-8", errors="replace")).hexdigest()[:12]
|
||||
suffix = extension.lstrip(".") or "txt"
|
||||
path = root / f"{_safe_segment(tool_name, 'tool')}-{stamp}-{digest}.{suffix}"
|
||||
path.write_text(text, encoding="utf-8")
|
||||
return {
|
||||
"full_output_path": str(path),
|
||||
"original_size_chars": len(text),
|
||||
}
|
||||
|
||||
|
||||
def _json_size(value: Any) -> int:
|
||||
return len(json.dumps(value, ensure_ascii=False, default=str))
|
||||
|
||||
|
||||
def budget_tool_output(
|
||||
output: dict[str, Any],
|
||||
*,
|
||||
tool_name: str,
|
||||
task: Any | None = None,
|
||||
max_chars: int,
|
||||
preview_chars: int | None = None,
|
||||
persist_large_results: bool = True,
|
||||
self_bounded_output: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Apply a recoverable registry-level budget to a tool output."""
|
||||
max_chars = max(1, int(max_chars or 1))
|
||||
try:
|
||||
serialized = json.dumps(output, ensure_ascii=False, default=str)
|
||||
except (TypeError, ValueError):
|
||||
return output
|
||||
if len(serialized) <= max_chars:
|
||||
return output
|
||||
|
||||
preview_limit = max(400, int(preview_chars or max_chars // 2))
|
||||
persisted: dict[str, Any] = {}
|
||||
if persist_large_results and not self_bounded_output:
|
||||
persisted = persist_tool_result(output, tool_name=tool_name, task=task, extension="json")
|
||||
|
||||
meta = {
|
||||
"truncated": True,
|
||||
"omitted_chars": max(0, len(serialized) - max_chars),
|
||||
"original_size_chars": len(serialized),
|
||||
**persisted,
|
||||
}
|
||||
result_val = output.get("result")
|
||||
|
||||
if isinstance(result_val, dict):
|
||||
preview_result = json.loads(json.dumps(result_val, ensure_ascii=False, default=str))
|
||||
for key in ("stdout", "stderr", "content", "rendered", "summary", "diff_preview"):
|
||||
value = preview_result.get(key)
|
||||
if isinstance(value, str):
|
||||
clip = clip_text(
|
||||
value,
|
||||
limit=preview_limit,
|
||||
marker=f"{tool_name} {key} truncated",
|
||||
)
|
||||
preview_result[key] = clip.text
|
||||
if clip.truncated:
|
||||
preview_result[f"{key}_truncated"] = True
|
||||
preview_result[f"{key}_omitted_chars"] = clip.omitted_chars
|
||||
preview_result.update({key: value for key, value in meta.items() if value not in ("", None)})
|
||||
preview_output = {**output, "result": preview_result, **meta}
|
||||
if _json_size(preview_output) <= max_chars:
|
||||
return preview_output
|
||||
|
||||
clip = clip_text(serialized, limit=preview_limit, marker=f"{tool_name} result truncated")
|
||||
return {
|
||||
"result": {
|
||||
"preview": clip.text,
|
||||
**{key: value for key, value in meta.items() if value not in ("", None)},
|
||||
},
|
||||
"success": output.get("success", True),
|
||||
**meta,
|
||||
}
|
||||
Reference in New Issue
Block a user