Download splitbit_llm/connectors/webhooks.py from hermescures1/splitbit-llm: direct link, hf CLI and curl.
- Browser
- Download file 4.79 kB
-
https://huggingface.co/hermescures1/splitbit-llm/resolve/main/splitbit_llm/connectors/webhooks.py
- Command line
-
hf download hf://hermescures1/splitbit-llm/splitbit_llm/connectors/webhooks.py
-
curl -L -o webhooks.py https://huggingface.co/hermescures1/splitbit-llm/resolve/main/splitbit_llm/connectors/webhooks.py
4.79 kB
| """Webhook support — receive incoming webhooks and send outgoing webhooks. | |
| Incoming: FastAPI endpoint that receives webhooks and routes them to the LLM. | |
| Outgoing: Send webhook notifications when events happen (goal completed, etc.). | |
| Webhooks allow external systems to trigger the LLM or be notified by it. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import hmac | |
| import json | |
| import logging | |
| import time | |
| import urllib.request | |
| from dataclasses import dataclass, field | |
| from typing import Any, Callable | |
| logger = logging.getLogger(__name__) | |
| class WebhookEndpoint: | |
| """A registered incoming webhook endpoint.""" | |
| path: str | |
| secret: str = "" | |
| handler: Callable[[dict], dict] | None = None | |
| calls: int = 0 | |
| last_call: float = 0.0 | |
| class WebhookManager: | |
| """Manages incoming and outgoing webhooks. | |
| Incoming webhooks are registered with a path and optional secret. | |
| When a POST request hits the path, the handler is called. | |
| Outgoing webhooks are sent to external URLs to notify them of events. | |
| """ | |
| def __init__(self) -> None: | |
| self._endpoints: dict[str, WebhookEndpoint] = {} | |
| self._outgoing: list[dict[str, str]] = [] | |
| self._stats = { | |
| "incoming_received": 0, | |
| "incoming_processed": 0, | |
| "incoming_rejected": 0, | |
| "outgoing_sent": 0, | |
| "outgoing_failed": 0, | |
| } | |
| def register_endpoint(self, path: str, handler: Callable[[dict], dict], | |
| secret: str = "") -> None: | |
| """Register an incoming webhook endpoint.""" | |
| self._endpoints[path] = WebhookEndpoint(path=path, secret=secret, handler=handler) | |
| logger.info("Registered webhook endpoint: %s", path) | |
| def handle_request(self, path: str, body: dict, signature: str = "") -> dict: | |
| """Handle an incoming webhook request. | |
| Args: | |
| path: webhook path | |
| body: request body | |
| signature: HMAC signature for verification | |
| Returns: | |
| Response dict | |
| """ | |
| self._stats["incoming_received"] += 1 | |
| endpoint = self._endpoints.get(path) | |
| if endpoint is None: | |
| self._stats["incoming_rejected"] += 1 | |
| return {"error": "Unknown webhook endpoint", "path": path} | |
| # Verify signature if secret is set | |
| if endpoint.secret: | |
| expected = hmac.new( | |
| endpoint.secret.encode(), | |
| json.dumps(body, sort_keys=True).encode(), | |
| hashlib.sha256 | |
| ).hexdigest() | |
| if not hmac.compare_digest(expected, signature): | |
| self._stats["incoming_rejected"] += 1 | |
| return {"error": "Invalid signature"} | |
| endpoint.calls += 1 | |
| endpoint.last_call = time.time() | |
| try: | |
| result = endpoint.handler(body) if endpoint.handler else {"received": True} | |
| self._stats["incoming_processed"] += 1 | |
| return result | |
| except Exception as e: | |
| logger.error("Webhook handler error: %s", e) | |
| return {"error": str(e)} | |
| def send(self, url: str, event: str, data: dict, secret: str = "") -> bool: | |
| """Send an outgoing webhook notification. | |
| Args: | |
| url: target URL | |
| event: event name | |
| data: payload | |
| secret: HMAC secret for signing | |
| Returns: | |
| True if sent successfully | |
| """ | |
| payload = json.dumps({"event": event, "data": data, "timestamp": time.time()}).encode() | |
| headers = {"Content-Type": "application/json"} | |
| if secret: | |
| signature = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() | |
| headers["X-Webhook-Signature"] = signature | |
| try: | |
| req = urllib.request.Request(url, data=payload, method="POST", headers=headers) | |
| with urllib.request.urlopen(req, timeout=10) as resp: | |
| if resp.status < 300: | |
| self._stats["outgoing_sent"] += 1 | |
| self._outgoing.append({"url": url, "event": event, "time": str(time.time())}) | |
| logger.info("Webhook sent to %s: %s", url, event) | |
| return True | |
| except Exception as e: | |
| logger.error("Webhook send failed: %s", e) | |
| self._stats["outgoing_failed"] += 1 | |
| return False | |
| def notify_event(self, event: str, data: dict) -> None: | |
| """Notify all registered outgoing webhooks of an event.""" | |
| for hook in list(self._outgoing): | |
| self.send(hook["url"], event, data) | |
| def list_endpoints(self) -> list[str]: | |
| return list(self._endpoints.keys()) | |
| def get_stats(self) -> dict[str, Any]: | |
| return {**self._stats, "endpoints": len(self._endpoints)} | |