Files
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

115 lines
3.9 KiB
Python

"""
消息入口的用户会话状态用例。
"""
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Callable, MutableMapping, Optional, Union
UserId = Union[str, int]
SessionEntry = tuple[str, datetime]
ExpiredSessionHandler = Callable[[str, UserId], None]
Clock = Callable[[], datetime]
SessionIdFactory = Callable[[UserId, datetime], str]
@dataclass(frozen=True, slots=True)
class SessionResolution:
"""
描述用户会话解析结果及是否复用了旧会话。
:param session_id: 本次解析得到的服务端会话标识
:param reused: 是否继续使用仍在有效期内的旧会话
:param inactive_minutes: 复用前距离上次活动的分钟数,新会话为 0
"""
session_id: str
reused: bool
inactive_minutes: float = 0.0
class MessageSessionService:
"""
管理消息用户到 Agent 会话的绑定、复用和过期清理。
"""
def __init__(
self,
*,
sessions: MutableMapping[UserId, SessionEntry],
timeout_minutes: int,
expired_handler: ExpiredSessionHandler,
clock: Clock = datetime.now,
session_id_factory: Optional[SessionIdFactory] = None,
) -> None:
"""
保存共享会话映射和由 Chain 提供的 Agent 清理端口。
"""
self._sessions = sessions
self._timeout = timedelta(minutes=timeout_minutes)
self._expired_handler = expired_handler
self._clock = clock
self._session_id_factory = session_id_factory or self._default_session_id
@staticmethod
def _default_session_id(user_id: UserId, now: datetime) -> str:
"""
按历史格式生成新的用户会话 ID。
"""
return f"user_{user_id}_{int(now.timestamp())}"
def cleanup(self, now: Optional[datetime] = None) -> None:
"""
移除超时绑定,并通知拥有者释放对应 Agent 会话。
"""
current_time = now or self._clock()
for user_id, (session_id, last_time) in list(self._sessions.items()):
if current_time - last_time <= self._timeout:
continue
self._sessions.pop(user_id, None)
self._expired_handler(session_id, user_id)
def resolve(self, user_id: UserId) -> SessionResolution:
"""
复用有效绑定或为用户创建新会话。
"""
current_time = self._clock()
self.cleanup(current_time)
current = self._sessions.get(user_id)
if current:
session_id, last_time = current
inactive = current_time - last_time
if inactive <= self._timeout:
self._sessions[user_id] = (session_id, current_time)
return SessionResolution(
session_id=session_id,
reused=True,
inactive_minutes=inactive.total_seconds() / 60,
)
session_id = self._session_id_factory(user_id, current_time)
self._sessions[user_id] = (session_id, current_time)
return SessionResolution(session_id=session_id, reused=False)
def bind(self, user_id: UserId, session_id: str) -> None:
"""
绑定指定会话,并在替换时释放旧会话。
"""
current = self._sessions.get(user_id)
if current and current[0] != session_id:
self._expired_handler(current[0], user_id)
self._sessions[user_id] = (session_id, self._clock())
def clear(self, user_id: UserId) -> Optional[str]:
"""
清除用户绑定并返回被移除的会话 ID。
"""
current = self._sessions.pop(user_id, None)
return current[0] if current else None
def get(self, user_id: UserId) -> Optional[SessionEntry]:
"""
读取用户当前会话绑定,不改变最后活动时间。
"""
return self._sessions.get(user_id)