feat: add pagination to agent files endpoint (#5086)
* feat: add pagination to agent files endpoint
This commit is contained in:
@@ -4961,6 +4961,87 @@
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "File ID cursor for pagination. Returns files that come before this file ID in the specified sort order",
|
||||
"title": "Before"
|
||||
},
|
||||
"description": "File ID cursor for pagination. Returns files that come before this file ID in the specified sort order"
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "File ID cursor for pagination. Returns files that come after this file ID in the specified sort order",
|
||||
"title": "After"
|
||||
},
|
||||
"description": "File ID cursor for pagination. Returns files that come after this file ID in the specified sort order"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Maximum number of files to return",
|
||||
"default": 100,
|
||||
"title": "Limit"
|
||||
},
|
||||
"description": "Maximum number of files to return"
|
||||
},
|
||||
{
|
||||
"name": "order",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string",
|
||||
"description": "Sort order for files by creation time. 'asc' for oldest first, 'desc' for newest first",
|
||||
"default": "desc",
|
||||
"title": "Order"
|
||||
},
|
||||
"description": "Sort order for files by creation time. 'asc' for oldest first, 'desc' for newest first"
|
||||
},
|
||||
{
|
||||
"name": "order_by",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"const": "created_at",
|
||||
"type": "string",
|
||||
"description": "Field to sort by",
|
||||
"default": "created_at",
|
||||
"title": "Order By"
|
||||
},
|
||||
"description": "Field to sort by"
|
||||
},
|
||||
{
|
||||
"name": "cursor",
|
||||
"in": "query",
|
||||
@@ -4974,24 +5055,12 @@
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Pagination cursor from previous response",
|
||||
"description": "Pagination cursor from previous response (deprecated, use before/after)",
|
||||
"deprecated": true,
|
||||
"title": "Cursor"
|
||||
},
|
||||
"description": "Pagination cursor from previous response"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"description": "Number of items to return (1-100)",
|
||||
"default": 20,
|
||||
"title": "Limit"
|
||||
},
|
||||
"description": "Number of items to return (1-100)"
|
||||
"description": "Pagination cursor from previous response (deprecated, use before/after)",
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "is_open",
|
||||
|
||||
@@ -794,8 +794,20 @@ async def list_agent_folders(
|
||||
@router.get("/{agent_id}/files", response_model=PaginatedAgentFiles, operation_id="list_agent_files")
|
||||
async def list_agent_files(
|
||||
agent_id: str,
|
||||
cursor: Optional[str] = Query(None, description="Pagination cursor from previous response"),
|
||||
limit: int = Query(20, ge=1, le=100, description="Number of items to return (1-100)"),
|
||||
before: Optional[str] = Query(
|
||||
None, description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order"
|
||||
),
|
||||
after: Optional[str] = Query(
|
||||
None, description="File ID cursor for pagination. Returns files that come after this file ID in the specified sort order"
|
||||
),
|
||||
limit: Optional[int] = Query(100, description="Maximum number of files to return"),
|
||||
order: Literal["asc", "desc"] = Query(
|
||||
"desc", description="Sort order for files by creation time. 'asc' for oldest first, 'desc' for newest first"
|
||||
),
|
||||
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
||||
cursor: Optional[str] = Query(
|
||||
None, description="Pagination cursor from previous response (deprecated, use before/after)", deprecated=True
|
||||
),
|
||||
is_open: Optional[bool] = Query(None, description="Filter by open status (true for open files, false for closed files)"),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -805,9 +817,18 @@ async def list_agent_files(
|
||||
"""
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
|
||||
effective_limit = limit or 20
|
||||
|
||||
# get paginated file-agent relationships for this agent
|
||||
file_agents, next_cursor, has_more = await server.file_agent_manager.list_files_for_agent_paginated(
|
||||
agent_id=agent_id, actor=actor, cursor=cursor, limit=limit, is_open=is_open
|
||||
agent_id=agent_id,
|
||||
actor=actor,
|
||||
cursor=cursor, # keep for backwards compatibility
|
||||
limit=effective_limit,
|
||||
is_open=is_open,
|
||||
before=before,
|
||||
after=after,
|
||||
ascending=(order == "asc"),
|
||||
)
|
||||
|
||||
# enrich with file and source metadata
|
||||
|
||||
@@ -300,6 +300,9 @@ class FileAgentManager:
|
||||
cursor: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
is_open: Optional[bool] = None,
|
||||
before: Optional[str] = None,
|
||||
after: Optional[str] = None,
|
||||
ascending: bool = False,
|
||||
) -> tuple[List[PydanticFileAgent], Optional[str], bool]:
|
||||
"""
|
||||
Return paginated file associations for an agent.
|
||||
@@ -307,9 +310,12 @@ class FileAgentManager:
|
||||
Args:
|
||||
agent_id: The agent ID to get files for
|
||||
actor: User performing the action
|
||||
cursor: Pagination cursor (file-agent ID to start after)
|
||||
cursor: Pagination cursor (file-agent ID to start after) - deprecated, use before/after
|
||||
limit: Maximum number of results to return
|
||||
is_open: Optional filter for open/closed status (None = all, True = open only, False = closed only)
|
||||
before: File-agent ID cursor for pagination. Returns files that come before this ID in the specified sort order
|
||||
after: File-agent ID cursor for pagination. Returns files that come after this ID in the specified sort order
|
||||
ascending: Sort order (True = ascending by created_at/id, False = descending)
|
||||
|
||||
Returns:
|
||||
Tuple of (file_agents, next_cursor, has_more)
|
||||
@@ -325,14 +331,27 @@ class FileAgentManager:
|
||||
if is_open is not None:
|
||||
conditions.append(FileAgentModel.is_open == is_open)
|
||||
|
||||
# apply cursor if provided (get records after this ID)
|
||||
if cursor:
|
||||
# handle pagination cursors (support both old and new style)
|
||||
if before:
|
||||
conditions.append(FileAgentModel.id < before)
|
||||
elif after:
|
||||
conditions.append(FileAgentModel.id > after)
|
||||
elif cursor:
|
||||
# fallback to old cursor behavior for backwards compatibility
|
||||
conditions.append(FileAgentModel.id > cursor)
|
||||
|
||||
query = select(FileAgentModel).where(and_(*conditions))
|
||||
|
||||
# order by ID for stable pagination
|
||||
query = query.order_by(FileAgentModel.id)
|
||||
# apply sorting based on pagination method
|
||||
if before or after:
|
||||
# For new cursor-based pagination, use created_at + id ordering
|
||||
if ascending:
|
||||
query = query.order_by(FileAgentModel.created_at.asc(), FileAgentModel.id.asc())
|
||||
else:
|
||||
query = query.order_by(FileAgentModel.created_at.desc(), FileAgentModel.id.desc())
|
||||
else:
|
||||
# For old cursor compatibility, maintain original behavior (ascending by ID)
|
||||
query = query.order_by(FileAgentModel.id)
|
||||
|
||||
# fetch limit + 1 to check if there are more results
|
||||
query = query.limit(limit + 1)
|
||||
|
||||
Reference in New Issue
Block a user