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,8 +1,8 @@
1
- from typing import TYPE_CHECKING, List, Optional
1
+ from typing import TYPE_CHECKING, List, Literal, Optional
2
2
 
3
- from fastapi import APIRouter, Depends, Header, Query
3
+ from fastapi import APIRouter, Depends, Query
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
 
7
7
  if TYPE_CHECKING:
8
8
  from letta.server.server import SyncServer
@@ -13,15 +13,30 @@ router = APIRouter(prefix="/tags", tags=["tag", "admin"])
13
13
 
14
14
  @router.get("/", tags=["admin"], response_model=List[str], operation_id="list_tags")
15
15
  async def list_tags(
16
- after: Optional[str] = Query(None),
17
- limit: Optional[int] = Query(50),
16
+ before: Optional[str] = Query(
17
+ None, description="Tag cursor for pagination. Returns tags that come before this tag in the specified sort order"
18
+ ),
19
+ after: Optional[str] = Query(
20
+ None, description="Tag cursor for pagination. Returns tags that come after this tag in the specified sort order"
21
+ ),
22
+ limit: Optional[int] = Query(50, description="Maximum number of tags to return"),
23
+ order: Literal["asc", "desc"] = Query(
24
+ "asc", description="Sort order for tags. 'asc' for alphabetical order, 'desc' for reverse alphabetical order"
25
+ ),
26
+ order_by: Literal["name"] = Query("name", description="Field to sort by"),
27
+ query_text: Optional[str] = Query(
28
+ None, description="Filter tags by text search. Deprecated, please use name field instead", deprecated=True
29
+ ),
30
+ name: Optional[str] = Query(None, description="Filter tags by name"),
18
31
  server: "SyncServer" = Depends(get_letta_server),
19
- query_text: Optional[str] = Query(None),
20
- actor_id: Optional[str] = Header(None, alias="user_id"),
32
+ headers: HeaderParams = Depends(get_headers),
21
33
  ):
22
34
  """
23
- Get a list of all tags in the database
35
+ Get the list of all agent tags that have been created.
24
36
  """
25
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
26
- tags = await server.agent_manager.list_tags_async(actor=actor, after=after, limit=limit, query_text=query_text)
37
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
38
+ text_filter = name or query_text
39
+ tags = await server.agent_manager.list_tags_async(
40
+ actor=actor, before=before, after=after, limit=limit, query_text=text_filter, ascending=(order == "asc")
41
+ )
27
42
  return tags
@@ -1,26 +1,31 @@
1
1
  from typing import Optional
2
2
 
3
- from fastapi import APIRouter, Depends, Header
3
+ from fastapi import APIRouter, Depends
4
4
 
5
5
  from letta.schemas.provider_trace import ProviderTrace
6
- from letta.server.rest_api.utils import get_letta_server
6
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
7
7
  from letta.server.server import SyncServer
8
8
  from letta.settings import settings
9
9
 
10
10
  router = APIRouter(prefix="/telemetry", tags=["telemetry"])
11
11
 
12
12
 
