mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(acp): follow-up — named-const page size, alias kwarg, tests
- Replace kwargs.get('limit', 50) with module-level _LIST_SESSIONS_PAGE_SIZE
constant. ListSessionsRequest schema has no 'limit' field, so the kwarg
path was dead. Constant is the single source of truth for the page cap.
- Use next_cursor= (field name) instead of nextCursor= (alias). Both work
under the schema's populate_by_name config, but using the declared
Python field name is the consistent style in this file.
- Add docstring explaining cwd pass-through and cursor semantics.
- Add 4 tests: first-page with next_cursor, single-page no next_cursor,
cursor resumes after match, unknown cursor returns empty page.
This commit is contained in:
parent
c1fb7b6d27
commit
4cc5065f63
2 changed files with 69 additions and 8 deletions
|
|
@ -71,6 +71,11 @@ except Exception:
|
|||
# Thread pool for running AIAgent (synchronous) in parallel.
|
||||
_executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="acp-agent")
|
||||
|
||||
# Server-side page size for list_sessions. The ACP ListSessionsRequest schema
|
||||
# does not expose a client-side limit, so this is a fixed cap that clients
|
||||
# paginate against using `cursor` / `next_cursor`.
|
||||
_LIST_SESSIONS_PAGE_SIZE = 50
|
||||
|
||||
|
||||
def _extract_text(
|
||||
prompt: list[
|
||||
|
|
@ -446,22 +451,27 @@ class HermesACPAgent(acp.Agent):
|
|||
cwd: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ListSessionsResponse:
|
||||
"""List ACP sessions with optional ``cwd`` filtering and cursor pagination.
|
||||
|
||||
``cwd`` is passed through to ``SessionManager.list_sessions`` which already
|
||||
normalizes and filters by working directory. ``cursor`` is a ``session_id``
|
||||
previously returned as ``next_cursor``; results resume after that entry.
|
||||
Server-side page size is capped at ``_LIST_SESSIONS_PAGE_SIZE``; when more
|
||||
results remain, ``next_cursor`` is set to the last returned ``session_id``.
|
||||
"""
|
||||
infos = self.session_manager.list_sessions(cwd=cwd)
|
||||
|
||||
if cursor:
|
||||
# Find the cursor index
|
||||
for idx, s in enumerate(infos):
|
||||
if s["session_id"] == cursor:
|
||||
infos = infos[idx + 1:]
|
||||
break
|
||||
else:
|
||||
# Cursor not found, return empty
|
||||
# Unknown cursor -> empty page (do not fall back to full list).
|
||||
infos = []
|
||||
|
||||
# Cap limit
|
||||
limit = kwargs.get("limit", 50)
|
||||
has_more = len(infos) > limit
|
||||
infos = infos[:limit]
|
||||
has_more = len(infos) > _LIST_SESSIONS_PAGE_SIZE
|
||||
infos = infos[:_LIST_SESSIONS_PAGE_SIZE]
|
||||
|
||||
sessions = []
|
||||
for s in infos:
|
||||
|
|
@ -476,9 +486,9 @@ class HermesACPAgent(acp.Agent):
|
|||
updated_at=updated_at,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
next_cursor = sessions[-1].session_id if has_more and sessions else None
|
||||
return ListSessionsResponse(sessions=sessions, nextCursor=next_cursor)
|
||||
return ListSessionsResponse(sessions=sessions, next_cursor=next_cursor)
|
||||
|
||||
# ---- Prompt (core) ------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -270,6 +270,57 @@ class TestListAndFork:
|
|||
|
||||
mock_list.assert_called_once_with(cwd="/mnt/e/Projects/AI/browser-link-3")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sessions_pagination_first_page(self, agent):
|
||||
from acp_adapter import server as acp_server
|
||||
|
||||
infos = [
|
||||
{"session_id": f"s{i}", "cwd": "/tmp", "title": None, "updated_at": 0.0}
|
||||
for i in range(acp_server._LIST_SESSIONS_PAGE_SIZE + 5)
|
||||
]
|
||||
with patch.object(agent.session_manager, "list_sessions", return_value=infos):
|
||||
resp = await agent.list_sessions()
|
||||
|
||||
assert len(resp.sessions) == acp_server._LIST_SESSIONS_PAGE_SIZE
|
||||
assert resp.next_cursor == resp.sessions[-1].session_id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sessions_pagination_no_more(self, agent):
|
||||
infos = [
|
||||
{"session_id": f"s{i}", "cwd": "/tmp", "title": None, "updated_at": 0.0}
|
||||
for i in range(3)
|
||||
]
|
||||
with patch.object(agent.session_manager, "list_sessions", return_value=infos):
|
||||
resp = await agent.list_sessions()
|
||||
|
||||
assert len(resp.sessions) == 3
|
||||
assert resp.next_cursor is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sessions_cursor_resumes_after_match(self, agent):
|
||||
infos = [
|
||||
{"session_id": "s1", "cwd": "/tmp", "title": None, "updated_at": 0.0},
|
||||
{"session_id": "s2", "cwd": "/tmp", "title": None, "updated_at": 0.0},
|
||||
{"session_id": "s3", "cwd": "/tmp", "title": None, "updated_at": 0.0},
|
||||
]
|
||||
with patch.object(agent.session_manager, "list_sessions", return_value=infos):
|
||||
resp = await agent.list_sessions(cursor="s1")
|
||||
|
||||
assert [s.session_id for s in resp.sessions] == ["s2", "s3"]
|
||||
assert resp.next_cursor is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sessions_unknown_cursor_returns_empty(self, agent):
|
||||
infos = [
|
||||
{"session_id": "s1", "cwd": "/tmp", "title": None, "updated_at": 0.0},
|
||||
{"session_id": "s2", "cwd": "/tmp", "title": None, "updated_at": 0.0},
|
||||
]
|
||||
with patch.object(agent.session_manager, "list_sessions", return_value=infos):
|
||||
resp = await agent.list_sessions(cursor="does-not-exist")
|
||||
|
||||
assert resp.sessions == []
|
||||
assert resp.next_cursor is None
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# session configuration / model routing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue