diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 8232ab77c0a..92208650b8c 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -8121,15 +8121,30 @@ async def get_session_latest_descendant(session_id: str): } @app.get("/api/sessions/{session_id}/messages") -async def get_session_messages(session_id: str, profile: Optional[str] = None): +async def get_session_messages( + session_id: str, + profile: Optional[str] = None, + limit: Optional[int] = None, + offset: int = 0, +): db = _open_session_db_for_profile(profile) try: sid = db.resolve_session_id(session_id) if not sid: raise HTTPException(status_code=404, detail="Session not found") sid = db.resolve_resume_session_id(sid) - messages = db.get_messages(sid) - return {"session_id": sid, "messages": messages} + # Clamp limit to prevent abuse (max 500 per page) + _limit = min(limit, 500) if limit is not None else None + messages = db.get_messages(sid, limit=_limit, offset=offset) + return { + "session_id": sid, + "messages": messages, + "pagination": { + "limit": _limit, + "offset": offset, + "returned": len(messages), + }, + } finally: db.close() diff --git a/hermes_state.py b/hermes_state.py index 3702623aac4..c30048b1974 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -3710,7 +3710,11 @@ class SessionDB: def get_messages( - self, session_id: str, include_inactive: bool = False + self, + session_id: str, + include_inactive: bool = False, + limit: Optional[int] = None, + offset: int = 0, ) -> List[Dict[str, Any]]: """Load messages for a session in insertion order. @@ -3721,14 +3725,22 @@ class SessionDB: Ordered by AUTOINCREMENT id (true insertion order) rather than timestamp — see c03acca50 for the WSL2 clock-regression rationale. + + When ``limit`` is provided, returns at most ``limit`` messages + starting from ``offset`` (0-based, in insertion order). Enables + pagination for the API endpoint to avoid loading entire transcripts. """ active_clause = "" if include_inactive else " AND active = 1" + sql = ( + "SELECT * FROM messages WHERE session_id = ?" + f"{active_clause} ORDER BY id" + ) + params: list = [session_id] + if limit is not None: + sql += " LIMIT ? OFFSET ?" + params.extend([limit, offset]) with self._lock: - cursor = self._conn.execute( - "SELECT * FROM messages WHERE session_id = ?" - f"{active_clause} ORDER BY id", - (session_id,), - ) + cursor = self._conn.execute(sql, params) rows = cursor.fetchall() result = [] for row in rows: