"""Microsoft Teams 入站消息防抖。 相同 sender + conversation 连续消息文本合并, 减少 Agent 调用次数,提升响应效率。 """ from __future__ import annotations import time from typing import Any DEBOUNCE_TTL_SECONDS = 3.0 DEBOUNCE_MAX_TEXT_LENGTH = 4000 class DebounceManager: def __init__(self, ttl: float = DEBOUNCE_TTL_SECONDS): self._ttl = ttl self._pending: dict[str, dict[str, Any]] = {} @staticmethod def make_key(app_id: str, conversation_id: str, sender_id: str) -> str: return f"msteams:{app_id}:{conversation_id}:{sender_id}" def merge(self, key: str, text: str, metadata: dict[str, Any] | None = None) -> dict[str, Any] | None: now = time.monotonic() existing = self._pending.get(key) if existing and (now - existing["timestamp"]) <= self._ttl: merged_text = existing["text"] + "\n" + text if len(merged_text) > DEBOUNCE_MAX_TEXT_LENGTH: merged_text = merged_text[:DEBOUNCE_MAX_TEXT_LENGTH] self._pending[key] = { "text": merged_text, "timestamp": now, "merge_count": existing.get("merge_count", 1) + 1, "metadata": {**(existing.get("metadata") or {}), **(metadata or {})}, } return None self._pending[key] = { "text": text, "timestamp": now, "merge_count": 1, "metadata": metadata or {}, } self._cleanup(now) return self._pending[key] def get(self, key: str) -> dict[str, Any] | None: return self._pending.get(key) def consume(self, key: str) -> dict[str, Any] | None: entry = self._pending.pop(key, None) if not entry: return None return { "text": entry["text"], "merge_count": entry["merge_count"], "metadata": entry["metadata"], } def _cleanup(self, now: float) -> None: expired = [k for k, v in self._pending.items() if now - v["timestamp"] > self._ttl * 2] for k in expired: self._pending.pop(k, None) def clear(self) -> None: self._pending.clear() @property def pending_count(self) -> int: return len(self._pending)