87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
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: ("<redacted>" 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 "<redacted>"
|
|
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)
|