letta-nightly 0.11.7.dev20250915104130__py3-none-any.whl → 0.11.7.dev20250917104122__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. letta/__init__.py +10 -2
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +0 -1
  4. letta/agent.py +1 -1
  5. letta/agents/letta_agent.py +1 -4
  6. letta/agents/letta_agent_v2.py +2 -1
  7. letta/agents/voice_agent.py +1 -1
  8. letta/functions/function_sets/multi_agent.py +1 -1
  9. letta/functions/helpers.py +1 -1
  10. letta/helpers/converters.py +8 -2
  11. letta/helpers/crypto_utils.py +144 -0
  12. letta/llm_api/llm_api_tools.py +0 -1
  13. letta/llm_api/llm_client_base.py +0 -2
  14. letta/orm/__init__.py +1 -0
  15. letta/orm/agent.py +5 -1
  16. letta/orm/job.py +3 -1
  17. letta/orm/mcp_oauth.py +6 -0
  18. letta/orm/mcp_server.py +7 -1
  19. letta/orm/sqlalchemy_base.py +2 -1
  20. letta/prompts/gpt_system.py +13 -15
  21. letta/prompts/system_prompts/__init__.py +27 -0
  22. letta/prompts/{system/memgpt_chat.txt → system_prompts/memgpt_chat.py} +2 -0
  23. letta/prompts/{system/memgpt_generate_tool.txt → system_prompts/memgpt_generate_tool.py} +4 -2
  24. letta/prompts/{system/memgpt_v2_chat.txt → system_prompts/memgpt_v2_chat.py} +2 -0
  25. letta/prompts/{system/react.txt → system_prompts/react.py} +2 -0
  26. letta/prompts/{system/sleeptime_doc_ingest.txt → system_prompts/sleeptime_doc_ingest.py} +2 -0
  27. letta/prompts/{system/sleeptime_v2.txt → system_prompts/sleeptime_v2.py} +2 -0
  28. letta/prompts/{system/summary_system_prompt.txt → system_prompts/summary_system_prompt.py} +2 -0
  29. letta/prompts/{system/voice_chat.txt → system_prompts/voice_chat.py} +2 -0
  30. letta/prompts/{system/voice_sleeptime.txt → system_prompts/voice_sleeptime.py} +2 -0
  31. letta/prompts/{system/workflow.txt → system_prompts/workflow.py} +2 -0
  32. letta/schemas/agent.py +10 -7
  33. letta/schemas/job.py +10 -0
  34. letta/schemas/mcp.py +146 -6
  35. letta/schemas/provider_trace.py +0 -2
  36. letta/schemas/run.py +2 -0
  37. letta/schemas/secret.py +378 -0
  38. letta/serialize_schemas/marshmallow_agent.py +4 -0
  39. letta/server/rest_api/dependencies.py +37 -0
  40. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +4 -3
  41. letta/server/rest_api/routers/v1/__init__.py +2 -0
  42. letta/server/rest_api/routers/v1/agents.py +115 -107
  43. letta/server/rest_api/routers/v1/archives.py +113 -0
  44. letta/server/rest_api/routers/v1/blocks.py +44 -20
  45. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  46. letta/server/rest_api/routers/v1/folders.py +107 -47
  47. letta/server/rest_api/routers/v1/groups.py +52 -32
  48. letta/server/rest_api/routers/v1/identities.py +110 -21
  49. letta/server/rest_api/routers/v1/internal_templates.py +28 -13
  50. letta/server/rest_api/routers/v1/jobs.py +19 -14
  51. letta/server/rest_api/routers/v1/llms.py +6 -8
  52. letta/server/rest_api/routers/v1/messages.py +14 -14
  53. letta/server/rest_api/routers/v1/organizations.py +1 -1
  54. letta/server/rest_api/routers/v1/providers.py +40 -16
  55. letta/server/rest_api/routers/v1/runs.py +28 -20
  56. letta/server/rest_api/routers/v1/sandbox_configs.py +25 -25
  57. letta/server/rest_api/routers/v1/sources.py +44 -45
  58. letta/server/rest_api/routers/v1/steps.py +27 -25
  59. letta/server/rest_api/routers/v1/tags.py +11 -7
  60. letta/server/rest_api/routers/v1/telemetry.py +11 -6
  61. letta/server/rest_api/routers/v1/tools.py +78 -80
  62. letta/server/rest_api/routers/v1/users.py +1 -1
  63. letta/server/rest_api/routers/v1/voice.py +6 -5
  64. letta/server/rest_api/utils.py +1 -18
  65. letta/services/agent_manager.py +17 -9
  66. letta/services/agent_serialization_manager.py +11 -3
  67. letta/services/archive_manager.py +73 -0
  68. letta/services/file_manager.py +6 -0
  69. letta/services/group_manager.py +2 -1
  70. letta/services/helpers/agent_manager_helper.py +6 -1
  71. letta/services/identity_manager.py +67 -0
  72. letta/services/job_manager.py +18 -2
  73. letta/services/mcp_manager.py +198 -82
  74. letta/services/provider_manager.py +14 -1
  75. letta/services/source_manager.py +11 -1
  76. letta/services/telemetry_manager.py +2 -0
  77. letta/services/tool_executor/composio_tool_executor.py +1 -1
  78. letta/services/tool_manager.py +46 -9
  79. letta/services/tool_sandbox/base.py +2 -3
  80. letta/utils.py +4 -2
  81. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/METADATA +5 -2
  82. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/RECORD +85 -94
  83. letta/prompts/system/memgpt_base.txt +0 -54
  84. letta/prompts/system/memgpt_chat_compressed.txt +0 -13
  85. letta/prompts/system/memgpt_chat_fstring.txt +0 -51
  86. letta/prompts/system/memgpt_convo_only.txt +0 -12
  87. letta/prompts/system/memgpt_doc.txt +0 -50
  88. letta/prompts/system/memgpt_gpt35_extralong.txt +0 -53
  89. letta/prompts/system/memgpt_intuitive_knowledge.txt +0 -31
  90. letta/prompts/system/memgpt_memory_only.txt +0 -29
  91. letta/prompts/system/memgpt_modified_chat.txt +0 -23
  92. letta/prompts/system/memgpt_modified_o1.txt +0 -31
  93. letta/prompts/system/memgpt_offline_memory.txt +0 -23
  94. letta/prompts/system/memgpt_offline_memory_chat.txt +0 -35
  95. letta/prompts/system/memgpt_sleeptime_chat.txt +0 -52
  96. letta/prompts/system/sleeptime.txt +0 -37
  97. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/WHEEL +0 -0
  98. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/entry_points.txt +0 -0
  99. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,113 @@
