"""Bound model-facing observations while keeping full, task-scoped evidence.""" from pathlib import Path import re from typing import Any from artemis.config.constants import PLUGIN_PROMPT_TEXT_CHARS def result_path(engine: Any, call_id: str) -> Path: if not re.fullmatch(r"[a-f0-9]{32}", call_id): raise ValueError("插件结果编号无效") root = Path(engine.base_dir).resolve() path = root / "evidence" / "plugin-results" / f"{call_id}.txt" if not path.resolve().is_relative_to(root): raise ValueError("插件结果路径超出任务目录") return path def save_result_text(engine: Any, call_id: str, text: str) -> None: path = result_path(engine, call_id) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text, encoding="utf-8") def bounded_result_text( text: str, *, call_id: str = "", limit: int = PLUGIN_PROMPT_TEXT_CHARS ) -> str: if len(text) <= limit: return text reference = ( f'完整结果可用 plugin_read_result(call_id="{call_id}", offset=0) 分页读取。' if call_id else "完整结果保存在本步骤的 trace;需要目标控件时可缩小观察范围重新读取。" ) notice = f"\n[观察文本已节选,原文 {len(text)} 字符。{reference}]\n" available = max(0, limit - len(notice)) if not available: return notice[:limit] head = available * 3 // 4 return text[:head] + notice + text[-(available - head) :] def read_result_text(engine: Any, call_id: str, offset: int = 0) -> str: if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0: raise ValueError("offset 必须为非负整数") text = result_path(engine, call_id).read_text(encoding="utf-8") if offset > len(text): raise ValueError(f"offset 超出结果长度 {len(text)}") end = min(len(text), offset + PLUGIN_PROMPT_TEXT_CHARS) return ( f"插件结果 {call_id},字符 {offset}:{end}/{len(text)};" + (f"下一页 offset={end}" if end < len(text) else "已到结尾") + "\n" + text[offset:end] )