13
- @router.get("/{step_id}", response_model=Optional[ProviderTrace], operation_id="retrieve_provider_trace")
14
- async def retrieve_provider_trace_by_step_id(
13
+ @router.get("/{step_id}", response_model=Optional[ProviderTrace], operation_id="retrieve_provider_trace", deprecated=True)
14
+ async def retrieve_provider_trace(
15
15
  step_id: str,
16
16
  server: SyncServer = Depends(get_letta_server),
17
- actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
17
+ headers: HeaderParams = Depends(get_headers),
18
18
  ):
19
+ """
20
+ **DEPRECATED**: Use `GET /steps/{step_id}/trace` instead.
21
+
22
+ Retrieve provider trace by step ID.
23
+ """
19
24
  provider_trace = None
20
25
  if settings.track_provider_trace:
21
26
  try:
22
27
  provider_trace = await server.telemetry_manager.get_provider_trace_by_step_id_async(
23
- step_id=step_id, actor=await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
28
+ step_id=step_id, actor=await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
24
29
  )
25
30
  except:
26
31
  pass
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from collections.abc import AsyncGenerator
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Literal, Optional, Union
4
4
 
5
5
  from composio.client import ComposioClientError, HTTPError, NoItemsFound
6
6
  from composio.client.collections import ActionModel, AppModel
@@ -11,7 +11,7 @@ from composio.exceptions import (
11
11
  EnumMetadataNotFound,
12
12
  EnumStringNotFound,
13
13
  )
14
- from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Request
14
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request
15
15
  from httpx import ConnectError, HTTPStatusError
16
16
  from pydantic import BaseModel, Field
17
17
  from starlette.responses import StreamingResponse
@@ -34,8 +34,8 @@ from letta.schemas.mcp import UpdateSSEMCPServer, UpdateStdioMCPServer, UpdateSt
34
34
  from letta.schemas.message import Message
35
35
  from letta.schemas.pip_requirement import PipRequirement
36
36
  from letta.schemas.tool import Tool, ToolCreate, ToolRunFromSource, ToolUpdate
37
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
37
38
  from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode
38
- from letta.server.rest_api.utils import get_letta_server
39
39
  from letta.server.server import SyncServer
40
40
  from letta.services.mcp.oauth_utils import MCPOAuthSession, drill_down_exception, oauth_stream_event
41
41
  from letta.services.mcp.stdio_client import AsyncStdioMCPClient
@@ -51,12 +51,12 @@ logger = get_logger(__name__)
51
51
  async def delete_tool(
52
52
  tool_id: str,
53
53
  server: SyncServer = Depends(get_letta_server),
54
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
54
+ headers: HeaderParams = Depends(get_headers),
55
55
  ):
56
56
  """
57
57
  Delete a tool by name
58
58
  """
59
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
59
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
60
60
  await server.tool_manager.delete_tool_by_id_async(tool_id=tool_id, actor=actor)
61
61
 
62
62
 
@@ -75,7 +75,7 @@ async def count_tools(
75
75
  return_only_letta_tools: Optional[bool] = Query(False, description="Count only tools with tool_type starting with 'letta_'"),
76
76
  exclude_letta_tools: Optional[bool] = Query(False, description="Exclude built-in Letta tools from the count"),
77
77
  server: SyncServer = Depends(get_letta_server),
78
- actor_id: Optional[str] = Header(None, alias="user_id"),
78
+ headers: HeaderParams = Depends(get_headers),
79
79
  ):
80
80
  """
81
81
  Get a count of all tools available to agents belonging to the org of the user.
@@ -110,7 +110,7 @@ async def count_tools(
110
110
  tool_types_str = parse_tool_types(tool_types)
111
111
  exclude_tool_types_str = parse_tool_types(exclude_tool_types)
112
112
 
113
- 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)
114
114
 
115
115
  # Combine single name with names list for unified processing (same logic as list_tools)
116
116
  combined_names = []
@@ -158,12 +158,12 @@ async def count_tools(
158
158
  async def retrieve_tool(
159
159
  tool_id: str,
160
160
  server: SyncServer = Depends(get_letta_server),
161
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
161
+ headers: HeaderParams = Depends(get_headers),
162
162
  ):
163
163
  """
164
164
  Get a tool by ID
165
165
  """
166
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
166
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
167
167
  tool = await server.tool_manager.get_tool_by_id_async(tool_id=tool_id, actor=actor)
168
168
  if tool is None:
169
169
  # return 404 error
@@ -173,9 +173,18 @@ async def retrieve_tool(
173
173
 
174
174
  @router.get("/", response_model=List[Tool], operation_id="list_tools")
175
175
  async def list_tools(
176
- after: Optional[str] = None,
177
- limit: Optional[int] = 50,
178
- name: Optional[str] = None,
176
+ before: Optional[str] = Query(
177
+ None, description="Tool ID cursor for pagination. Returns tools that come before this tool ID in the specified sort order"
178
+ ),
179
+ after: Optional[str] = Query(
180
+ None, description="Tool ID cursor for pagination. Returns tools that come after this tool ID in the specified sort order"
181
+ ),
182
+ limit: Optional[int] = Query(50, description="Maximum number of tools to return"),
183
+ order: Literal["asc", "desc"] = Query(
184
+ "desc", description="Sort order for tools by creation time. 'asc' for oldest first, 'desc' for newest first"
185
+ ),
186
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
187
+ name: Optional[str] = Query(None, description="Filter by single tool name"),
179
188
  names: Optional[List[str]] = Query(None, description="Filter by specific tool names"),
180
189
  tool_ids: Optional[List[str]] = Query(
181
190
  None, description="Filter by specific tool IDs - accepts repeated params or comma-separated values"
@@ -187,10 +196,10 @@ async def list_tools(
187
196
  ),
188
197
  return_only_letta_tools: Optional[bool] = Query(False, description="Return only tools with tool_type starting with 'letta_'"),
189
198
  server: SyncServer = Depends(get_letta_server),
190
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
199
+ headers: HeaderParams = Depends(get_headers),
191
200
  ):
192
201
  """
193
- Get a list of all tools available to agents belonging to the org of the user
202
+ Get a list of all tools available to agents.
194
203
  """
195
204
  try:
196
205
  # Helper function to parse tool types - supports both repeated params and comma-separated values
@@ -222,7 +231,7 @@ async def list_tools(
222
231
  tool_types_str = parse_tool_types(tool_types)
223
232
  exclude_tool_types_str = parse_tool_types(exclude_tool_types)
224
233
 
225
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
234
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
226
235
 
227
236
  # Combine single name with names list for unified processing
228
237
  combined_names = []
@@ -254,8 +263,10 @@ async def list_tools(
254
263
  # Get the list of tools using unified query
255
264
  return await server.tool_manager.list_tools_async(
256
265
  actor=actor,
266
+ before=before,
257
267
  after=after,
258
268
  limit=limit,
269
+ ascending=(order == "asc"),
259
270
  tool_types=tool_types_str,
260
271
  exclude_tool_types=exclude_tool_types_str,
261
272
  names=final_names,
@@ -271,13 +282,13 @@ async def list_tools(
271
282
  async def create_tool(
272
283
  request: ToolCreate = Body(...),
273
284
  server: SyncServer = Depends(get_letta_server),
274
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
285
+ headers: HeaderParams = Depends(get_headers),
275
286
  ):
276
287
  """
277
288
  Create a new tool
278
289
  """
279
290
  try:
280
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
291
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
281
292
  tool = Tool(**request.model_dump(exclude_unset=True))
282
293
  return await server.tool_manager.create_tool_async(pydantic_tool=tool, actor=actor)
283
294
  except UniqueConstraintViolationError as e:
@@ -295,13 +306,13 @@ async def create_tool(
295
306
  async def upsert_tool(
296
307
  request: ToolCreate = Body(...),
297
308
  server: SyncServer = Depends(get_letta_server),
298
- actor_id: Optional[str] = Header(None, alias="user_id"),
309
+ headers: HeaderParams = Depends(get_headers),
299
310
  ):
300
311
  """
301
312
  Create or update a tool
302
313
  """
303
314
  try:
304
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
315
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
305
316
  tool = await server.tool_manager.create_or_update_tool_async(
306
317
  pydantic_tool=Tool(**request.model_dump(exclude_unset=True)), actor=actor
307
318
  )
@@ -322,13 +333,13 @@ async def modify_tool(
322
333
  tool_id: str,
323
334
  request: ToolUpdate = Body(...),
324
335
  server: SyncServer = Depends(get_letta_server),
325
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
336
+ headers: HeaderParams = Depends(get_headers),
326
337
  ):
327
338
  """
328
339
  Update an existing tool
329
340
  """
330
341
  try:
331
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
342
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
332
343
  tool = await server.tool_manager.update_tool_by_id_async(tool_id=tool_id, tool_update=request, actor=actor)
333
344
  return tool
334
345
  except LettaToolNameConflictError as e:
@@ -345,12 +356,12 @@ async def modify_tool(
345
356
  @router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
346
357
  async def upsert_base_tools(
347
358
  server: SyncServer = Depends(get_letta_server),
348
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
359
+ headers: HeaderParams = Depends(get_headers),
349
360
  ):
350
361
  """
351
362
  Upsert base tools
352
363
  """
353
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
364
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
354
365
  return await server.tool_manager.upsert_base_tools_async(actor=actor)
355
366
 
356
367
 
@@ -358,12 +369,12 @@ async def upsert_base_tools(
358
369
  async def run_tool_from_source(
359
370
  server: SyncServer = Depends(get_letta_server),
360
371
  request: ToolRunFromSource = Body(...),
361
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
372
+ headers: HeaderParams = Depends(get_headers),
362
373
  ):
363
374
  """
364
375
  Attempt to build a tool from source, then run it on the provided arguments
365
376
  """
366
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
377
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
367
378
 
368
379
  try:
369
380
  return await server.run_tool_from_source(
@@ -388,11 +399,14 @@ async def run_tool_from_source(
388
399
 
389
400
  # Specific routes for Composio
390
401
  @router.get("/composio/apps", response_model=List[AppModel], operation_id="list_composio_apps")
391
- def list_composio_apps(server: SyncServer = Depends(get_letta_server), user_id: Optional[str] = Header(None, alias="user_id")):
402
+ def list_composio_apps(
403
+ server: SyncServer = Depends(get_letta_server),
404
+ headers: HeaderParams = Depends(get_headers),
405
+ ):
392
406
  """
393
407
  Get a list of all Composio apps
394
408
  """
395
- actor = server.user_manager.get_user_or_default(user_id=user_id)
409
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
396
410
  composio_api_key = get_composio_api_key(actor=actor, logger=logger)
397
411
  if not composio_api_key:
398
412
  raise HTTPException(
@@ -406,12 +420,12 @@ def list_composio_apps(server: SyncServer = Depends(get_letta_server), user_id:
406
420
  def list_composio_actions_by_app(
407
421
  composio_app_name: str,
408
422
  server: SyncServer = Depends(get_letta_server),
409
- actor_id: Optional[str] = Header(None, alias="user_id"),
423
+ headers: HeaderParams = Depends(get_headers),
410
424
  ):
411
425
  """
412
426
  Get a list of all Composio actions for a specific app
413
427
  """
414
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
428
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
415
429
  composio_api_key = get_composio_api_key(actor=actor, logger=logger)
416
430
  if not composio_api_key:
417
431
  raise HTTPException(
@@ -425,12 +439,12 @@ def list_composio_actions_by_app(
425
439
  async def add_composio_tool(
426
440
  composio_action_name: str,
427
441
  server: SyncServer = Depends(get_letta_server),
428
- actor_id: Optional[str] = Header(None, alias="user_id"),
442
+ headers: HeaderParams = Depends(get_headers),
429
443
  ):
430
444
  """
431
445
  Add a new Composio tool by action name (Composio refers to each tool as an `Action`)
432
446
  """
433
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
447
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
434
448
 
435
449
  try:
436
450
  tool_create = ToolCreate.from_composio(action_name=composio_action_name)
@@ -515,14 +529,17 @@ async def add_composio_tool(
515
529
  response_model=dict[str, Union[SSEServerConfig, StdioServerConfig, StreamableHTTPServerConfig]],
516
530
  operation_id="list_mcp_servers",
517
531
  )
518
- async def list_mcp_servers(server: SyncServer = Depends(get_letta_server), user_id: Optional[str] = Header(None, alias="user_id")):
532
+ async def list_mcp_servers(
533
+ server: SyncServer = Depends(get_letta_server),
534
+ headers: HeaderParams = Depends(get_headers),
535
+ ):
519
536
  """
520
537
  Get a list of all configured MCP servers
521
538
  """
522
539
  if tool_settings.mcp_read_from_config:
523
540
  return server.get_mcp_servers()
524
541
  else:
525
- actor = await server.user_manager.get_actor_or_default_async(actor_id=user_id)
542
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
526
543
  mcp_servers = await server.mcp_manager.list_mcp_servers(actor=actor)
527
544
  return {server.server_name: server.to_config(resolve_variables=False) for server in mcp_servers}
528
545
 
@@ -533,13 +550,13 @@ async def list_mcp_servers(server: SyncServer = Depends(get_letta_server), user_
533
550
  async def list_mcp_tools_by_server(
534
551
  mcp_server_name: str,
535
552
  server: SyncServer = Depends(get_letta_server),
536
- actor_id: Optional[str] = Header(None, alias="user_id"),
553
+ headers: HeaderParams = Depends(get_headers),
537
554
  ):
538
555
  """
539
556
  Get a list of all tools for a specific MCP server
540
557
  """
541
558
  try:
542
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
559
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
543
560
  mcp_tools = await server.mcp_manager.list_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor)
544
561
  return mcp_tools
545
562
  except Exception as e:
@@ -576,7 +593,7 @@ async def list_mcp_tools_by_server(
576
593
  async def resync_mcp_server_tools(
577
594
  mcp_server_name: str,
578
595
  server: SyncServer = Depends(get_letta_server),
579
- actor_id: Optional[str] = Header(None, alias="user_id"),
596
+ headers: HeaderParams = Depends(get_headers),
580
597
  agent_id: Optional[str] = None,
581
598
  ):
582
599
  """
@@ -588,7 +605,7 @@ async def resync_mcp_server_tools(
588
605
 
589
606
  Returns a summary of changes made.
590
607
  """
591
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
608
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
592
609
 
593
610
  try:
594
611
  result = await server.mcp_manager.resync_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor, agent_id=agent_id)
@@ -619,12 +636,12 @@ async def add_mcp_tool(
619
636
  mcp_server_name: str,
620
637
  mcp_tool_name: str,
621
638
  server: SyncServer = Depends(get_letta_server),
622
- actor_id: Optional[str] = Header(None, alias="user_id"),
639
+ headers: HeaderParams = Depends(get_headers),
623
640
  ):
624
641
  """
625
642
  Register a new MCP tool as a Letta server by MCP server + tool name
626
643
  """
627
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
644
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
628
645
 
629
646
  if tool_settings.mcp_read_from_config:
630
647
  try:
@@ -698,13 +715,13 @@ async def add_mcp_tool(
698
715
  async def add_mcp_server_to_config(
699
716
  request: Union[StdioServerConfig, SSEServerConfig, StreamableHTTPServerConfig] = Body(...),
700
717
  server: SyncServer = Depends(get_letta_server),
701
- actor_id: Optional[str] = Header(None, alias="user_id"),
718
+ headers: HeaderParams = Depends(get_headers),
702
719
  ):
703
720
  """
704
721
  Add a new MCP server to the Letta MCP server config
705
722
  """
706
723
  try:
707
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
724
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
708
725
 
709
726
  if tool_settings.mcp_read_from_config:
710
727
  # write to config file
@@ -767,13 +784,13 @@ async def update_mcp_server(
767
784
  mcp_server_name: str,
768
785
  request: Union[UpdateStdioMCPServer, UpdateSSEMCPServer, UpdateStreamableHTTPMCPServer] = Body(...),
769
786
  server: SyncServer = Depends(get_letta_server),
770
- actor_id: Optional[str] = Header(None, alias="user_id"),
787
+ headers: HeaderParams = Depends(get_headers),
771
788
  ):
772
789
  """
773
790
  Update an existing MCP server configuration
774
791
  """
775
792
  try:
776
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
793
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
777
794
 
778
795
  if tool_settings.mcp_read_from_config:
779
796
  raise HTTPException(status_code=501, detail="Update not implemented for config file mode, config files to be deprecated.")
@@ -797,7 +814,7 @@ async def update_mcp_server(
797
814
  async def delete_mcp_server_from_config(
798
815
  mcp_server_name: str,
799
816
  server: SyncServer = Depends(get_letta_server),
800
- actor_id: Optional[str] = Header(None, alias="user_id"),
817
+ headers: HeaderParams = Depends(get_headers),
801
818
  ):
802
819
  """
803
820
  Delete a MCP server configuration
@@ -807,7 +824,7 @@ async def delete_mcp_server_from_config(
807
824
  return server.delete_mcp_server_from_config(server_name=mcp_server_name)
808
825
  else:
809
826
  # log to DB
810
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
827
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
811
828
  mcp_server_id = await server.mcp_manager.get_mcp_server_id_by_name(mcp_server_name, actor)
812
829
  await server.mcp_manager.delete_mcp_server_by_id(mcp_server_id, actor=actor)
813
830
 
@@ -821,7 +838,7 @@ async def delete_mcp_server_from_config(
821
838
  async def test_mcp_server(
822
839
  request: Union[StdioServerConfig, SSEServerConfig, StreamableHTTPServerConfig] = Body(...),
823
840
  server: SyncServer = Depends(get_letta_server),
824
- actor_id: Optional[str] = Header(None, alias="user_id"),
841
+ headers: HeaderParams = Depends(get_headers),
825
842
  ):
826
843
  """
827
844
  Test connection to an MCP server without adding it.
@@ -829,7 +846,7 @@ async def test_mcp_server(
829
846
  """
830
847
  client = None
831
848
  try:
832
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
849
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
833
850
  request.resolve_environment_variables()
834
851
  client = await server.mcp_manager.get_mcp_client(request, actor)
835
852
 
@@ -888,7 +905,7 @@ async def test_mcp_server(
888
905
  async def connect_mcp_server(
889
906
  request: Union[StdioServerConfig, SSEServerConfig, StreamableHTTPServerConfig] = Body(...),
890
907
  server: SyncServer = Depends(get_letta_server),
891
- actor_id: Optional[str] = Header(None, alias="user_id"),
908
+ headers: HeaderParams = Depends(get_headers),
892
909
  http_request: Request = None,
893
910
  ) -> StreamingResponse:
894
911
  """
@@ -907,7 +924,7 @@ async def connect_mcp_server(
907
924
  # Acknolwedge connection attempt
908
925
  yield oauth_stream_event(OauthStreamEvent.CONNECTION_ATTEMPT, server_name=request.server_name)
909
926
 
910
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
927
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
911
928
 
912
929
  # Create MCP client with respective transport type
913
930
  try:
@@ -974,7 +991,7 @@ class CodeInput(BaseModel):
974
991
  async def generate_json_schema(
975
992
  request: CodeInput = Body(...),
976
993
  server: SyncServer = Depends(get_letta_server),
977
- actor_id: Optional[str] = Header(None, alias="user_id"),
994
+ headers: HeaderParams = Depends(get_headers),
978
995
  ):
979
996
  """
980
997
  Generate a JSON schema from the given source code defining a function or class.
@@ -1005,7 +1022,7 @@ async def execute_mcp_tool(
1005
1022
  tool_name: str,
1006
1023
  request: MCPToolExecuteRequest = Body(...),
1007
1024
  server: SyncServer = Depends(get_letta_server),
1008
- actor_id: Optional[str] = Header(None, alias="user_id"),
1025
+ headers: HeaderParams = Depends(get_headers),
1009
1026
  ):
1010
1027
  """
1011
1028
  Execute a specific MCP tool from a configured server.
@@ -1013,7 +1030,7 @@ async def execute_mcp_tool(
1013
1030
  """
1014
1031
  client = None
1015
1032
  try:
1016
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
1033
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
1017
1034
 
1018
1035
  # Get the MCP server by name
1019
1036
  mcp_server = await server.mcp_manager.get_mcp_server(mcp_server_name, actor)
@@ -1117,7 +1134,7 @@ class GenerateToolOutput(BaseModel):
1117
1134
  async def generate_tool_from_prompt(
1118
1135
  request: GenerateToolInput = Body(...),
1119
1136
  server: SyncServer = Depends(get_letta_server),
1120
- actor_id: Optional[str] = Header(None, alias="user_id"),
1137
+ headers: HeaderParams = Depends(get_headers),
1121
1138
  ):
1122
1139
  """
1123
1140
  Generate a tool from the given user prompt.
@@ -1125,7 +1142,7 @@ async def generate_tool_from_prompt(
1125
1142
  response_data = None
1126
1143
 
1127
1144
  try:
1128
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
1145
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
1129
1146
  llm_config = await server.get_cached_llm_config_async(actor=actor, handle=request.handle or "anthropic/claude-3-5-sonnet-20240620")
1130
1147
  formatted_prompt = (
1131
1148
  f"Generate a python function named {request.tool_name} using the instructions below "
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, List, Optional
3
3
  from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
 
5
5
  from letta.schemas.user import User, UserCreate, UserUpdate
6
- from letta.server.rest_api.utils import get_letta_server
6
+ from letta.server.rest_api.dependencies import get_letta_server
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from letta.schemas.user import User
@@ -1,12 +1,13 @@
1
- from typing import TYPE_CHECKING, Any, Dict, Optional
1
+ from typing import TYPE_CHECKING, Any, Dict
2
2
 
3
3
  import openai
4
- from fastapi import APIRouter, Body, Depends, Header
4
+ from fastapi import APIRouter, Body, Depends
5
5
  from fastapi.responses import StreamingResponse
6
6
 
7
7
  from letta.agents.voice_agent import VoiceAgent
8
8
  from letta.log import get_logger
9
- from letta.server.rest_api.utils import get_letta_server, get_user_message_from_chat_completions_request
9
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
10
+ from letta.server.rest_api.utils import get_user_message_from_chat_completions_request
10
11
  from letta.settings import model_settings
11
12
 
12
13
  if TYPE_CHECKING:
@@ -33,9 +34,9 @@ async def create_voice_chat_completions(
33
34
  agent_id: str,
34
35
  completion_request: Dict[str, Any] = Body(...), # The validation is soft in case providers like VAPI send extra params
35
36
  server: "SyncServer" = Depends(get_letta_server),
36
- user_id: Optional[str] = Header(None, alias="user_id"),
37
+ headers: HeaderParams = Depends(get_headers),
37
38
  ):
38
- actor = await server.user_manager.get_actor_or_default_async(actor_id=user_id)
39
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
39
40
 
40
41
  # Create OpenAI async client
41
42
  client = openai.AsyncClient(
@@ -3,7 +3,7 @@ import json
3
3
  import os
4
4
  import uuid
5
5
  from enum import Enum
6
- from typing import TYPE_CHECKING, AsyncGenerator, Dict, Iterable, List, Optional, Union, cast
6
+ from typing import AsyncGenerator, Dict, Iterable, List, Optional, Union, cast
7
7
 
8
8
  from fastapi import Header, HTTPException
9
9
  from openai.types.chat import ChatCompletionMessageParam
@@ -35,9 +35,6 @@ from letta.schemas.usage import LettaUsageStatistics
35
35
  from letta.schemas.user import User
36
36
  from letta.system import get_heartbeat, package_function_response
37
37
 
38
- if TYPE_CHECKING:
39
- from letta.server.server import SyncServer
40
-
41
38
  SENTRY_ENABLED = bool(os.getenv("SENTRY_DSN"))
42
39
 
43
40
  if SENTRY_ENABLED:
@@ -145,20 +142,6 @@ async def sse_async_generator(
145
142
  yield sse_formatter(SSE_FINISH_MSG)
146
143
 
147
144
 
148
- # TODO: why does this double up the interface?
149
- def get_letta_server() -> "SyncServer":
150
- # Check if a global server is already instantiated
151
- from letta.server.rest_api.app import server
152
-
153
- # assert isinstance(server, SyncServer)
154
- return server
155
-
156
-
157
- # Dependency to get user_id from headers
158
- def get_user_id(user_id: Optional[str] = Header(None, alias="user_id")) -> Optional[str]:
159
- return user_id
160
-
161
-
162
145
  def capture_sentry_exception(e: BaseException):
163
146
  """This will capture the exception in sentry, since the exception handler upstack (in FastAPI) won't catch it, because this may be a 200 response"""
164
147
  if SENTRY_ENABLED: