ForcePilot/backend/package/yuxi/channel/extensions/msteams/threading.py

87 lines
2.7 KiB
Python
Raw Normal View History

from __future__ import annotations
import logging
from datetime import UTC, datetime
logger = logging.getLogger(__name__)
THREAD_PARENT_CACHE_MAX = 100
THREAD_PARENT_CACHE_TTL_SECONDS = 300
class ThreadSessionManager:
def __init__(self):
self._parent_cache: dict[str, tuple[datetime, dict]] = {}
def cache_thread_parent(self, thread_id: str, parent_msg: dict) -> None:
now = datetime.now(UTC)
self._parent_cache[thread_id] = (now, parent_msg)
self._evict_expired()
def get_thread_parent(self, thread_id: str) -> dict | None:
now = datetime.now(UTC)
entry = self._parent_cache.get(thread_id)
if not entry:
return None
cached_at, parent = entry
if (now - cached_at).total_seconds() > THREAD_PARENT_CACHE_TTL_SECONDS:
self._parent_cache.pop(thread_id, None)
return None
return parent
def _evict_expired(self) -> None:
if len(self._parent_cache) <= THREAD_PARENT_CACHE_MAX:
return
now = datetime.now(UTC)
expired = [
k for k, (t, _) in self._parent_cache.items() if (now - t).total_seconds() > THREAD_PARENT_CACHE_TTL_SECONDS
]
for k in expired:
self._parent_cache.pop(k, None)
if len(self._parent_cache) > THREAD_PARENT_CACHE_MAX:
sorted_keys = sorted(
self._parent_cache.keys(),
key=lambda k: self._parent_cache[k][0],
)
for k in sorted_keys[: len(self._parent_cache) - THREAD_PARENT_CACHE_MAX]:
self._parent_cache.pop(k, None)
def resolve_msteams_session_key(
conversation_id: str,
*,
is_channel_thread: bool = False,
thread_root_message_id: str | None = None,
) -> str:
if is_channel_thread and thread_root_message_id:
return f"msteams:thread:{conversation_id}:{thread_root_message_id}"
return f"msteams:{conversation_id}"
def resolve_msteams_thread_id(
reply_to_id: str | None,
conversation_type: str | None,
) -> str | None:
if conversation_type == "channel" and reply_to_id:
return reply_to_id
return None
def format_thread_history(messages: list[dict]) -> str:
if not messages:
return ""
lines = ["[Thread history]"]
for msg in reversed(messages):
ts = msg.get("timestamp", "")
sender = msg.get("from_name", msg.get("from", {}).get("name", "Unknown"))
text = msg.get("text", "")
ts_display = ts[:19].replace("T", " ") if ts else ""
if ts_display:
lines.append(f"{ts_display} {sender}: {text}")
else:
lines.append(f"{sender}: {text}")
lines.append("[/Thread history]")
return "\n".join(lines)