1
+ from typing import List, Literal, Optional
2
+
3
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
+ from pydantic import BaseModel
5
+
6
+ from letta.orm.errors import NoResultFound
7
+ from letta.schemas.archive import Archive as PydanticArchive
8
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
9
+ from letta.server.server import SyncServer
10
+
11
+ router = APIRouter(prefix="/archives", tags=["archives"])
12
+
13
+
14
+ class ArchiveCreateRequest(BaseModel):
15
+ """Request model for creating an archive.
16
+
17
+ Intentionally excludes vector_db_provider. These are derived internally (vector DB provider from env).
18
+ """
19
+
20
+ name: str
21
+ description: Optional[str] = None
22
+
23
+
24
+ class ArchiveUpdateRequest(BaseModel):
25
+ """Request model for updating an archive (partial).
26
+
27
+ Supports updating only name and description.
28
+ """
29
+
30
+ name: Optional[str] = None
31
+ description: Optional[str] = None
32
+
33
+
34
+ @router.post("/", response_model=PydanticArchive, operation_id="create_archive")
35
+ async def create_archive(
36
+ archive: ArchiveCreateRequest = Body(...),
37
+ server: "SyncServer" = Depends(get_letta_server),
38
+ headers: HeaderParams = Depends(get_headers),
39
+ ):
40
+ """
41
+ Create a new archive.
42
+ """
43
+ try:
44
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
45
+ return await server.archive_manager.create_archive_async(
46
+ name=archive.name,
47
+ description=archive.description,
48
+ actor=actor,
49
+ )
50
+ except Exception as e:
51
+ raise HTTPException(status_code=500, detail=str(e))
52
+
53
+
54
+ @router.get("/", response_model=List[PydanticArchive], operation_id="list_archives")
55
+ async def list_archives(
56
+ before: Optional[str] = Query(
57
+ None,
58
+ description="Archive ID cursor for pagination. Returns archives that come before this archive ID in the specified sort order",
59
+ ),
60
+ after: Optional[str] = Query(
61
+ None,
62
+ description="Archive ID cursor for pagination. Returns archives that come after this archive ID in the specified sort order",
63
+ ),
64
+ limit: Optional[int] = Query(50, description="Maximum number of archives to return"),
65
+ order: Literal["asc", "desc"] = Query(
66
+ "desc", description="Sort order for archives by creation time. 'asc' for oldest first, 'desc' for newest first"
67
+ ),
68
+ name: Optional[str] = Query(None, description="Filter by archive name (exact match)"),
69
+ agent_id: Optional[str] = Query(None, description="Only archives attached to this agent ID"),
70
+ server: "SyncServer" = Depends(get_letta_server),
71
+ headers: HeaderParams = Depends(get_headers),
72
+ ):
73
+ """
74
+ Get a list of all archives for the current organization with optional filters and pagination.
75
+ """
76
+ try:
77
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
78
+ archives = await server.archive_manager.list_archives_async(
79
+ actor=actor,
80
+ before=before,
81
+ after=after,
82
+ limit=limit,
83
+ ascending=(order == "asc"),
84
+ name=name,
85
+ agent_id=agent_id,
86
+ )
87
+ return archives
88
+ except Exception as e:
89
+ raise HTTPException(status_code=500, detail=str(e))
90
+
91
+
92
+ @router.patch("/{archive_id}", response_model=PydanticArchive, operation_id="modify_archive")
93
+ async def modify_archive(
94
+ archive_id: str,
95
+ archive: ArchiveUpdateRequest = Body(...),
96
+ server: "SyncServer" = Depends(get_letta_server),
97
+ headers: HeaderParams = Depends(get_headers),
98
+ ):
99
+ """
100
+ Update an existing archive's name and/or description.
101
+ """
102
+ try:
103
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
104
+ return await server.archive_manager.update_archive_async(
105
+ archive_id=archive_id,
106
+ name=archive.name,
107
+ description=archive.description,
108
+ actor=actor,
109
+ )
110
+ except NoResultFound as e:
111
+ raise HTTPException(status_code=404, detail=str(e))
112
+ except Exception as e:
113
+ raise HTTPException(status_code=500, detail=str(e))
@@ -1,11 +1,11 @@
1
- from typing import TYPE_CHECKING, List, Optional
1
+ from typing import TYPE_CHECKING, List, Literal, Optional
2
2
 
