Files
yvgude--lean-ctx/integrations/hermes-lean-ctx/transport.py
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

128 lines
4.6 KiB
Python

"""Resilient gateway around the lean-ctx HTTP ``/v1`` SDK.
The engine runs inside the agent's synchronous turn loop, so every call must be
fast and must never raise into the host: a missing ``leanctx`` package or a
down daemon degrades to a logged no-op, not a crash.
"""
from __future__ import annotations
import logging
import time
from typing import Any, Dict, Optional
from .config import LeanCtxConfig
logger = logging.getLogger(__name__)
# How long a successful/failed health probe is trusted before re-checking.
_HEALTH_TTL_SECONDS = 30.0
class ToolGateway:
"""Lazy, fault-tolerant facade over ``leanctx.LeanCtxClient``."""
def __init__(self, config: LeanCtxConfig) -> None:
self._config = config
self._client: Any = None
self._client_error: Optional[str] = None
self._healthy: Optional[bool] = None
self._health_checked_at: float = 0.0
# --- client construction ------------------------------------------------
def _get_client(self) -> Any:
if self._client is not None or self._client_error is not None:
return self._client
try:
from leanctx import LeanCtxClient
except Exception as exc: # pragma: no cover - depends on install
self._client_error = f"leanctx SDK not importable: {exc}"
logger.warning(
"lean-ctx engine: %s — install with `pip install lean-ctx-client`. "
"Compaction/recall will no-op until resolved.",
self._client_error,
)
return None
try:
self._client = LeanCtxClient(
self._config.base_url,
bearer_token=self._config.token,
workspace_id=self._config.workspace_id,
channel_id=self._config.channel_id,
timeout=self._config.timeout,
)
except Exception as exc:
self._client_error = f"failed to construct LeanCtxClient: {exc}"
logger.warning("lean-ctx engine: %s", self._client_error)
return None
return self._client
# --- health -------------------------------------------------------------
def is_available(self, *, force: bool = False) -> bool:
"""Return whether the daemon is reachable, cached for a short TTL."""
now = time.monotonic()
if (
not force
and self._healthy is not None
and (now - self._health_checked_at) < _HEALTH_TTL_SECONDS
):
return self._healthy
client = self._get_client()
if client is None:
self._healthy = False
self._health_checked_at = now
return False
try:
client.health()
self._healthy = True
except Exception as exc:
if self._healthy is not False:
logger.warning(
"lean-ctx engine: daemon unreachable at %s (%s). "
"Operating in degraded no-op mode until it returns.",
self._config.base_url,
exc,
)
self._healthy = False
self._health_checked_at = now
return self._healthy
# --- calls --------------------------------------------------------------
def call_text(self, name: str, arguments: Optional[Dict[str, Any]] = None) -> Optional[str]:
"""Call a tool, returning its text result or ``None`` on any failure."""
client = self._get_client()
if client is None:
return None
try:
return client.call_tool_text(name, arguments or {})
except Exception as exc:
logger.warning("lean-ctx engine: tool '%s' failed: %s", name, exc)
self._healthy = False
self._health_checked_at = time.monotonic()
return None
def get_metrics(self) -> Optional[Dict[str, Any]]:
client = self._get_client()
if client is None:
return None
try:
result = client.metrics()
return result if isinstance(result, dict) else None
except Exception as exc:
logger.debug("lean-ctx engine: metrics() failed: %s", exc)
return None
def get_context_summary(self) -> Optional[Dict[str, Any]]:
client = self._get_client()
if client is None:
return None
try:
result = client.context_summary()
return result if isinstance(result, dict) else None
except Exception as exc:
logger.debug("lean-ctx engine: context_summary() failed: %s", exc)
return None