From 2878a6e4bd0c1c19a13afa079e0c96abbeaa0b49 Mon Sep 17 00:00:00 2001 From: Kian Jones <11655409+kianjones9@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:39:26 -0700 Subject: [PATCH] feat: add path parameter validation for folder_id (#5523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- fern/openapi.json | 72 ++++++++++++++++++--- letta/server/rest_api/routers/v1/agents.py | 4 +- letta/server/rest_api/routers/v1/folders.py | 14 ++-- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/fern/openapi.json b/fern/openapi.json index 28278989..9679c029 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -2479,8 +2479,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" } ], "responses": { @@ -2518,8 +2524,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" } ], "requestBody": { @@ -2567,8 +2579,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" } ], "responses": { @@ -2865,8 +2883,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" }, { "name": "duplicate_handling", @@ -2945,8 +2969,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" }, { "name": "before", @@ -3071,8 +3101,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" }, { "name": "before", @@ -3197,8 +3233,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" }, { "name": "before", @@ -4541,8 +4583,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" }, { "name": "agent_id", @@ -4653,8 +4701,14 @@ "required": true, "schema": { "type": "string", + "minLength": 43, + "maxLength": 43, + "pattern": "^folder-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", + "description": "The ID of the folder in the format 'folder-'", + "examples": ["folder-123e4567-e89b-42d3-8456-426614174000"], "title": "Folder Id" - } + }, + "description": "The ID of the folder in the format 'folder-'" }, { "name": "agent_id", diff --git a/letta/server/rest_api/routers/v1/agents.py b/letta/server/rest_api/routers/v1/agents.py index cc26faf6..f3c178e9 100644 --- a/letta/server/rest_api/routers/v1/agents.py +++ b/letta/server/rest_api/routers/v1/agents.py @@ -512,7 +512,7 @@ async def attach_source( @router.patch("/{agent_id}/folders/attach/{folder_id}", response_model=AgentState, operation_id="attach_folder_to_agent") async def attach_folder_to_agent( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], agent_id: str = PATH_VALIDATORS["agent"], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -569,7 +569,7 @@ async def detach_source( @router.patch("/{agent_id}/folders/detach/{folder_id}", response_model=AgentState, operation_id="detach_folder_from_agent") async def detach_folder_from_agent( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], agent_id: str = PATH_VALIDATORS["agent"], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), diff --git a/letta/server/rest_api/routers/v1/folders.py b/letta/server/rest_api/routers/v1/folders.py index 149f3177..f5ea753f 100644 --- a/letta/server/rest_api/routers/v1/folders.py +++ b/letta/server/rest_api/routers/v1/folders.py @@ -62,7 +62,7 @@ async def count_folders( @router.get("/{folder_id}", response_model=Folder, operation_id="retrieve_folder") async def retrieve_folder( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -177,8 +177,8 @@ async def create_folder( @router.patch("/{folder_id}", response_model=Folder, operation_id="modify_folder") async def modify_folder( - folder_id: str, folder: SourceUpdate, + folder_id: str = PATH_VALIDATORS["folder"], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -193,7 +193,7 @@ async def modify_folder( @router.delete("/{folder_id}", response_model=None, operation_id="delete_folder") async def delete_folder( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -229,7 +229,7 @@ async def delete_folder( @router.post("/{folder_id}/upload", response_model=FileMetadata, operation_id="upload_file_to_folder") async def upload_file_to_folder( file: UploadFile, - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], duplicate_handling: DuplicateFileHandling = Query(DuplicateFileHandling.SUFFIX, description="How to handle duplicate filenames"), name: Optional[str] = Query(None, description="Optional custom name to override the uploaded file's name"), server: "SyncServer" = Depends(get_letta_server), @@ -344,7 +344,7 @@ async def upload_file_to_folder( @router.get("/{folder_id}/agents", response_model=List[str], operation_id="list_agents_for_folder") async def list_agents_for_folder( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], before: Optional[str] = Query( None, description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order", @@ -377,7 +377,7 @@ async def list_agents_for_folder( @router.get("/{folder_id}/passages", response_model=List[Passage], operation_id="list_folder_passages") async def list_folder_passages( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], before: Optional[str] = Query( None, description="Passage ID cursor for pagination. Returns passages that come before this passage ID in the specified sort order", @@ -410,7 +410,7 @@ async def list_folder_passages( @router.get("/{folder_id}/files", response_model=List[FileMetadata], operation_id="list_folder_files") async def list_folder_files( - folder_id: str, + folder_id: str = PATH_VALIDATORS["folder"], before: Optional[str] = Query( None, description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order",