letta-nightly 0.11.7.dev20250914103918__py3-none-any.whl → 0.11.7.dev20250916104104__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 (67) hide show
  1. letta/functions/function_sets/multi_agent.py +1 -1
  2. letta/functions/helpers.py +1 -1
  3. letta/prompts/gpt_system.py +13 -15
  4. letta/prompts/system_prompts/__init__.py +27 -0
  5. letta/prompts/{system/memgpt_chat.txt → system_prompts/memgpt_chat.py} +2 -0
  6. letta/prompts/{system/memgpt_generate_tool.txt → system_prompts/memgpt_generate_tool.py} +4 -2
  7. letta/prompts/{system/memgpt_v2_chat.txt → system_prompts/memgpt_v2_chat.py} +2 -0
  8. letta/prompts/{system/react.txt → system_prompts/react.py} +2 -0
  9. letta/prompts/{system/sleeptime_doc_ingest.txt → system_prompts/sleeptime_doc_ingest.py} +2 -0
  10. letta/prompts/{system/sleeptime_v2.txt → system_prompts/sleeptime_v2.py} +2 -0
  11. letta/prompts/{system/summary_system_prompt.txt → system_prompts/summary_system_prompt.py} +2 -0
  12. letta/prompts/{system/voice_chat.txt → system_prompts/voice_chat.py} +2 -0
  13. letta/prompts/{system/voice_sleeptime.txt → system_prompts/voice_sleeptime.py} +2 -0
  14. letta/prompts/{system/workflow.txt → system_prompts/workflow.py} +2 -0
  15. letta/server/rest_api/dependencies.py +37 -0
  16. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +4 -3
  17. letta/server/rest_api/routers/v1/agents.py +112 -109
  18. letta/server/rest_api/routers/v1/blocks.py +44 -20
  19. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  20. letta/server/rest_api/routers/v1/folders.py +107 -47
  21. letta/server/rest_api/routers/v1/groups.py +52 -32
  22. letta/server/rest_api/routers/v1/health.py +2 -2
  23. letta/server/rest_api/routers/v1/identities.py +110 -21
  24. letta/server/rest_api/routers/v1/internal_templates.py +28 -13
  25. letta/server/rest_api/routers/v1/jobs.py +12 -12
  26. letta/server/rest_api/routers/v1/llms.py +6 -8
  27. letta/server/rest_api/routers/v1/messages.py +53 -36
  28. letta/server/rest_api/routers/v1/organizations.py +1 -1
  29. letta/server/rest_api/routers/v1/providers.py +47 -20
  30. letta/server/rest_api/routers/v1/runs.py +19 -19
  31. letta/server/rest_api/routers/v1/sandbox_configs.py +25 -25
  32. letta/server/rest_api/routers/v1/sources.py +44 -45
  33. letta/server/rest_api/routers/v1/steps.py +50 -22
  34. letta/server/rest_api/routers/v1/tags.py +25 -10
  35. letta/server/rest_api/routers/v1/telemetry.py +11 -6
  36. letta/server/rest_api/routers/v1/tools.py +71 -54
  37. letta/server/rest_api/routers/v1/users.py +1 -1
  38. letta/server/rest_api/routers/v1/voice.py +6 -5
  39. letta/server/rest_api/utils.py +1 -18
  40. letta/services/agent_manager.py +31 -7
  41. letta/services/file_manager.py +6 -0
  42. letta/services/group_manager.py +2 -1
  43. letta/services/identity_manager.py +67 -0
  44. letta/services/provider_manager.py +14 -1
  45. letta/services/source_manager.py +11 -1
  46. letta/services/step_manager.py +5 -1
  47. letta/services/tool_manager.py +46 -9
  48. letta/utils.py +6 -2
  49. {letta_nightly-0.11.7.dev20250914103918.dist-info → letta_nightly-0.11.7.dev20250916104104.dist-info}/METADATA +1 -1
  50. {letta_nightly-0.11.7.dev20250914103918.dist-info → letta_nightly-0.11.7.dev20250916104104.dist-info}/RECORD +53 -65
  51. letta/prompts/system/memgpt_base.txt +0 -54
  52. letta/prompts/system/memgpt_chat_compressed.txt +0 -13
  53. letta/prompts/system/memgpt_chat_fstring.txt +0 -51
  54. letta/prompts/system/memgpt_convo_only.txt +0 -12
  55. letta/prompts/system/memgpt_doc.txt +0 -50
  56. letta/prompts/system/memgpt_gpt35_extralong.txt +0 -53
  57. letta/prompts/system/memgpt_intuitive_knowledge.txt +0 -31
  58. letta/prompts/system/memgpt_memory_only.txt +0 -29
  59. letta/prompts/system/memgpt_modified_chat.txt +0 -23
  60. letta/prompts/system/memgpt_modified_o1.txt +0 -31
  61. letta/prompts/system/memgpt_offline_memory.txt +0 -23
  62. letta/prompts/system/memgpt_offline_memory_chat.txt +0 -35
  63. letta/prompts/system/memgpt_sleeptime_chat.txt +0 -52
  64. letta/prompts/system/sleeptime.txt +0 -37
  65. {letta_nightly-0.11.7.dev20250914103918.dist-info → letta_nightly-0.11.7.dev20250916104104.dist-info}/WHEEL +0 -0
  66. {letta_nightly-0.11.7.dev20250914103918.dist-info → letta_nightly-0.11.7.dev20250916104104.dist-info}/entry_points.txt +0 -0
  67. {letta_nightly-0.11.7.dev20250914103918.dist-info → letta_nightly-0.11.7.dev20250916104104.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- from typing import Annotated, List, Optional
1
+ from typing import Annotated, List, Literal, Optional
2
2
 
3
3
  from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, status
4
4
  from fastapi.responses import JSONResponse
@@ -10,7 +10,7 @@ from letta.schemas.group import Group, GroupCreate, GroupUpdate, ManagerType
10
10
  from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion
11
11
  from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
12
12
  from letta.schemas.letta_response import LettaResponse
13
- from letta.server.rest_api.utils import get_letta_server
13
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
14
14
  from letta.server.server import SyncServer
15
15
 
16
16
  router = APIRouter(prefix="/groups", tags=["groups"])
@@ -19,11 +19,19 @@ router = APIRouter(prefix="/groups", tags=["groups"])
19
19
  @router.get("/", response_model=List[Group], operation_id="list_groups")
20
20
  async def list_groups(
21
21
  server: "SyncServer" = Depends(get_letta_server),
22
- actor_id: Optional[str] = Header(None, alias="user_id"),
22
+ headers: HeaderParams = Depends(get_headers),
23
23
  manager_type: Optional[ManagerType] = Query(None, description="Search groups by manager type"),
24
- before: Optional[str] = Query(None, description="Cursor for pagination"),
25
- after: Optional[str] = Query(None, description="Cursor for pagination"),
26
- limit: Optional[int] = Query(None, description="Limit for pagination"),
24
+ before: Optional[str] = Query(
25
+ None, description="Group ID cursor for pagination. Returns groups that come before this group ID in the specified sort order"
26
+ ),
27
+ after: Optional[str] = Query(
28
+ None, description="Group ID cursor for pagination. Returns groups that come after this group ID in the specified sort order"
29
+ ),
30
+ limit: Optional[int] = Query(50, description="Maximum number of groups to return"),
31
+ order: Literal["asc", "desc"] = Query(
32
+ "asc", description="Sort order for groups by creation time. 'asc' for oldest first, 'desc' for newest first"
33
+ ),
34
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
27
35
  project_id: Optional[str] = Query(None, description="Search groups by project id"),
28
36
  show_hidden_groups: bool | None = Query(
29
37
  False,
@@ -34,7 +42,7 @@ async def list_groups(
34
42
  """
35
43
  Fetch all multi-agent groups matching query.
36
44
  """
37
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
45
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
38
46
  return await server.group_manager.list_groups_async(
39
47
  actor=actor,
40
48
  project_id=project_id,
@@ -42,6 +50,7 @@ async def list_groups(
42
50
  before=before,
43
51
  after=after,
44
52
  limit=limit,
53
+ ascending=(order == "asc"),
45
54
  show_hidden_groups=show_hidden_groups,
46
55
  )
47
56
 
@@ -49,12 +58,12 @@ async def list_groups(
49
58
  @router.get("/count", response_model=int, operation_id="count_groups")
50
59
  async def count_groups(
51
60
  server: SyncServer = Depends(get_letta_server),
52
- actor_id: Optional[str] = Header(None, alias="user_id"),
61
+ headers: HeaderParams = Depends(get_headers),
53
62
  ):
54
63
  """
55
64
  Get the count of all groups associated with a given user.
56
65
  """
57
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
66
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
58
67
  return await server.group_manager.size(actor=actor)
59
68
 
60
69
 
@@ -62,12 +71,12 @@ async def count_groups(
62
71
  async def retrieve_group(
63
72
  group_id: str,
64
73
  server: "SyncServer" = Depends(get_letta_server),
65
- actor_id: Optional[str] = Header(None, alias="user_id"),
74
+ headers: HeaderParams = Depends(get_headers),
66
75
  ):
67
76
  """
68
77
  Retrieve the group by id.
69
78
  """
70
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
79
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
71
80
 
72
81
  try:
73
82
  return await server.group_manager.retrieve_group_async(group_id=group_id, actor=actor)
@@ -79,7 +88,7 @@ async def retrieve_group(
79
88
  async def create_group(
80
89
  group: GroupCreate = Body(...),
81
90
  server: "SyncServer" = Depends(get_letta_server),
82
- actor_id: Optional[str] = Header(None, alias="user_id"),
91
+ headers: HeaderParams = Depends(get_headers),
83
92
  x_project: Optional[str] = Header(
84
93
  None, alias="X-Project", description="The project slug to associate with the group (cloud only)."
85
94
  ), # Only handled by next js middleware
@@ -88,7 +97,7 @@ async def create_group(
88
97
  Create a new multi-agent group with the specified configuration.
89
98
  """
90
99
  try:
91
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
100
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
92
101
  return await server.group_manager.create_group_async(group, actor=actor)
93
102
  except Exception as e:
94
103
  raise HTTPException(status_code=500, detail=str(e))
@@ -99,7 +108,7 @@ async def modify_group(
99
108
  group_id: str,
100
109
  group: GroupUpdate = Body(...),
101
110
  server: "SyncServer" = Depends(get_letta_server),
102
- actor_id: Optional[str] = Header(None, alias="user_id"),
111
+ headers: HeaderParams = Depends(get_headers),
103
112
  x_project: Optional[str] = Header(
104
113
  None, alias="X-Project", description="The project slug to associate with the group (cloud only)."
105
114
  ), # Only handled by next js middleware
@@ -108,7 +117,7 @@ async def modify_group(
108
117
  Create a new multi-agent group with the specified configuration.
109
118
  """
110
119
  try:
111
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
120
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
112
121
  return await server.group_manager.modify_group_async(group_id=group_id, group_update=group, actor=actor)
113
122
  except Exception as e:
114
123
  raise HTTPException(status_code=500, detail=str(e))
@@ -118,12 +127,12 @@ async def modify_group(
118
127
  async def delete_group(
119
128
  group_id: str,
120
129
  server: "SyncServer" = Depends(get_letta_server),
121
- actor_id: Optional[str] = Header(None, alias="user_id"),
130
+ headers: HeaderParams = Depends(get_headers),
122
131
  ):
123
132
  """
124
133
  Delete a multi-agent group.
125
134
  """
126
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
135
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
127
136
  try:
128
137
  await server.group_manager.delete_group_async(group_id=group_id, actor=actor)
129
138
  return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Group id={group_id} successfully deleted"})
@@ -140,13 +149,13 @@ async def send_group_message(
140
149
  group_id: str,
141
150
  server: SyncServer = Depends(get_letta_server),
142
151
  request: LettaRequest = Body(...),
143
- actor_id: Optional[str] = Header(None, alias="user_id"),
152
+ headers: HeaderParams = Depends(get_headers),
144
153
  ):
145
154
  """
146
155
  Process a user message and return the group's response.
147
156
  This endpoint accepts a message from a user and processes it through through agents in the group based on the specified pattern
148
157
  """
149
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
158
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
150
159
  result = await server.send_group_message_to_agent(
151
160
  group_id=group_id,
152
161
  actor=actor,
@@ -178,14 +187,14 @@ async def send_group_message_streaming(
178
187
  group_id: str,
179
188
  server: SyncServer = Depends(get_letta_server),
180
189
  request: LettaStreamingRequest = Body(...),
181
- actor_id: Optional[str] = Header(None, alias="user_id"),
190
+ headers: HeaderParams = Depends(get_headers),
182
191
  ):
183
192
  """
184
193
  Process a user message and return the group's responses.
185
194
  This endpoint accepts a message from a user and processes it through agents in the group based on the specified pattern.
186
195
  It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
187
196
  """
188
- 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)
189
198
  result = await server.send_group_message_to_agent(
190
199
  group_id=group_id,
191
200
  actor=actor,
@@ -211,32 +220,42 @@ async def modify_group_message(
211
220
  message_id: str,
212
221
  request: LettaMessageUpdateUnion = Body(...),
213
222
  server: "SyncServer" = Depends(get_letta_server),
214
- actor_id: Optional[str] = Header(None, alias="user_id"),
223
+ headers: HeaderParams = Depends(get_headers),
215
224
  ):
216
225
  """
217
226
  Update the details of a message associated with an agent.
218
227
  """
219
228
  # TODO: support modifying tool calls/returns
220
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
229
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
221
230
  return await server.message_manager.update_message_by_letta_message(message_id=message_id, letta_message_update=request, actor=actor)
222
231
 
223
232
 
224
233
  @router.get("/{group_id}/messages", response_model=GroupMessagesResponse, operation_id="list_group_messages")
225
234
  async def list_group_messages(
226
235
  group_id: str,
227
- server: "SyncServer" = Depends(get_letta_server),
228
- after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."),
229
- before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
230
- limit: int = Query(10, description="Maximum number of messages to retrieve."),
236
+ before: Optional[str] = Query(
237
+ None,
238
+ description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order",
239
+ ),
240
+ after: Optional[str] = Query(
241
+ None,
242
+ description="Message ID cursor for pagination. Returns messages that come after this message ID in the specified sort order",
243
+ ),
244
+ limit: Optional[int] = Query(10, description="Maximum number of messages to retrieve"),
245
+ order: Literal["asc", "desc"] = Query(
246
+ "desc", description="Sort order for messages by creation time. 'asc' for oldest first, 'desc' for newest first"
247
+ ),
248
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
231
249
  use_assistant_message: bool = Query(True, description="Whether to use assistant messages"),
232
250
  assistant_message_tool_name: str = Query(DEFAULT_MESSAGE_TOOL, description="The name of the designated message tool."),
233
251
  assistant_message_tool_kwarg: str = Query(DEFAULT_MESSAGE_TOOL_KWARG, description="The name of the message argument."),
234
- actor_id: Optional[str] = Header(None, alias="user_id"),
252
+ server: "SyncServer" = Depends(get_letta_server),
253
+ headers: HeaderParams = Depends(get_headers),
235
254
  ):
236
255
  """
237
256
  Retrieve message history for an agent.
238
257
  """
239
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
258
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
240
259
  group = await server.group_manager.retrieve_group_async(group_id=group_id, actor=actor)
241
260
  if group.manager_agent_id:
242
261
  return await server.get_agent_recall_async(
@@ -246,7 +265,7 @@ async def list_group_messages(
246
265
  before=before,
247
266
  limit=limit,
248
267
  group_id=group_id,
249
- reverse=True,
268
+ reverse=(order == "desc"),
250
269
  return_message_object=False,
251
270
  use_assistant_message=use_assistant_message,
252
271
  assistant_message_tool_name=assistant_message_tool_name,
@@ -258,6 +277,7 @@ async def list_group_messages(
258
277
  after=after,
259
278
  before=before,
260
279
  limit=limit,
280
+ ascending=(order == "asc"),
261
281
  actor=actor,
262
282
  use_assistant_message=use_assistant_message,
263
283
  assistant_message_tool_name=assistant_message_tool_name,
@@ -269,10 +289,10 @@ async def list_group_messages(
269
289
  async def reset_group_messages(
270
290
  group_id: str,
271
291
  server: "SyncServer" = Depends(get_letta_server),
272
- actor_id: Optional[str] = Header(None, alias="user_id"),
292
+ headers: HeaderParams = Depends(get_headers),
273
293
  ):
274
294
  """
275
295
  Delete the group messages for all agents that are part of the multi-agent group.
276
296
  """
277
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
297
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
278
298
  await server.group_manager.reset_messages_async(group_id=group_id, actor=actor)
@@ -12,8 +12,8 @@ router = APIRouter(prefix="/health", tags=["health"])
12
12
 
13
13
 
14
14
  # Health check
15
- @router.get("/", response_model=Health, operation_id="health_check")
16
- def health_check():
15
+ @router.get("/", response_model=Health, operation_id="check_health")
16
+ def check_health():
17
17
  return Health(
18
18
  version=__version__,
19
19
  status="ok",
@@ -1,10 +1,12 @@
1
- from typing import TYPE_CHECKING, List, Optional
1
+ from typing import TYPE_CHECKING, List, Literal, Optional
2
2
 
3
3
  from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
4
4
 
5
5
  from letta.orm.errors import NoResultFound, UniqueConstraintViolationError
6
+ from letta.schemas.agent import AgentState
7
+ from letta.schemas.block import Block
6
8
  from letta.schemas.identity import Identity, IdentityCreate, IdentityProperty, IdentityType, IdentityUpdate, IdentityUpsert
7
- from letta.server.rest_api.utils import get_letta_server
9
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from letta.server.server import SyncServer
@@ -18,17 +20,27 @@ async def list_identities(
18
20
  project_id: Optional[str] = Query(None),
19
21
  identifier_key: Optional[str] = Query(None),
20
22
  identity_type: Optional[IdentityType] = Query(None),
21
- before: Optional[str] = Query(None),
22
- after: Optional[str] = Query(None),
23
- limit: Optional[int] = Query(50),
23
+ before: Optional[str] = Query(
24
+ None,
25
+ description="Identity ID cursor for pagination. Returns identities that come before this identity ID in the specified sort order",
26
+ ),
27
+ after: Optional[str] = Query(
28
+ None,
29
+ description="Identity ID cursor for pagination. Returns identities that come after this identity ID in the specified sort order",
30
+ ),
31
+ limit: Optional[int] = Query(50, description="Maximum number of identities to return"),
32
+ order: Literal["asc", "desc"] = Query(
33
+ "desc", description="Sort order for identities by creation time. 'asc' for oldest first, 'desc' for newest first"
34
+ ),
35
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
24
36
  server: "SyncServer" = Depends(get_letta_server),
25
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
37
+ headers: HeaderParams = Depends(get_headers),
26
38
  ):
27
39
  """
28
40
  Get a list of all identities in the database
29
41
  """
30
42
  try:
31
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
43
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
32
44
 
33
45
  identities = await server.identity_manager.list_identities_async(
34
46
  name=name,
@@ -38,6 +50,7 @@ async def list_identities(
38
50
  before=before,
39
51
  after=after,
40
52
  limit=limit,
53
+ ascending=(order == "asc"),
41
54
  actor=actor,
42
55
  )
43
56
  except HTTPException:
@@ -52,13 +65,13 @@ async def list_identities(
52
65
  @router.get("/count", tags=["identities"], response_model=int, operation_id="count_identities")
53
66
  async def count_identities(
54
67
  server: "SyncServer" = Depends(get_letta_server),
55
- actor_id: Optional[str] = Header(None, alias="user_id"),
68
+ headers: HeaderParams = Depends(get_headers),
56
69
  ):
57
70
  """
58
71
  Get count of all identities for a user
59
72
  """
60
73
  try:
61
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
74
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
62
75
  return await server.identity_manager.size_async(actor=actor)
63
76
  except NoResultFound:
64
77
  return 0
@@ -72,10 +85,10 @@ async def count_identities(
72
85
  async def retrieve_identity(
73
86
  identity_id: str,
74
87
  server: "SyncServer" = Depends(get_letta_server),
75
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
88
+ headers: HeaderParams = Depends(get_headers),
76
89
  ):
77
90
  try:
78
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
91
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
79
92
  return await server.identity_manager.get_identity_async(identity_id=identity_id, actor=actor)
80
93
  except NoResultFound as e:
81
94
  raise HTTPException(status_code=404, detail=str(e))
@@ -85,13 +98,13 @@ async def retrieve_identity(
85
98
  async def create_identity(
86
99
  identity: IdentityCreate = Body(...),
87
100
  server: "SyncServer" = Depends(get_letta_server),
88
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
101
+ headers: HeaderParams = Depends(get_headers),
89
102
  x_project: Optional[str] = Header(
90
103
  None, alias="X-Project", description="The project slug to associate with the identity (cloud only)."
91
104
  ), # Only handled by next js middleware
92
105
  ):
93
106
  try:
94
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
107
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
95
108
  return await server.identity_manager.create_identity_async(identity=identity, actor=actor)
96
109
  except HTTPException:
97
110
  raise
@@ -111,13 +124,13 @@ async def create_identity(
111
124
  async def upsert_identity(
112
125
  identity: IdentityUpsert = Body(...),
113
126
  server: "SyncServer" = Depends(get_letta_server),
114
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
127
+ headers: HeaderParams = Depends(get_headers),
115
128
  x_project: Optional[str] = Header(
116
129
  None, alias="X-Project", description="The project slug to associate with the identity (cloud only)."
117
130
  ), # Only handled by next js middleware
118
131
  ):
119
132
  try:
120
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
133
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
121
134
  return await server.identity_manager.upsert_identity_async(identity=identity, actor=actor)
122
135
  except HTTPException:
123
136
  raise
@@ -132,10 +145,10 @@ async def modify_identity(
132
145
  identity_id: str,
133
146
  identity: IdentityUpdate = Body(...),
134
147
  server: "SyncServer" = Depends(get_letta_server),
135
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
148
+ headers: HeaderParams = Depends(get_headers),
136
149
  ):
137
150
  try:
138
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
151
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
139
152
  return await server.identity_manager.update_identity_async(identity_id=identity_id, identity=identity, actor=actor)
140
153
  except HTTPException:
141
154
  raise
@@ -150,10 +163,10 @@ async def upsert_identity_properties(
150
163
  identity_id: str,
151
164
  properties: List[IdentityProperty] = Body(...),
152
165
  server: "SyncServer" = Depends(get_letta_server),
153
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
166
+ headers: HeaderParams = Depends(get_headers),
154
167
  ):
155
168
  try:
156
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
169
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
157
170
  return await server.identity_manager.upsert_identity_properties_async(identity_id=identity_id, properties=properties, actor=actor)
158
171
  except HTTPException:
159
172
  raise
@@ -167,13 +180,13 @@ async def upsert_identity_properties(
167
180
  async def delete_identity(
168
181
  identity_id: str,
169
182
  server: "SyncServer" = Depends(get_letta_server),
170
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
183
+ headers: HeaderParams = Depends(get_headers),
171
184
  ):
172
185
  """
173
186
  Delete an identity by its identifier key
174
187
  """
175
188
  try:
176
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
189
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
177
190
  await server.identity_manager.delete_identity_async(identity_id=identity_id, actor=actor)
178
191
  except HTTPException:
179
192
  raise
@@ -181,3 +194,79 @@ async def delete_identity(
181
194
  raise HTTPException(status_code=404, detail=str(e))
182
195
  except Exception as e:
183
196
  raise HTTPException(status_code=500, detail=f"{e}")
197
+
198
+
199
+ @router.get("/{identity_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_identity")
200
+ async def list_agents_for_identity(
201
+ identity_id: str,
202
+ before: Optional[str] = Query(
203
+ None,
204
+ description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order",
205
+ ),
206
+ after: Optional[str] = Query(
207
+ None,
208
+ description="Agent ID cursor for pagination. Returns agents that come after this agent ID in the specified sort order",
209
+ ),
210
+ limit: Optional[int] = Query(50, description="Maximum number of agents to return"),
211
+ order: Literal["asc", "desc"] = Query(
212
+ "desc", description="Sort order for agents by creation time. 'asc' for oldest first, 'desc' for newest first"
213
+ ),
214
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
215
+ server: "SyncServer" = Depends(get_letta_server),
216
+ headers: HeaderParams = Depends(get_headers),
217
+ ):
218
+ """
219
+ Get all agents associated with the specified identity.
220
+ """
221
+ try:
222
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
223
+ return await server.identity_manager.list_agents_for_identity_async(
224
+ identity_id=identity_id,
225
+ before=before,
226
+ after=after,
227
+ limit=limit,
228
+ ascending=(order == "asc"),
229
+ actor=actor,
230
+ )
231
+ except NoResultFound as e:
232
+ raise HTTPException(status_code=404, detail=f"Identity with id={identity_id} not found")
233
+ except Exception as e:
234
+ raise HTTPException(status_code=500, detail=f"{e}")
235
+
236
+
237
+ @router.get("/{identity_id}/blocks", response_model=List[Block], operation_id="list_blocks_for_identity")
238
+ async def list_blocks_for_identity(
239
+ identity_id: str,
240
+ before: Optional[str] = Query(
241
+ None,
242
+ description="Block ID cursor for pagination. Returns blocks that come before this block ID in the specified sort order",
243
+ ),
244
+ after: Optional[str] = Query(
245
+ None,
246
+ description="Block ID cursor for pagination. Returns blocks that come after this block ID in the specified sort order",
247
+ ),
248
+ limit: Optional[int] = Query(50, description="Maximum number of blocks to return"),
249
+ order: Literal["asc", "desc"] = Query(
250
+ "desc", description="Sort order for blocks by creation time. 'asc' for oldest first, 'desc' for newest first"
251
+ ),
252
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
253
+ server: "SyncServer" = Depends(get_letta_server),
254
+ headers: HeaderParams = Depends(get_headers),
255
+ ):
256
+ """
257
+ Get all blocks associated with the specified identity.
258
+ """
259
+ try:
260
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
261
+ return await server.identity_manager.list_blocks_for_identity_async(
262
+ identity_id=identity_id,
263
+ before=before,
264
+ after=after,
265
+ limit=limit,
266
+ ascending=(order == "asc"),
267
+ actor=actor,
268
+ )
269
+ except NoResultFound as e:
270
+ raise HTTPException(status_code=404, detail=f"Identity with id={identity_id} not found")
271
+ except Exception as e:
272
+ raise HTTPException(status_code=500, detail=f"{e}")
@@ -1,12 +1,12 @@
1
1
  from typing import List, Optional
2
2
 
3
- from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
3
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
  from pydantic import BaseModel
5
5
 
6
6
  from letta.schemas.agent import AgentState, InternalTemplateAgentCreate
7
7
  from letta.schemas.block import Block, InternalTemplateBlockCreate
8
8
  from letta.schemas.group import Group, InternalTemplateGroupCreate
9
- from letta.server.rest_api.utils import get_letta_server
9
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
10
10
  from letta.server.server import SyncServer
11
11
 
12
12
  router = APIRouter(prefix="/_internal_templates", tags=["_internal_templates"])
@@ -16,13 +16,13 @@ router = APIRouter(prefix="/_internal_templates", tags=["_internal_templates"])
16
16
  async def create_group(
17
17
  group: InternalTemplateGroupCreate = Body(...),
18
18
  server: "SyncServer" = Depends(get_letta_server),
19
- actor_id: Optional[str] = Header(None, alias="user_id"),
19
+ headers: HeaderParams = Depends(get_headers),
20
20
  ):
21
21
  """
22
22
  Create a new multi-agent group with the specified configuration.
23
23
  """
24
24
  try:
25
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
25
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
26
26
  return await server.group_manager.create_group_async(group, actor=actor)
27
27
  except Exception as e:
28
28
  raise HTTPException(status_code=500, detail=str(e))
@@ -32,13 +32,13 @@ async def create_group(
32
32
  async def create_agent(
33
33
  agent: InternalTemplateAgentCreate = Body(...),
34
34
  server: "SyncServer" = Depends(get_letta_server),
35
- actor_id: Optional[str] = Header(None, alias="user_id"),
35
+ headers: HeaderParams = Depends(get_headers),
36
36
  ):
37
37
  """
38
38
  Create a new agent with template-related fields.
39
39
  """
40
40
  try:
41
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
41
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
42
42
  return await server.agent_manager.create_agent_async(agent, actor=actor)
43
43
  except Exception as e:
44
44
  raise HTTPException(status_code=500, detail=str(e))
@@ -48,13 +48,13 @@ async def create_agent(
48
48
  async def create_block(
49
49
  block: InternalTemplateBlockCreate = Body(...),
50
50
  server: "SyncServer" = Depends(get_letta_server),
51
- actor_id: Optional[str] = Header(None, alias="user_id"),
51
+ headers: HeaderParams = Depends(get_headers),
52
52
  ):
53
53
  """
54
54
  Create a new block with template-related fields.
55
55
  """
56
56
  try:
57
- 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)
58
58
  block_obj = Block(**block.model_dump())
59
59
  return await server.block_manager.create_or_update_block_async(block_obj, actor=actor)
60
60
  except Exception as e:
@@ -68,6 +68,8 @@ class DeploymentEntity(BaseModel):
68
68
  type: str
69
69
  name: Optional[str] = None
70
70
  description: Optional[str] = None
71
+ entity_id: Optional[str] = None
72
+ project_id: Optional[str] = None
71
73
 
72
74
 
73
75
  class ListDeploymentEntitiesResponse(BaseModel):
@@ -92,7 +94,7 @@ class DeleteDeploymentResponse(BaseModel):
92
94
  async def list_deployment_entities(
93
95
  deployment_id: str,
94
96
  server: "SyncServer" = Depends(get_letta_server),
95
- actor_id: Optional[str] = Header(None, alias="user_id"),
97
+ headers: HeaderParams = Depends(get_headers),
96
98
  entity_types: Optional[List[str]] = Query(None, description="Filter by entity types (block, agent, group)"),
97
99
  ):
98
100
  """
@@ -100,7 +102,7 @@ async def list_deployment_entities(
100
102
  Optionally filter by entity types.
101
103
  """
102
104
  try:
103
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
105
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
104
106
 
105
107
  entities = []
106
108
 
@@ -140,6 +142,8 @@ async def list_deployment_entities(
140
142
  type="block",
141
143
  name=getattr(block, "template_name", None) or getattr(block, "label", None),
142
144
  description=block.description,
145
+ entity_id=getattr(block, "entity_id", None),
146
+ project_id=getattr(block, "project_id", None),
143
147
  )
144
148
  )
145
149
 
@@ -155,7 +159,16 @@ async def list_deployment_entities(
155
159
  agents = result.scalars().all()
156
160
 
157
161
  for agent in agents:
158
- entities.append(DeploymentEntity(id=agent.id, type="agent", name=agent.name, description=agent.description))
162
+ entities.append(
163
+ DeploymentEntity(
164
+ id=agent.id,
165
+ type="agent",
166
+ name=agent.name,
167
+ description=agent.description,
168
+ entity_id=getattr(agent, "entity_id", None),
169
+ project_id=getattr(agent, "project_id", None),
170
+ )
171
+ )
159
172
 
160
173
  # Query groups if requested
161
174
  if "group" in types_to_include:
@@ -175,6 +188,8 @@ async def list_deployment_entities(
175
188
  type="group",
176
189
  name=None, # Groups don't have a name field
177
190
  description=group.description,
191
+ entity_id=getattr(group, "entity_id", None),
192
+ project_id=getattr(group, "project_id", None),
178
193
  )
179
194
  )
180
195
 
@@ -191,14 +206,14 @@ async def list_deployment_entities(
191
206
  async def delete_deployment(
192
207
  deployment_id: str,
193
208
  server: "SyncServer" = Depends(get_letta_server),
194
- actor_id: Optional[str] = Header(None, alias="user_id"),
209
+ headers: HeaderParams = Depends(get_headers),
195
210
  ):
196
211
  """
197
212
  Delete all entities (blocks, agents, groups) with the specified deployment_id.
198
213
  Deletion order: blocks -> agents -> groups to maintain referential integrity.
199
214
  """
200
215
  try:
201
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
216
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
202
217
 
203
218
  deleted_blocks = []
204
219
  deleted_agents = []