3
- from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
3
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
 
5
5
  from letta.orm.errors import NoResultFound
6
6
  from letta.schemas.agent import AgentState
7
7
  from letta.schemas.block import Block, BlockUpdate, CreateBlock
8
- from letta.server.rest_api.utils import get_letta_server
8
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
9
9
  from letta.server.server import SyncServer
10
10
 
11
11
  if TYPE_CHECKING:
@@ -26,12 +26,16 @@ async def list_blocks(
26
26
  limit: Optional[int] = Query(50, description="Number of blocks to return"),
27
27
  before: Optional[str] = Query(
28
28
  None,
29
- description="Cursor for pagination. If provided, returns blocks before this cursor.",
29
+ description="Block ID cursor for pagination. Returns blocks that come before this block ID in the specified sort order",
30
30
  ),
31
31
  after: Optional[str] = Query(
32
32
  None,
33
- description="Cursor for pagination. If provided, returns blocks after this cursor.",
33
+ description="Block ID cursor for pagination. Returns blocks that come after this block ID in the specified sort order",
34
34
  ),
35
+ order: Literal["asc", "desc"] = Query(
36
+ "asc", description="Sort order for blocks by creation time. 'asc' for oldest first, 'desc' for newest first"
37
+ ),
38
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
35
39
  label_search: Optional[str] = Query(
36
40
  None,
37
41
  description=("Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."),
@@ -74,9 +78,9 @@ async def list_blocks(
74
78
  description="If set to True, include blocks marked as hidden in the results.",
75
79
  ),
76
80
  server: SyncServer = Depends(get_letta_server),
77
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
81
+ headers: HeaderParams = Depends(get_headers),
78
82
  ):
79
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
83
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
80
84
  return await server.block_manager.get_blocks_async(
81
85
  actor=actor,
82
86
  label=label,
@@ -94,6 +98,7 @@ async def list_blocks(
94
98
  connected_to_agents_count_eq=connected_to_agents_count_eq,
95
99
  limit=limit,
96
100
  after=after,
101
+ ascending=(order == "asc"),
97
102
  show_hidden_blocks=show_hidden_blocks,
98
103
  )
99
104
 
@@ -101,12 +106,12 @@ async def list_blocks(
101
106
  @router.get("/count", response_model=int, operation_id="count_blocks")
102
107
  async def count_blocks(
103
108
  server: SyncServer = Depends(get_letta_server),
104
- actor_id: Optional[str] = Header(None, alias="user_id"),
109
+ headers: HeaderParams = Depends(get_headers),
105
110
  ):
106
111
  """
107
112
  Count all blocks created by a user.
108
113
  """
109
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
114
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
110
115
  return await server.block_manager.size_async(actor=actor)
111
116
 
112
117
 
@@ -114,9 +119,9 @@ async def count_blocks(
114
119
  async def create_block(
115
120
  create_block: CreateBlock = Body(...),
116
121
  server: SyncServer = Depends(get_letta_server),
117
- actor_id: Optional[str] = Header(None, alias="user_id"),
122
+ headers: HeaderParams = Depends(get_headers),
118
123
  ):
119
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
124
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
120
125
  block = Block(**create_block.model_dump())
121
126
  return await server.block_manager.create_or_update_block_async(actor=actor, block=block)
122
127
 
@@ -126,9 +131,9 @@ async def modify_block(
126
131
  block_id: str,
127
132
  block_update: BlockUpdate = Body(...),
128
133
  server: SyncServer = Depends(get_letta_server),
129
- actor_id: Optional[str] = Header(None, alias="user_id"),
134
+ headers: HeaderParams = Depends(get_headers),
130
135
  ):
131
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
136
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
132
137
  return await server.block_manager.update_block_async(block_id=block_id, block_update=block_update, actor=actor)
133
138
 
134
139
 
@@ -136,9 +141,9 @@ async def modify_block(
136
141
  async def delete_block(
137
142
  block_id: str,
138
143
  server: SyncServer = Depends(get_letta_server),
139
- actor_id: Optional[str] = Header(None, alias="user_id"),
144
+ headers: HeaderParams = Depends(get_headers),
140
145
  ):
141
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
146
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
142
147
  await server.block_manager.delete_block_async(block_id=block_id, actor=actor)
143
148
 
144
149
 
@@ -146,9 +151,9 @@ async def delete_block(
146
151
  async def retrieve_block(
147
152
  block_id: str,
148
153
  server: SyncServer = Depends(get_letta_server),
149
- actor_id: Optional[str] = Header(None, alias="user_id"),
154
+ headers: HeaderParams = Depends(get_headers),
150
155
  ):
151
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
156
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
152
157
  try:
153
158
  block = await server.block_manager.get_block_by_id_async(block_id=block_id, actor=actor)
154
159
  if block is None:
@@ -161,6 +166,19 @@ async def retrieve_block(
161
166
  @router.get("/{block_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_block")
162
167
  async def list_agents_for_block(
163
168
  block_id: str,
169
+ before: Optional[str] = Query(
170
+ None,
171
+ description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order",
172
+ ),
173
+ after: Optional[str] = Query(
174
+ None,
175
+ description="Agent ID cursor for pagination. Returns agents that come after this agent ID in the specified sort order",
176
+ ),
177
+ limit: Optional[int] = Query(50, description="Maximum number of agents to return"),
178
+ order: Literal["asc", "desc"] = Query(
179
+ "desc", description="Sort order for agents by creation time. 'asc' for oldest first, 'desc' for newest first"
180
+ ),
181
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
164
182
  include_relationships: list[str] | None = Query(
165
183
  None,
166
184
  description=(
@@ -170,16 +188,22 @@ async def list_agents_for_block(
170
188
  ),
171
189
  ),
172
190
  server: SyncServer = Depends(get_letta_server),
173
- actor_id: Optional[str] = Header(None, alias="user_id"),
191
+ headers: HeaderParams = Depends(get_headers),
174
192
  ):
175
193
  """
176
194
  Retrieves all agents associated with the specified block.
177
195
  Raises a 404 if the block does not exist.
178
196
  """
179
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
197
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
180
198
  try:
181
199
  agents = await server.block_manager.get_agents_for_block_async(
182
- block_id=block_id, include_relationships=include_relationships, actor=actor
200
+ block_id=block_id,
201
+ before=before,
202
+ after=after,
203
+ limit=limit,
204
+ ascending=(order == "asc"),
205
+ include_relationships=include_relationships,
206
+ actor=actor,
183
207
  )
184
208
  return agents
185
209
  except NoResultFound:
@@ -2,7 +2,7 @@ from typing import Optional
2
2
 
3
3
  from fastapi import APIRouter, Depends, Header
4
4
 
5
- from letta.server.rest_api.utils import get_letta_server
5
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
6
6
  from letta.server.server import SyncServer
7
7
 
8
8
  router = APIRouter(prefix="/embeddings", tags=["embeddings"])
@@ -11,11 +11,11 @@ router = APIRouter(prefix="/embeddings", tags=["embeddings"])
11
11
  @router.get("/total_storage_size", response_model=float, operation_id="get_total_storage_size")
12
12
  async def get_embeddings_total_storage_size(
13
13
  server: SyncServer = Depends(get_letta_server),
14
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
14
+ headers: HeaderParams = Depends(get_headers),
15
15
  storage_unit: Optional[str] = Header("GB", alias="storage_unit"), # Extract storage unit from header, default to GB
16
16
  ):
17
17
  """
18
18
  Get the total size of all embeddings in the database for a user in the storage unit given.
19
19
  """
20
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
20
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
21
21
  return await server.passage_manager.estimate_embeddings_size_async(actor=actor, storage_unit=storage_unit)
@@ -1,11 +1,10 @@
1
- import asyncio
2
1
  import mimetypes
3
2
  import os
4
3
  import tempfile
5
4
  from pathlib import Path
6
- from typing import List, Optional
5
+ from typing import List, Literal, Optional
7
6
 
8
- from fastapi import APIRouter, Depends, Header, HTTPException, Query, UploadFile
7
+ from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile
9
8
  from starlette import status
10
9
  from starlette.responses import Response
11
10
 
@@ -27,7 +26,7 @@ from letta.schemas.passage import Passage
27
26
  from letta.schemas.source import Source, SourceCreate, SourceUpdate
28
27
  from letta.schemas.source_metadata import OrganizationSourcesStats
29
28
  from letta.schemas.user import User
30
- from letta.server.rest_api.utils import get_letta_server
29
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
31
30
  from letta.server.server import SyncServer
32
31
  from letta.services.file_processor.embedder.openai_embedder import OpenAIEmbedder
33
32
  from letta.services.file_processor.embedder.pinecone_embedder import PineconeEmbedder
@@ -50,12 +49,12 @@ router = APIRouter(prefix="/folders", tags=["folders"])
50
49
  @router.get("/count", response_model=int, operation_id="count_folders")
51
50
  async def count_folders(
52
51
  server: "SyncServer" = Depends(get_letta_server),
53
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
52
+ headers: HeaderParams = Depends(get_headers),
54
53
  ):
55
54
  """
56
55
  Count all data folders created by a user.
57
56
  """
58
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
57
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
59
58
  return await server.source_manager.size_async(actor=actor)
60
59
 
61
60
 
@@ -63,12 +62,12 @@ async def count_folders(
63
62
  async def retrieve_folder(
64
63
  folder_id: str,
65
64
  server: "SyncServer" = Depends(get_letta_server),
66
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
65
+ headers: HeaderParams = Depends(get_headers),
67
66
  ):
68
67
  """
69
68
  Get a folder by ID
70
69
  """
71
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
70
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
72
71
 
73
72
  folder = await server.source_manager.get_source_by_id(source_id=folder_id, actor=actor)
74
73
  if not folder:
@@ -76,16 +75,19 @@ async def retrieve_folder(
76
75
  return folder
77
76
 
78
77
 
79
- @router.get("/name/{folder_name}", response_model=str, operation_id="get_folder_id_by_name")
80
- async def get_folder_id_by_name(
78
+ @router.get("/name/{folder_name}", response_model=str, operation_id="get_folder_by_name", deprecated=True)
79
+ async def get_folder_by_name(
81
80
  folder_name: str,
82
81
  server: "SyncServer" = Depends(get_letta_server),
83
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
82
+ headers: HeaderParams = Depends(get_headers),
84
83
  ):
85
84
  """
86
- Get a folder by name
85
+ **Deprecated**: Please use the list endpoint `GET /v1/folders?name=` instead.
86
+
87
+
88
+ Get a folder by name.
87
89
  """
88
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
90
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
89
91
 
90
92
  folder = await server.source_manager.get_source_by_name(source_name=folder_name, actor=actor)
91
93
  if not folder:
@@ -93,10 +95,10 @@ async def get_folder_id_by_name(
93
95
  return folder.id
94
96
 
95
97
 
96
- @router.get("/metadata", response_model=OrganizationSourcesStats, operation_id="get_folders_metadata")
97
- async def get_folders_metadata(
98
+ @router.get("/metadata", response_model=OrganizationSourcesStats, operation_id="retrieve_metadata")
99
+ async def retrieve_metadata(
98
100
  server: "SyncServer" = Depends(get_letta_server),
99
- actor_id: Optional[str] = Header(None, alias="user_id"),
101
+ headers: HeaderParams = Depends(get_headers),
100
102
  include_detailed_per_source_metadata: bool = False,
101
103
  ):
102
104
  """
@@ -108,7 +110,7 @@ async def get_folders_metadata(
108
110
  - Total size of all files
109
111
  - Per-source breakdown with file details (file_name, file_size per file) if include_detailed_per_source_metadata is True
110
112
  """
111
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
113
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
112
114
  return await server.file_manager.get_organization_sources_metadata(
113
115
  actor=actor, include_detailed_per_source_metadata=include_detailed_per_source_metadata
114
116
  )
@@ -116,26 +118,40 @@ async def get_folders_metadata(
116
118
 
117
119
  @router.get("/", response_model=List[Folder], operation_id="list_folders")
118
120
  async def list_folders(
121
+ before: Optional[str] = Query(
122
+ None, description="Folder ID cursor for pagination. Returns folders that come before this folder ID in the specified sort order"
123
+ ),
124
+ after: Optional[str] = Query(
125
+ None, description="Folder ID cursor for pagination. Returns folders that come after this folder ID in the specified sort order"
126
+ ),
127
+ limit: Optional[int] = Query(50, description="Maximum number of folders to return"),
128
+ order: Literal["asc", "desc"] = Query(
129
+ "asc", description="Sort order for folders by creation time. 'asc' for oldest first, 'desc' for newest first"
130
+ ),
131
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
132
+ name: Optional[str] = Query(None, description="Folder name to filter by"),
119
133
  server: "SyncServer" = Depends(get_letta_server),
120
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
134
+ headers: HeaderParams = Depends(get_headers),
121
135
  ):
122
136
  """
123
137
  List all data folders created by a user.
124
138
  """
125
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
126
- return await server.source_manager.list_sources(actor=actor)
139
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
140
+ return await server.source_manager.list_sources(
141
+ actor=actor, before=before, after=after, limit=limit, ascending=(order == "asc"), name=name
142
+ )
127
143
 
128
144
 
129
145
  @router.post("/", response_model=Folder, operation_id="create_folder")
130
146
  async def create_folder(
131
147
  folder_create: SourceCreate,
132
148
  server: "SyncServer" = Depends(get_letta_server),
133
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
149
+ headers: HeaderParams = Depends(get_headers),
134
150
  ):
135
151
  """
136
152
  Create a new data folder.
137
153
  """
138
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
154
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
139
155
 
140
156
  # TODO: need to asyncify this
141
157
  if not folder_create.embedding_config:
@@ -165,13 +181,13 @@ async def modify_folder(
165
181
  folder_id: str,
166
182
  folder: SourceUpdate,
167
183
  server: "SyncServer" = Depends(get_letta_server),
168
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
184
+ headers: HeaderParams = Depends(get_headers),
169
185
  ):
170
186
  """
171
187
  Update the name or documentation of an existing data folder.
172
188
  """
173
189
  # TODO: allow updating the handle/embedding config
174
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
190
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
175
191
  if not await server.source_manager.get_source_by_id(source_id=folder_id, actor=actor):
176
192
  raise HTTPException(status_code=404, detail=f"Folder with id={folder_id} does not exist.")
177
193
  return await server.source_manager.update_source(source_id=folder_id, source_update=folder, actor=actor)
@@ -181,12 +197,12 @@ async def modify_folder(
181
197
  async def delete_folder(
182
198
  folder_id: str,
183
199
  server: "SyncServer" = Depends(get_letta_server),
184
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
200
+ headers: HeaderParams = Depends(get_headers),
185
201
  ):
186
202
  """
187
203
  Delete a data folder.
188
204
  """
189
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
205
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
190
206
  folder = await server.source_manager.get_source_by_id(source_id=folder_id, actor=actor)
191
207
  agent_states = await server.source_manager.list_attached_agents(source_id=folder_id, actor=actor)
192
208
  files = await server.file_manager.list_files(folder_id, actor)
@@ -221,7 +237,7 @@ async def upload_file_to_folder(
221
237
  duplicate_handling: DuplicateFileHandling = Query(DuplicateFileHandling.SUFFIX, description="How to handle duplicate filenames"),
222
238
  name: Optional[str] = Query(None, description="Optional custom name to override the uploaded file's name"),
223
239
  server: "SyncServer" = Depends(get_letta_server),
224
- actor_id: Optional[str] = Header(None, alias="user_id"),
240
+ headers: HeaderParams = Depends(get_headers),
225
241
  ):
226
242
  """
227
243
  Upload a file to a data folder.
@@ -258,7 +274,7 @@ async def upload_file_to_folder(
258
274
  ),
259
275
  )
260
276
 
261
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
277
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
262
278
 
263
279
  folder = await server.source_manager.get_source_by_id(source_id=folder_id, actor=actor)
264
280
  if folder is None:
@@ -332,58 +348,102 @@ async def upload_file_to_folder(
332
348
  return file_metadata
333
349
 
334
350
 
335
- @router.get("/{folder_id}/agents", response_model=List[str], operation_id="get_agents_for_folder")
336
- async def get_agents_for_folder(
351
+ @router.get("/{folder_id}/agents", response_model=List[str], operation_id="list_agents_for_folder")
352
+ async def list_agents_for_folder(
337
353
  folder_id: str,
354
+ before: Optional[str] = Query(
355
+ None,
356
+ description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order",
357
+ ),
358
+ after: Optional[str] = Query(
359
+ None,
360
+ description="Agent ID cursor for pagination. Returns agents that come after this agent ID in the specified sort order",
361
+ ),
362
+ limit: Optional[int] = Query(50, description="Maximum number of agents to return"),
363
+ order: Literal["asc", "desc"] = Query(
364
+ "desc", description="Sort order for agents by creation time. 'asc' for oldest first, 'desc' for newest first"
365
+ ),
366
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
338
367
  server: SyncServer = Depends(get_letta_server),
339
- actor_id: Optional[str] = Header(None, alias="user_id"),
368
+ headers: HeaderParams = Depends(get_headers),
340
369
  ):
341
370
  """
342
371
  Get all agent IDs that have the specified folder attached.
343
372
  """
344
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
345
- return await server.source_manager.get_agents_for_source_id(source_id=folder_id, actor=actor)
373
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
374
+ return await server.source_manager.get_agents_for_source_id(
375
+ source_id=folder_id,
376
+ before=before,
377
+ after=after,
378
+ limit=limit,
379
+ ascending=(order == "asc"),
380
+ actor=actor,
381
+ )
346
382
 
347
383
 
348
384
  @router.get("/{folder_id}/passages", response_model=List[Passage], operation_id="list_folder_passages")
349
385
  async def list_folder_passages(
350
386
  folder_id: str,
351
- after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."),
352
- before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
353
- limit: int = Query(100, description="Maximum number of messages to retrieve."),
387
+ before: Optional[str] = Query(
388
+ None,
389
+ description="Passage ID cursor for pagination. Returns passages that come before this passage ID in the specified sort order",
390
+ ),
391
+ after: Optional[str] = Query(
392
+ None,
393
+ description="Passage ID cursor for pagination. Returns passages that come after this passage ID in the specified sort order",
394
+ ),
395
+ limit: Optional[int] = Query(100, description="Maximum number of passages to return"),
396
+ order: Literal["asc", "desc"] = Query(
397
+ "desc", description="Sort order for passages by creation time. 'asc' for oldest first, 'desc' for newest first"
398
+ ),
399
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
354
400
  server: SyncServer = Depends(get_letta_server),
355
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
401
+ headers: HeaderParams = Depends(get_headers),
356
402
  ):
357
403
  """
358
404
  List all passages associated with a data folder.
359
405
  """
360
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
406
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
361
407
  return await server.agent_manager.query_source_passages_async(
362
408
  actor=actor,
363
409
  source_id=folder_id,
364
410
  after=after,
365
411
  before=before,
366
412
  limit=limit,
413
+ ascending=(order == "asc"),
367
414
  )
368
415
 
369
416
 
370
417
  @router.get("/{folder_id}/files", response_model=List[FileMetadata], operation_id="list_folder_files")
371
418
  async def list_folder_files(
372
419
  folder_id: str,
373
- limit: int = Query(1000, description="Number of files to return"),
374
- after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"),
420
+ before: Optional[str] = Query(
421
+ None,
422
+ description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order",
423
+ ),
424
+ after: Optional[str] = Query(
425
+ None,
426
+ description="File ID cursor for pagination. Returns files that come after this file ID in the specified sort order",
427
+ ),
428
+ limit: Optional[int] = Query(1000, description="Maximum number of files to return"),
429
+ order: Literal["asc", "desc"] = Query(
430
+ "desc", description="Sort order for files by creation time. 'asc' for oldest first, 'desc' for newest first"
431
+ ),
432
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
375
433
  include_content: bool = Query(False, description="Whether to include full file content"),
376
434
  server: "SyncServer" = Depends(get_letta_server),
377
- actor_id: Optional[str] = Header(None, alias="user_id"),
435
+ headers: HeaderParams = Depends(get_headers),
378
436
  ):
379
437
  """
380
438
  List paginated files associated with a data folder.
381
439
  """
382
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
440
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
383
441
  return await server.file_manager.list_files(
384
442
  source_id=folder_id,
385
- limit=limit,
443
+ before=before,
386
444
  after=after,
445
+ limit=limit,
446
+ ascending=(order == "asc"),
387
447
  actor=actor,
388
448
  include_content=include_content,
389
449
  strip_directory_prefix=True, # TODO: Reconsider this. This is purely for aesthetics.
@@ -396,12 +456,12 @@ async def list_folder_files(
396
456
  # file_id: str,
397
457
  # include_content: bool = Query(False, description="Whether to include full file content"),
398
458
  # server: "SyncServer" = Depends(get_letta_server),
399
- # actor_id: Optional[str] = Header(None, alias="user_id"),
459
+ # headers: HeaderParams = Depends(get_headers),
400
460
  # ):
401
461
  # """
402
462
  # Retrieve metadata for a specific file by its ID.
403
463
  # """
404
- # actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
464
+ # actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
405
465
  #
406
466
  # # Get file metadata using the file manager
407
467
  # file_metadata = await server.file_manager.get_file_by_id(
@@ -446,12 +506,12 @@ async def delete_file_from_folder(
446
506
  folder_id: str,
447
507
  file_id: str,
448
508
  server: "SyncServer" = Depends(get_letta_server),
449
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
509
+ headers: HeaderParams = Depends(get_headers),
450
510
  ):
451
511
  """
452
512
  Delete a file from a folder.
453
513
  """
454
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
514
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
455
515
 
456
516
  deleted_file = await server.file_manager.delete_file(file_id=file_id, actor=actor)
457
517