from __future__ import annotations import json import os from typing import Any SENSITIVE_KEYS = ("api_key", "apikey", "authorization", "token", "cookie", "secret", "password", "x-api-key") def debug_raw_enabled() -> bool: return os.environ.get("MEMORY_GATEWAY_PLUGIN_DEBUG_RAW", "").strip().lower() in {"1", "true", "yes", "on"} def short_id(value: Any, prefix: int = 8, suffix: int = 4) -> str: text = "" if value is None else str(value) if len(text) <= prefix + suffix + 3: return text return f"{text[:prefix]}...{text[-suffix:]}" import re def redact(value: Any) -> Any: if isinstance(value, dict): return { key: ("" if key.lower() in SENSITIVE_KEYS else redact(item)) for key, item in value.items() } if isinstance(value, list): return [redact(item) for item in value] if isinstance(value, str): lowered = value.lower() sensitive_markers = ("api_key=", "password=", "token=", "bearer ", "cookie:", "private key") if any(marker in lowered for marker in sensitive_markers): return "" return value def summarize_data(data: Any) -> Any: if debug_raw_enabled(): return redact(data) if isinstance(data, list): return {"count": len(data)} if not isinstance(data, dict): return data if "results" in data: return { "count": len(data.get("results") or []), "total": data.get("total"), "local_total": data.get("local_total"), "openviking_total": data.get("openviking_total"), "searched_namespaces": data.get("searched_namespaces", []), } if "id" in data: return { "id": short_id(data.get("id")), "namespace": data.get("namespace"), "memory_type": data.get("memory_type"), "source": data.get("source"), } if "memory_id" in data: return {"status": data.get("status"), "memory_id": short_id(data.get("memory_id")), "feedback": data.get("feedback")} if "promoted" in data or "consolidation" in data: return { "status": data.get("status"), "promoted_count": len(data.get("promoted") or []), "archived_count": len(data.get("archived_episode_ids") or []), "consolidation_status": (data.get("consolidation") or {}).get("status") if isinstance(data.get("consolidation"), dict) else None, } allowed = {"ok", "status", "gateway", "service", "version", "healthy", "endpoint", "status_code", "error", "count"} return {key: redact(value) for key, value in data.items() if key in allowed} def summarize_result(result: dict[str, Any]) -> dict[str, Any]: return { "ok": bool(result.get("ok")), "endpoint": result.get("endpoint"), "status_code": result.get("status_code"), "error": redact(result.get("error", "")), "data": summarize_data(result.get("data")), } def dumps_safe(payload: Any, *, indent: int = 2) -> str: return json.dumps(redact(payload), ensure_ascii=False, indent=indent, default=str)