feat: add path parameter validation for file_id (#5522)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Kian Jones
2025-10-17 16:39:17 -07:00
committed by Caren Thomas
parent 3b56c53b6c
commit 47bf549dfc
4 changed files with 66 additions and 16 deletions

View File

@@ -2317,8 +2317,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 43,
"maxLength": 43,
"pattern": "^source-[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 source in the format 'source-<uuid4>'",
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"],
"title": "Source Id"
}
},
"description": "The ID of the source in the format 'source-<uuid4>'"
},
{
"name": "file_id",
@@ -2326,8 +2332,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 41,
"maxLength": 41,
"pattern": "^file-[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 file in the format 'file-<uuid4>'",
"examples": ["file-123e4567-e89b-42d3-8456-426614174000"],
"title": "File Id"
}
},
"description": "The ID of the file in the format 'file-<uuid4>'"
},
{
"name": "include_content",
@@ -2380,8 +2392,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 43,
"maxLength": 43,
"pattern": "^source-[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 source in the format 'source-<uuid4>'",
"examples": ["source-123e4567-e89b-42d3-8456-426614174000"],
"title": "Source Id"
}
},
"description": "The ID of the source in the format 'source-<uuid4>'"
},
{
"name": "file_id",
@@ -2389,8 +2407,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 41,
"maxLength": 41,
"pattern": "^file-[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 file in the format 'file-<uuid4>'",
"examples": ["file-123e4567-e89b-42d3-8456-426614174000"],
"title": "File Id"
}
},
"description": "The ID of the file in the format 'file-<uuid4>'"
}
],
"responses": {
@@ -3311,8 +3335,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-<uuid4>'",
"examples": ["folder-123e4567-e89b-42d3-8456-426614174000"],
"title": "Folder Id"
}
},
"description": "The ID of the folder in the format 'folder-<uuid4>'"
},
{
"name": "file_id",
@@ -3320,8 +3350,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 41,
"maxLength": 41,
"pattern": "^file-[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 file in the format 'file-<uuid4>'",
"examples": ["file-123e4567-e89b-42d3-8456-426614174000"],
"title": "File Id"
}
},
"description": "The ID of the file in the format 'file-<uuid4>'"
}
],
"responses": {
@@ -4724,8 +4760,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 41,
"maxLength": 41,
"pattern": "^file-[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 file in the format 'file-<uuid4>'",
"examples": ["file-123e4567-e89b-42d3-8456-426614174000"],
"title": "File Id"
}
},
"description": "The ID of the file in the format 'file-<uuid4>'"
},
{
"name": "agent_id",
@@ -4784,8 +4826,14 @@
"required": true,
"schema": {
"type": "string",
"minLength": 41,
"maxLength": 41,
"pattern": "^file-[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 file in the format 'file-<uuid4>'",
"examples": ["file-123e4567-e89b-42d3-8456-426614174000"],
"title": "File Id"
}
},
"description": "The ID of the file in the format 'file-<uuid4>'"
},
{
"name": "agent_id",

View File

@@ -616,7 +616,7 @@ async def close_all_open_files(
@router.patch("/{agent_id}/files/{file_id}/open", response_model=List[str], operation_id="open_file")
async def open_file(
file_id: str,
file_id: str = PATH_VALIDATORS["file"],
agent_id: str = PATH_VALIDATORS["agent"],
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
@@ -665,7 +665,7 @@ async def open_file(
@router.patch("/{agent_id}/files/{file_id}/close", response_model=None, operation_id="close_file")
async def close_file(
file_id: str,
file_id: str = PATH_VALIDATORS["file"],
agent_id: str = PATH_VALIDATORS["agent"],
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),

View File

@@ -37,6 +37,7 @@ from letta.services.file_processor.parser.markitdown_parser import MarkitdownFil
from letta.services.file_processor.parser.mistral_parser import MistralFileParser
from letta.settings import settings
from letta.utils import safe_create_file_processing_task, safe_create_task, sanitize_filename
from letta.validators import PATH_VALIDATORS
logger = get_logger(__name__)
@@ -496,8 +497,8 @@ async def list_folder_files(
# it's still good practice to return a status indicating the success or failure of the deletion
@router.delete("/{folder_id}/{file_id}", status_code=204, operation_id="delete_file_from_folder")
async def delete_file_from_folder(
folder_id: str,
file_id: str,
folder_id: str = PATH_VALIDATORS["folder"],
file_id: str = PATH_VALIDATORS["file"],
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):

View File

@@ -36,6 +36,7 @@ from letta.services.file_processor.parser.markitdown_parser import MarkitdownFil
from letta.services.file_processor.parser.mistral_parser import MistralFileParser
from letta.settings import settings
from letta.utils import safe_create_file_processing_task, safe_create_task, sanitize_filename
from letta.validators import PATH_VALIDATORS
logger = get_logger(__name__)
@@ -386,8 +387,8 @@ async def list_source_files(
@router.get("/{source_id}/files/{file_id}", response_model=FileMetadata, operation_id="get_file_metadata", deprecated=True)
async def get_file_metadata(
source_id: str,
file_id: str,
source_id: str = PATH_VALIDATORS["source"],
file_id: str = PATH_VALIDATORS["file"],
include_content: bool = Query(False, description="Whether to include full file content"),
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
@@ -412,8 +413,8 @@ async def get_file_metadata(
# it's still good practice to return a status indicating the success or failure of the deletion
@router.delete("/{source_id}/{file_id}", status_code=204, operation_id="delete_file_from_source", deprecated=True)
async def delete_file_from_source(
source_id: str,
file_id: str,
source_id: str = PATH_VALIDATORS["source"],
file_id: str = PATH_VALIDATORS["file"],
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):