diff --git a/fern/openapi.json b/fern/openapi.json index 0e2258ed..c1861dd9 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -2521,14 +2521,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } } ], "responses": { @@ -2566,14 +2561,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } } ], "requestBody": { @@ -2621,14 +2611,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } } ], "responses": { @@ -2925,14 +2910,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } }, { "name": "duplicate_handling", @@ -3011,14 +2991,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } }, { "name": "before", @@ -3143,14 +3118,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } }, { "name": "before", @@ -3275,14 +3245,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } }, { "name": "before", @@ -3419,14 +3384,9 @@ "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"], + "pattern": "^source-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", "title": "Folder Id" - }, - "description": "The ID of the folder in the format 'folder-'" + } }, { "name": "file_id", @@ -3434,14 +3394,9 @@ "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-'", - "examples": ["file-123e4567-e89b-42d3-8456-426614174000"], "title": "File Id" - }, - "description": "The ID of the file in the format 'file-'" + } } ], "responses": { @@ -4626,19 +4581,19 @@ "operationId": "attach_folder_to_agent", "parameters": [ { - "name": "folder_id", + "name": "source_id", "in": "path", "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" + "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-'", + "examples": ["source-123e4567-e89b-42d3-8456-426614174000"], + "title": "Source Id" }, - "description": "The ID of the folder in the format 'folder-'" + "description": "The ID of the source in the format 'source-'" }, { "name": "agent_id", @@ -4750,19 +4705,19 @@ "operationId": "detach_folder_from_agent", "parameters": [ { - "name": "folder_id", + "name": "source_id", "in": "path", "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" + "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-'", + "examples": ["source-123e4567-e89b-42d3-8456-426614174000"], + "title": "Source Id" }, - "description": "The ID of the folder in the format 'folder-'" + "description": "The ID of the source in the format 'source-'" }, { "name": "agent_id", diff --git a/letta/server/rest_api/routers/v1/agents.py b/letta/server/rest_api/routers/v1/agents.py index a185bc62..237accfa 100644 --- a/letta/server/rest_api/routers/v1/agents.py +++ b/letta/server/rest_api/routers/v1/agents.py @@ -30,9 +30,9 @@ from letta.otel.context import get_ctx_attributes from letta.otel.metric_registry import MetricRegistry from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent from letta.schemas.agent_file import AgentFileSchema -from letta.schemas.block import Block, BlockUpdate +from letta.schemas.block import BaseBlock, Block, BlockUpdate from letta.schemas.enums import AgentType, RunStatus -from letta.schemas.file import AgentFileAttachment, PaginatedAgentFiles +from letta.schemas.file import AgentFileAttachment, FileMetadataBase, PaginatedAgentFiles from letta.schemas.group import Group from letta.schemas.job import LettaRequestConfig from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion, MessageType @@ -46,11 +46,11 @@ from letta.schemas.memory import ( CreateArchivalMemory, Memory, ) -from letta.schemas.message import MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult +from letta.schemas.message import BaseMessage, MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult from letta.schemas.passage import Passage from letta.schemas.run import Run as PydanticRun, RunUpdate -from letta.schemas.source import Source -from letta.schemas.tool import Tool +from letta.schemas.source import BaseSource, Source +from letta.schemas.tool import BaseTool, Tool from letta.schemas.user import User from letta.serialize_schemas.pydantic_agent_schema import AgentSchema from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server @@ -170,7 +170,7 @@ class IndentedORJSONResponse(Response): @router.get("/{agent_id}/export", response_class=IndentedORJSONResponse, operation_id="export_agent") async def export_agent( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], max_steps: int = 100, server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -353,7 +353,7 @@ async def import_agent( @router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window") async def retrieve_agent_context_window( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -393,7 +393,7 @@ async def create_agent( @router.patch("/{agent_id}", response_model=AgentState, operation_id="modify_agent") async def modify_agent( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], update_agent: UpdateAgent = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -405,7 +405,7 @@ async def modify_agent( @router.get("/{agent_id}/tools", response_model=list[Tool], operation_id="list_agent_tools") async def list_agent_tools( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), before: Optional[str] = Query( @@ -434,8 +434,8 @@ async def list_agent_tools( @router.patch("/{agent_id}/tools/attach/{tool_id}", response_model=AgentState, operation_id="attach_tool") async def attach_tool( - tool_id: str = PATH_VALIDATORS["tool"], - agent_id: str = PATH_VALIDATORS["agent"], + tool_id: str = PATH_VALIDATORS[BaseTool.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -450,8 +450,8 @@ async def attach_tool( @router.patch("/{agent_id}/tools/detach/{tool_id}", response_model=AgentState, operation_id="detach_tool") async def detach_tool( - tool_id: str = PATH_VALIDATORS["tool"], - agent_id: str = PATH_VALIDATORS["agent"], + tool_id: str = PATH_VALIDATORS[BaseTool.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -468,7 +468,7 @@ async def detach_tool( async def modify_approval( tool_name: str, requires_approval: bool, - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -485,8 +485,8 @@ async def modify_approval( @router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent") async def attach_source( - source_id: str = PATH_VALIDATORS["source"], - agent_id: str = PATH_VALIDATORS["agent"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -512,8 +512,8 @@ 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 = PATH_VALIDATORS["folder"], - agent_id: str = PATH_VALIDATORS["agent"], + folder_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -539,8 +539,8 @@ async def attach_folder_to_agent( @router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent") async def detach_source( - source_id: str = PATH_VALIDATORS["source"], - agent_id: str = PATH_VALIDATORS["agent"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -569,8 +569,8 @@ 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 = PATH_VALIDATORS["folder"], - agent_id: str = PATH_VALIDATORS["agent"], + folder_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -599,7 +599,7 @@ async def detach_folder_from_agent( @router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="close_all_open_files") async def close_all_open_files( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -616,8 +616,8 @@ 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 = PATH_VALIDATORS["file"], - agent_id: str = PATH_VALIDATORS["agent"], + file_id: str = PATH_VALIDATORS[FileMetadataBase.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -665,8 +665,8 @@ 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 = PATH_VALIDATORS["file"], - agent_id: str = PATH_VALIDATORS["agent"], + file_id: str = PATH_VALIDATORS[FileMetadataBase.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -690,7 +690,7 @@ async def close_file( @router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent") async def retrieve_agent( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], include_relationships: list[str] | None = Query( None, description=( @@ -713,7 +713,7 @@ async def retrieve_agent( @router.delete("/{agent_id}", response_model=None, operation_id="delete_agent") async def delete_agent( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -727,7 +727,7 @@ async def delete_agent( @router.get("/{agent_id}/sources", response_model=list[Source], operation_id="list_agent_sources") async def list_agent_sources( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), before: Optional[str] = Query( @@ -758,7 +758,7 @@ async def list_agent_sources( @router.get("/{agent_id}/folders", response_model=list[Source], operation_id="list_agent_folders") async def list_agent_folders( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), before: Optional[str] = Query( @@ -789,7 +789,7 @@ 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 = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], before: Optional[str] = Query( None, description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order" ), @@ -854,7 +854,7 @@ async def list_agent_files( # TODO: remove? can also get with agent blocks @router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory") async def retrieve_agent_memory( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -870,7 +870,7 @@ async def retrieve_agent_memory( @router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block") async def retrieve_block( block_label: str, - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -884,7 +884,7 @@ async def retrieve_block( @router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks") async def list_blocks( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), before: Optional[str] = Query( @@ -917,7 +917,7 @@ async def list_blocks( @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block") async def modify_block( block_label: str, - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], block_update: BlockUpdate = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -939,8 +939,8 @@ async def modify_block( @router.patch("/{agent_id}/core-memory/blocks/attach/{block_id}", response_model=AgentState, operation_id="attach_core_memory_block") async def attach_block( - block_id: str = PATH_VALIDATORS["block"], - agent_id: str = PATH_VALIDATORS["agent"], + block_id: str = PATH_VALIDATORS[BaseBlock.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -953,8 +953,8 @@ async def attach_block( @router.patch("/{agent_id}/core-memory/blocks/detach/{block_id}", response_model=AgentState, operation_id="detach_core_memory_block") async def detach_block( - block_id: str = PATH_VALIDATORS["block"], - agent_id: str = PATH_VALIDATORS["agent"], + block_id: str = PATH_VALIDATORS[BaseBlock.__id_prefix__], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -967,7 +967,7 @@ async def detach_block( @router.get("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="list_passages") async def list_passages( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), after: str | None = Query(None, description="Unique ID of the memory to start the query range at."), before: str | None = Query(None, description="Unique ID of the memory to end the query range at."), @@ -996,7 +996,7 @@ async def list_passages( @router.post("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="create_passage") async def create_passage( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], request: CreateArchivalMemory = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1013,7 +1013,7 @@ async def create_passage( @router.get("/{agent_id}/archival-memory/search", response_model=ArchivalMemorySearchResponse, operation_id="search_archival_memory") async def search_archival_memory( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], query: str = Query(..., description="String to search for using semantic similarity"), tags: Optional[List[str]] = Query(None, description="Optional list of tags to filter search results"), tag_match_mode: Literal["any", "all"] = Query( @@ -1061,7 +1061,7 @@ async def search_archival_memory( @router.delete("/{agent_id}/archival-memory/{memory_id}", response_model=None, operation_id="delete_passage") async def delete_passage( memory_id: str, - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], # memory_id: str = Query(..., description="Unique ID of the memory to be deleted."), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1082,7 +1082,7 @@ AgentMessagesResponse = Annotated[ @router.get("/{agent_id}/messages", response_model=AgentMessagesResponse, operation_id="list_messages") async def list_messages( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), before: Optional[str] = Query( None, description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order" @@ -1127,8 +1127,8 @@ async def list_messages( @router.patch("/{agent_id}/messages/{message_id}", response_model=LettaMessageUnion, operation_id="modify_message") async def modify_message( - agent_id: str = PATH_VALIDATORS["agent"], # backwards compatible. Consider removing for v1 - message_id: str = PATH_VALIDATORS["message"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], # backwards compatible. Consider removing for v1 + message_id: str = PATH_VALIDATORS[BaseMessage.__id_prefix__], request: LettaMessageUpdateUnion = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1151,7 +1151,7 @@ async def modify_message( ) async def send_message( request_obj: Request, # FastAPI Request - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: SyncServer = Depends(get_letta_server), request: LettaRequest = Body(...), headers: HeaderParams = Depends(get_headers), @@ -1278,7 +1278,7 @@ async def send_message( ) async def send_message_streaming( request_obj: Request, # FastAPI Request - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: SyncServer = Depends(get_letta_server), request: LettaStreamingRequest = Body(...), headers: HeaderParams = Depends(get_headers), @@ -1311,7 +1311,7 @@ class CancelAgentRunRequest(BaseModel): @router.post("/{agent_id}/messages/cancel", operation_id="cancel_agent_run") async def cancel_agent_run( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], request: CancelAgentRunRequest = Body(None), server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1487,7 +1487,7 @@ async def _process_message_background( operation_id="create_agent_message_async", ) async def send_message_async( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], server: SyncServer = Depends(get_letta_server), request: LettaAsyncRequest = Body(...), headers: HeaderParams = Depends(get_headers), @@ -1591,7 +1591,7 @@ async def send_message_async( @router.patch("/{agent_id}/reset-messages", response_model=AgentState, operation_id="reset_messages") async def reset_messages( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], add_default_initial_messages: bool = Query(default=False, description="If true, adds the default initial messages after resetting."), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1605,7 +1605,7 @@ async def reset_messages( @router.get("/{agent_id}/groups", response_model=list[Group], operation_id="list_agent_groups") async def list_agent_groups( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], manager_type: str | None = Query(None, description="Manager type to filter groups by"), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1641,7 +1641,7 @@ async def list_agent_groups( operation_id="preview_raw_payload", ) async def preview_raw_payload( - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], request: Union[LettaRequest, LettaStreamingRequest] = Body(...), server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -1687,7 +1687,7 @@ async def preview_raw_payload( @router.post("/{agent_id}/summarize", status_code=204, operation_id="summarize_agent_conversation") async def summarize_agent_conversation( request_obj: Request, # FastAPI Request - agent_id: str = PATH_VALIDATORS["agent"], + agent_id: str = PATH_VALIDATORS[AgentState.__id_prefix__], max_message_length: int = Query(..., description="Maximum number of messages to retain after summarization."), server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), diff --git a/letta/server/rest_api/routers/v1/archives.py b/letta/server/rest_api/routers/v1/archives.py index 2cb24917..f41aa89b 100644 --- a/letta/server/rest_api/routers/v1/archives.py +++ b/letta/server/rest_api/routers/v1/archives.py @@ -3,7 +3,7 @@ from typing import List, Literal, Optional from fastapi import APIRouter, Body, Depends, Query from pydantic import BaseModel -from letta.schemas.archive import Archive as PydanticArchive +from letta.schemas.archive import Archive as PydanticArchive, ArchiveBase from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.server.server import SyncServer from letta.validators import PATH_VALIDATORS @@ -86,7 +86,7 @@ async def list_archives( @router.patch("/{archive_id}", response_model=PydanticArchive, operation_id="modify_archive") async def modify_archive( archive: ArchiveUpdateRequest = Body(...), - archive_id: str = PATH_VALIDATORS["archive"], + archive_id: str = PATH_VALIDATORS[ArchiveBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): diff --git a/letta/server/rest_api/routers/v1/blocks.py b/letta/server/rest_api/routers/v1/blocks.py index 3c75600a..4ccb2dc3 100644 --- a/letta/server/rest_api/routers/v1/blocks.py +++ b/letta/server/rest_api/routers/v1/blocks.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Query from letta.orm.errors import NoResultFound from letta.schemas.agent import AgentState -from letta.schemas.block import Block, BlockUpdate, CreateBlock +from letta.schemas.block import BaseBlock, Block, BlockUpdate, CreateBlock from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.server.server import SyncServer from letta.validators import PATH_VALIDATORS @@ -129,7 +129,7 @@ async def create_block( @router.patch("/{block_id}", response_model=Block, operation_id="modify_block") async def modify_block( - block_id: str = PATH_VALIDATORS["block"], + block_id: str = PATH_VALIDATORS[BaseBlock.__id_prefix__], block_update: BlockUpdate = Body(...), server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -140,7 +140,7 @@ async def modify_block( @router.delete("/{block_id}", operation_id="delete_block") async def delete_block( - block_id: str = PATH_VALIDATORS["block"], + block_id: str = PATH_VALIDATORS[BaseBlock.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -150,7 +150,7 @@ async def delete_block( @router.get("/{block_id}", response_model=Block, operation_id="retrieve_block") async def retrieve_block( - block_id: str = PATH_VALIDATORS["block"], + block_id: str = PATH_VALIDATORS[BaseBlock.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -163,7 +163,7 @@ async def retrieve_block( @router.get("/{block_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_block") async def list_agents_for_block( - block_id: str = PATH_VALIDATORS["block"], + block_id: str = PATH_VALIDATORS[BaseBlock.__id_prefix__], before: Optional[str] = Query( None, description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order", diff --git a/letta/server/rest_api/routers/v1/folders.py b/letta/server/rest_api/routers/v1/folders.py index f5ea753f..2a768d08 100644 --- a/letta/server/rest_api/routers/v1/folders.py +++ b/letta/server/rest_api/routers/v1/folders.py @@ -4,7 +4,7 @@ import tempfile from pathlib import Path from typing import List, Literal, Optional -from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile +from fastapi import APIRouter, Depends, HTTPException, Path as PathParam, Query, UploadFile from starlette import status from starlette.responses import Response @@ -21,10 +21,10 @@ from letta.otel.tracing import trace_method from letta.schemas.agent import AgentState from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.enums import DuplicateFileHandling, FileProcessingStatus -from letta.schemas.file import FileMetadata -from letta.schemas.folder import Folder +from letta.schemas.file import FileMetadata, FileMetadataBase +from letta.schemas.folder import BaseFolder, Folder from letta.schemas.passage import Passage -from letta.schemas.source import Source, SourceCreate, SourceUpdate +from letta.schemas.source import BaseSource, Source, SourceCreate, SourceUpdate from letta.schemas.source_metadata import OrganizationSourcesStats from letta.schemas.user import User from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server @@ -37,7 +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 +from letta.validators import PATH_VALIDATORS, PRIMITIVE_ID_PATTERNS logger = get_logger(__name__) @@ -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 = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -178,7 +178,7 @@ async def create_folder( @router.patch("/{folder_id}", response_model=Folder, operation_id="modify_folder") async def modify_folder( folder: SourceUpdate, - folder_id: str = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), 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 = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), 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 = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), 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 = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), 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 = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), 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 = PATH_VALIDATORS["folder"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), before: Optional[str] = Query( None, description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order", @@ -497,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 = PATH_VALIDATORS["folder"], - file_id: str = PATH_VALIDATORS["file"], + folder_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[BaseFolder.__id_prefix__].pattern), + file_id: str = PathParam(..., pattern=PRIMITIVE_ID_PATTERNS[FileMetadataBase.__id_prefix__].pattern), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): diff --git a/letta/server/rest_api/routers/v1/groups.py b/letta/server/rest_api/routers/v1/groups.py index f59e36b5..f800e477 100644 --- a/letta/server/rest_api/routers/v1/groups.py +++ b/letta/server/rest_api/routers/v1/groups.py @@ -5,10 +5,11 @@ from fastapi.responses import JSONResponse from pydantic import Field from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG -from letta.schemas.group import Group, GroupCreate, GroupUpdate, ManagerType +from letta.schemas.group import Group, GroupBase, GroupCreate, GroupUpdate, ManagerType from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest from letta.schemas.letta_response import LettaResponse +from letta.schemas.message import BaseMessage from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.server.server import SyncServer from letta.validators import PATH_VALIDATORS @@ -69,7 +70,7 @@ async def count_groups( @router.get("/{group_id}", response_model=Group, operation_id="retrieve_group") async def retrieve_group( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -98,7 +99,7 @@ async def create_group( @router.patch("/{group_id}", response_model=Group, operation_id="modify_group") async def modify_group( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], group: GroupUpdate = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -115,7 +116,7 @@ async def modify_group( @router.delete("/{group_id}", response_model=None, operation_id="delete_group") async def delete_group( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -133,7 +134,7 @@ async def delete_group( operation_id="send_group_message", ) async def send_group_message( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], server: SyncServer = Depends(get_letta_server), request: LettaRequest = Body(...), headers: HeaderParams = Depends(get_headers), @@ -171,7 +172,7 @@ async def send_group_message( }, ) async def send_group_message_streaming( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], server: SyncServer = Depends(get_letta_server), request: LettaStreamingRequest = Body(...), headers: HeaderParams = Depends(get_headers), @@ -203,8 +204,8 @@ GroupMessagesResponse = Annotated[ @router.patch("/{group_id}/messages/{message_id}", response_model=LettaMessageUnion, operation_id="modify_group_message") async def modify_group_message( - group_id: str = PATH_VALIDATORS["group"], - message_id: str = PATH_VALIDATORS["message"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], + message_id: str = PATH_VALIDATORS[BaseMessage.__id_prefix__], request: LettaMessageUpdateUnion = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -219,7 +220,7 @@ async def modify_group_message( @router.get("/{group_id}/messages", response_model=GroupMessagesResponse, operation_id="list_group_messages") async def list_group_messages( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], before: Optional[str] = Query( None, description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order", @@ -274,7 +275,7 @@ async def list_group_messages( @router.patch("/{group_id}/reset-messages", response_model=None, operation_id="reset_group_messages") async def reset_group_messages( - group_id: str = PATH_VALIDATORS["group"], + group_id: str = PATH_VALIDATORS[GroupBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): diff --git a/letta/server/rest_api/routers/v1/identities.py b/letta/server/rest_api/routers/v1/identities.py index af861403..d5875af2 100644 --- a/letta/server/rest_api/routers/v1/identities.py +++ b/letta/server/rest_api/routers/v1/identities.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, Body, Depends, Header, Query from letta.orm.errors import NoResultFound, UniqueConstraintViolationError from letta.schemas.agent import AgentState from letta.schemas.block import Block -from letta.schemas.identity import Identity, IdentityCreate, IdentityProperty, IdentityType, IdentityUpdate, IdentityUpsert +from letta.schemas.identity import Identity, IdentityBase, IdentityCreate, IdentityProperty, IdentityType, IdentityUpdate, IdentityUpsert from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.validators import PATH_VALIDATORS @@ -73,7 +73,7 @@ async def count_identities( @router.get("/{identity_id}", tags=["identities"], response_model=Identity, operation_id="retrieve_identity") async def retrieve_identity( - identity_id: str = PATH_VALIDATORS["identity"], + identity_id: str = PATH_VALIDATORS[IdentityBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -109,7 +109,7 @@ async def upsert_identity( @router.patch("/{identity_id}", tags=["identities"], response_model=Identity, operation_id="update_identity") async def modify_identity( - identity_id: str = PATH_VALIDATORS["identity"], + identity_id: str = PATH_VALIDATORS[IdentityBase.__id_prefix__], identity: IdentityUpdate = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -120,7 +120,7 @@ async def modify_identity( @router.put("/{identity_id}/properties", tags=["identities"], operation_id="upsert_identity_properties") async def upsert_identity_properties( - identity_id: str = PATH_VALIDATORS["identity"], + identity_id: str = PATH_VALIDATORS[IdentityBase.__id_prefix__], properties: List[IdentityProperty] = Body(...), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -131,7 +131,7 @@ async def upsert_identity_properties( @router.delete("/{identity_id}", tags=["identities"], operation_id="delete_identity") async def delete_identity( - identity_id: str = PATH_VALIDATORS["identity"], + identity_id: str = PATH_VALIDATORS[IdentityBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -144,7 +144,7 @@ async def delete_identity( @router.get("/{identity_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_identity") async def list_agents_for_identity( - identity_id: str = PATH_VALIDATORS["identity"], + identity_id: str = PATH_VALIDATORS[IdentityBase.__id_prefix__], before: Optional[str] = Query( None, description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order", @@ -177,7 +177,7 @@ async def list_agents_for_identity( @router.get("/{identity_id}/blocks", response_model=List[Block], operation_id="list_blocks_for_identity") async def list_blocks_for_identity( - identity_id: str = PATH_VALIDATORS["identity"], + identity_id: str = PATH_VALIDATORS[IdentityBase.__id_prefix__], before: Optional[str] = Query( None, description="Block ID cursor for pagination. Returns blocks that come before this block ID in the specified sort order", diff --git a/letta/server/rest_api/routers/v1/jobs.py b/letta/server/rest_api/routers/v1/jobs.py index 67f6080b..dfec9f68 100644 --- a/letta/server/rest_api/routers/v1/jobs.py +++ b/letta/server/rest_api/routers/v1/jobs.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, Query from letta.errors import LettaInvalidArgumentError from letta.schemas.enums import JobStatus -from letta.schemas.job import Job +from letta.schemas.job import Job, JobBase from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.server.server import SyncServer from letta.settings import settings @@ -89,7 +89,7 @@ async def list_active_jobs( @router.get("/{job_id}", response_model=Job, operation_id="retrieve_job") async def retrieve_job( - job_id: str = PATH_VALIDATORS["job"], + job_id: str = PATH_VALIDATORS[JobBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): @@ -102,7 +102,7 @@ async def retrieve_job( @router.patch("/{job_id}/cancel", response_model=Job, operation_id="cancel_job") async def cancel_job( - job_id: str = PATH_VALIDATORS["job"], + job_id: str = PATH_VALIDATORS[JobBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): @@ -127,7 +127,7 @@ async def cancel_job( @router.delete("/{job_id}", response_model=Job, operation_id="delete_job") async def delete_job( - job_id: str = PATH_VALIDATORS["job"], + job_id: str = PATH_VALIDATORS[JobBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): diff --git a/letta/server/rest_api/routers/v1/providers.py b/letta/server/rest_api/routers/v1/providers.py index 933abc20..031ce128 100644 --- a/letta/server/rest_api/routers/v1/providers.py +++ b/letta/server/rest_api/routers/v1/providers.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, Query, status from fastapi.responses import JSONResponse from letta.schemas.enums import ProviderType -from letta.schemas.providers import Provider, ProviderCheck, ProviderCreate, ProviderUpdate +from letta.schemas.providers import Provider, ProviderBase, ProviderCheck, ProviderCreate, ProviderUpdate from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.validators import PATH_VALIDATORS @@ -46,7 +46,7 @@ async def list_providers( @router.get("/{provider_id}", response_model=Provider, operation_id="retrieve_provider") async def retrieve_provider( - provider_id: str = PATH_VALIDATORS["provider"], + provider_id: str = PATH_VALIDATORS[ProviderBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): @@ -81,7 +81,7 @@ async def create_provider( @router.patch("/{provider_id}", response_model=Provider, operation_id="modify_provider") async def modify_provider( request: ProviderUpdate = Body(...), - provider_id: str = PATH_VALIDATORS["provider"], + provider_id: str = PATH_VALIDATORS[ProviderBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): @@ -111,7 +111,7 @@ async def check_provider( @router.post("/{provider_id}/check", response_model=None, operation_id="check_existing_provider") async def check_existing_provider( - provider_id: str = PATH_VALIDATORS["provider"], + provider_id: str = PATH_VALIDATORS[ProviderBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): @@ -136,7 +136,7 @@ async def check_existing_provider( @router.delete("/{provider_id}", response_model=None, operation_id="delete_provider") async def delete_provider( - provider_id: str = PATH_VALIDATORS["provider"], + provider_id: str = PATH_VALIDATORS[ProviderBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: "SyncServer" = Depends(get_letta_server), ): diff --git a/letta/server/rest_api/routers/v1/sandbox_configs.py b/letta/server/rest_api/routers/v1/sandbox_configs.py index 4356c2dd..c4d100d9 100644 --- a/letta/server/rest_api/routers/v1/sandbox_configs.py +++ b/letta/server/rest_api/routers/v1/sandbox_configs.py @@ -15,6 +15,7 @@ from letta.schemas.environment_variables import ( from letta.schemas.sandbox_config import ( LocalSandboxConfig, SandboxConfig as PydanticSandboxConfig, + SandboxConfigBase, SandboxConfigCreate, SandboxConfigUpdate, ) @@ -89,7 +90,7 @@ async def create_custom_local_sandbox_config( @router.patch("/{sandbox_config_id}", response_model=PydanticSandboxConfig) async def update_sandbox_config( config_update: SandboxConfigUpdate, - sandbox_config_id: str = PATH_VALIDATORS["sandbox"], + sandbox_config_id: str = PATH_VALIDATORS[SandboxConfigBase.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -99,7 +100,7 @@ async def update_sandbox_config( @router.delete("/{sandbox_config_id}", status_code=204) async def delete_sandbox_config( - sandbox_config_id: str = PATH_VALIDATORS["sandbox"], + sandbox_config_id: str = PATH_VALIDATORS[SandboxConfigBase.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -159,7 +160,7 @@ async def force_recreate_local_sandbox_venv( @router.post("/{sandbox_config_id}/environment-variable", response_model=PydanticEnvVar) async def create_sandbox_env_var( env_var_create: SandboxEnvironmentVariableCreate, - sandbox_config_id: str = PATH_VALIDATORS["sandbox"], + sandbox_config_id: str = PATH_VALIDATORS[SandboxConfigBase.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -190,7 +191,7 @@ async def delete_sandbox_env_var( @router.get("/{sandbox_config_id}/environment-variable", response_model=List[PydanticEnvVar]) async def list_sandbox_env_vars( - sandbox_config_id: str = PATH_VALIDATORS["sandbox"], + sandbox_config_id: str = PATH_VALIDATORS[SandboxConfigBase.__id_prefix__], limit: int = Query(1000, description="Number of results to return"), after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"), server: SyncServer = Depends(get_letta_server), diff --git a/letta/server/rest_api/routers/v1/sources.py b/letta/server/rest_api/routers/v1/sources.py index 8091a2d3..771285a3 100644 --- a/letta/server/rest_api/routers/v1/sources.py +++ b/letta/server/rest_api/routers/v1/sources.py @@ -21,9 +21,9 @@ from letta.otel.tracing import trace_method from letta.schemas.agent import AgentState from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.enums import DuplicateFileHandling, FileProcessingStatus -from letta.schemas.file import FileMetadata +from letta.schemas.file import FileMetadata, FileMetadataBase from letta.schemas.passage import Passage -from letta.schemas.source import Source, SourceCreate, SourceUpdate +from letta.schemas.source import BaseSource, Source, SourceCreate, SourceUpdate from letta.schemas.source_metadata import OrganizationSourcesStats from letta.schemas.user import User from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server @@ -60,7 +60,7 @@ async def count_sources( @router.get("/{source_id}", response_model=Source, operation_id="retrieve_source", deprecated=True) async def retrieve_source( - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -158,7 +158,7 @@ async def create_source( @router.patch("/{source_id}", response_model=Source, operation_id="modify_source", deprecated=True) async def modify_source( source: SourceUpdate, - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -173,7 +173,7 @@ async def modify_source( @router.delete("/{source_id}", response_model=None, operation_id="delete_source", deprecated=True) async def delete_source( - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -209,7 +209,7 @@ async def delete_source( @router.post("/{source_id}/upload", response_model=FileMetadata, operation_id="upload_file_to_source", deprecated=True) async def upload_file_to_source( file: UploadFile, - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], 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), @@ -324,7 +324,7 @@ async def upload_file_to_source( @router.get("/{source_id}/agents", response_model=List[str], operation_id="get_agents_for_source", deprecated=True) async def get_agents_for_source( - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -337,7 +337,7 @@ async def get_agents_for_source( @router.get("/{source_id}/passages", response_model=List[Passage], operation_id="list_source_passages", deprecated=True) async def list_source_passages( - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."), before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."), limit: int = Query(100, description="Maximum number of messages to retrieve."), @@ -359,7 +359,7 @@ async def list_source_passages( @router.get("/{source_id}/files", response_model=List[FileMetadata], operation_id="list_source_files", deprecated=True) async def list_source_files( - source_id: str = PATH_VALIDATORS["source"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], limit: int = Query(1000, description="Number of files to return"), after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"), include_content: bool = Query(False, description="Whether to include full file content"), @@ -387,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 = PATH_VALIDATORS["source"], - file_id: str = PATH_VALIDATORS["file"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], + file_id: str = PATH_VALIDATORS[FileMetadataBase.__id_prefix__], include_content: bool = Query(False, description="Whether to include full file content"), server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), @@ -413,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 = PATH_VALIDATORS["source"], - file_id: str = PATH_VALIDATORS["file"], + source_id: str = PATH_VALIDATORS[BaseSource.__id_prefix__], + file_id: str = PATH_VALIDATORS[FileMetadataBase.__id_prefix__], server: "SyncServer" = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): diff --git a/letta/server/rest_api/routers/v1/steps.py b/letta/server/rest_api/routers/v1/steps.py index 2f2087f8..310a061e 100644 --- a/letta/server/rest_api/routers/v1/steps.py +++ b/letta/server/rest_api/routers/v1/steps.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, Field from letta.schemas.letta_message import LettaMessageUnion from letta.schemas.message import Message from letta.schemas.provider_trace import ProviderTrace -from letta.schemas.step import Step +from letta.schemas.step import Step, StepBase from letta.schemas.step_metrics import StepMetrics from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.server.server import SyncServer @@ -70,7 +70,7 @@ async def list_steps( @router.get("/{step_id}", response_model=Step, operation_id="retrieve_step") async def retrieve_step( - step_id: str = PATH_VALIDATORS["step"], + step_id: str = PATH_VALIDATORS[StepBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: SyncServer = Depends(get_letta_server), ): @@ -83,7 +83,7 @@ async def retrieve_step( @router.get("/{step_id}/metrics", response_model=StepMetrics, operation_id="retrieve_metrics_for_step") async def retrieve_metrics_for_step( - step_id: str = PATH_VALIDATORS["step"], + step_id: str = PATH_VALIDATORS[StepBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: SyncServer = Depends(get_letta_server), ): @@ -96,7 +96,7 @@ async def retrieve_metrics_for_step( @router.get("/{step_id}/trace", response_model=Optional[ProviderTrace], operation_id="retrieve_trace_for_step") async def retrieve_trace_for_step( - step_id: str = PATH_VALIDATORS["step"], + step_id: str = PATH_VALIDATORS[StepBase.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -120,7 +120,7 @@ class ModifyFeedbackRequest(BaseModel): @router.patch("/{step_id}/feedback", response_model=Step, operation_id="modify_feedback_for_step") async def modify_feedback_for_step( request: ModifyFeedbackRequest = Body(...), - step_id: str = PATH_VALIDATORS["step"], + step_id: str = PATH_VALIDATORS[StepBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: SyncServer = Depends(get_letta_server), ): @@ -133,7 +133,7 @@ async def modify_feedback_for_step( @router.get("/{step_id}/messages", response_model=List[LettaMessageUnion], operation_id="list_messages_for_step") async def list_messages_for_step( - step_id: str = PATH_VALIDATORS["step"], + step_id: str = PATH_VALIDATORS[StepBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: SyncServer = Depends(get_letta_server), before: Optional[str] = Query( @@ -161,7 +161,7 @@ async def list_messages_for_step( @router.patch("/{step_id}/transaction/{transaction_id}", response_model=Step, operation_id="update_step_transaction_id") async def update_step_transaction_id( transaction_id: str, - step_id: str = PATH_VALIDATORS["step"], + step_id: str = PATH_VALIDATORS[StepBase.__id_prefix__], headers: HeaderParams = Depends(get_headers), server: SyncServer = Depends(get_letta_server), ): diff --git a/letta/server/rest_api/routers/v1/tools.py b/letta/server/rest_api/routers/v1/tools.py index c0e8642c..91d68c66 100644 --- a/letta/server/rest_api/routers/v1/tools.py +++ b/letta/server/rest_api/routers/v1/tools.py @@ -30,7 +30,7 @@ from letta.schemas.letta_message_content import TextContent from letta.schemas.mcp import UpdateSSEMCPServer, UpdateStdioMCPServer, UpdateStreamableHTTPMCPServer from letta.schemas.message import Message from letta.schemas.pip_requirement import PipRequirement -from letta.schemas.tool import Tool, ToolCreate, ToolRunFromSource, ToolUpdate +from letta.schemas.tool import BaseTool, Tool, ToolCreate, ToolRunFromSource, ToolUpdate from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode from letta.server.server import SyncServer @@ -47,7 +47,7 @@ logger = get_logger(__name__) @router.delete("/{tool_id}", operation_id="delete_tool") async def delete_tool( - tool_id: str = PATH_VALIDATORS["tool"], + tool_id: str = PATH_VALIDATORS[BaseTool.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -150,7 +150,7 @@ async def count_tools( @router.get("/{tool_id}", response_model=Tool, operation_id="retrieve_tool") async def retrieve_tool( - tool_id: str = PATH_VALIDATORS["tool"], + tool_id: str = PATH_VALIDATORS[BaseTool.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): @@ -299,7 +299,7 @@ async def upsert_tool( @router.patch("/{tool_id}", response_model=Tool, operation_id="modify_tool") async def modify_tool( request: ToolUpdate = Body(...), - tool_id: str = PATH_VALIDATORS["tool"], + tool_id: str = PATH_VALIDATORS[BaseTool.__id_prefix__], server: SyncServer = Depends(get_letta_server), headers: HeaderParams = Depends(get_headers), ): diff --git a/letta/validators.py b/letta/validators.py index a711693d..1c927e68 100644 --- a/letta/validators.py +++ b/letta/validators.py @@ -2,25 +2,42 @@ import re from fastapi import Path +from letta.schemas.agent import AgentState +from letta.schemas.archive import ArchiveBase +from letta.schemas.block import BaseBlock +from letta.schemas.file import FileMetadataBase +from letta.schemas.folder import BaseFolder +from letta.schemas.group import GroupBase +from letta.schemas.identity import IdentityBase +from letta.schemas.job import JobBase +from letta.schemas.message import BaseMessage +from letta.schemas.providers import ProviderBase +from letta.schemas.run import RunBase +from letta.schemas.sandbox_config import SandboxConfigBase +from letta.schemas.source import BaseSource +from letta.schemas.step import StepBase +from letta.schemas.tool import BaseTool + # TODO: extract this list from routers/v1/__init__.py and ROUTERS primitives = [ - "agent", - "message", - "run", - "job", - "group", - "block", - "file", - "folder", - "source", - "tool", - "archive", - "provider", - "sandbox", - "step", - "identity", + AgentState.__id_prefix__, + BaseMessage.__id_prefix__, + RunBase.__id_prefix__, + JobBase.__id_prefix__, + GroupBase.__id_prefix__, + BaseBlock.__id_prefix__, + FileMetadataBase.__id_prefix__, + BaseFolder.__id_prefix__, + BaseSource.__id_prefix__, + BaseTool.__id_prefix__, + ArchiveBase.__id_prefix__, + ProviderBase.__id_prefix__, + SandboxConfigBase.__id_prefix__, + StepBase.__id_prefix__, + IdentityBase.__id_prefix__, ] + PRIMITIVE_ID_PATTERNS = { # f-string interpolation gets confused because of the regex's required curly braces {} primitive: re.compile("^" + primitive + "-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")