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,11 +1,11 @@
1
1
  from typing import List, Optional
2
2
 
3
- from fastapi import APIRouter, Depends, Header, HTTPException, Query
3
+ from fastapi import APIRouter, Depends, HTTPException, Query
4
4
 
5
5
  from letta.orm.errors import NoResultFound
6
6
  from letta.schemas.enums import JobStatus
7
7
  from letta.schemas.job import Job
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
  from letta.settings import settings
11
11
 
@@ -20,13 +20,13 @@ async def list_jobs(
20
20
  after: Optional[str] = Query(None, description="Cursor for pagination"),
21
21
  limit: Optional[int] = Query(50, description="Limit for pagination"),
22
22
  ascending: bool = Query(True, description="Whether to sort jobs oldest to newest (True, default) or newest to oldest (False)"),
23
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
23
+ headers: HeaderParams = Depends(get_headers),
24
24
  ):
25
25
  """
26
26
  List all jobs.
27
27
  TODO (cliandy): implementation for pagination
28
28
  """
29
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
29
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
30
30
 
31
31
  # TODO: add filtering by status
32
32
  return await server.job_manager.list_jobs_async(
@@ -42,7 +42,7 @@ async def list_jobs(
42
42
  @router.get("/active", response_model=List[Job], operation_id="list_active_jobs")
43
43
  async def list_active_jobs(
44
44
  server: "SyncServer" = Depends(get_letta_server),
45
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
45
+ headers: HeaderParams = Depends(get_headers),
46
46
  source_id: Optional[str] = Query(None, description="Only list jobs associated with the source."),
47
47
  before: Optional[str] = Query(None, description="Cursor for pagination"),
48
48
  after: Optional[str] = Query(None, description="Cursor for pagination"),
@@ -52,7 +52,7 @@ async def list_active_jobs(
52
52
  """
53
53
  List all active jobs.
54
54
  """
55
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
55
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
56
56
  return await server.job_manager.list_jobs_async(
57
57
  actor=actor,
58
58
  statuses=[JobStatus.created, JobStatus.running],
@@ -67,13 +67,13 @@ async def list_active_jobs(
67
67
  @router.get("/{job_id}", response_model=Job, operation_id="retrieve_job")
68
68
  async def retrieve_job(
69
69
  job_id: str,
70
- actor_id: Optional[str] = Header(None, alias="user_id"),
70
+ headers: HeaderParams = Depends(get_headers),
71
71
  server: "SyncServer" = Depends(get_letta_server),
72
72
  ):
73
73
  """
74
74
  Get the status of a job.
75
75
  """
76
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
76
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
77
77
 
78
78
  try:
79
79
  return await server.job_manager.get_job_by_id_async(job_id=job_id, actor=actor)
@@ -84,7 +84,7 @@ async def retrieve_job(
84
84
  @router.patch("/{job_id}/cancel", response_model=Job, operation_id="cancel_job")
85
85
  async def cancel_job(
86
86
  job_id: str,
87
- actor_id: Optional[str] = Header(None, alias="user_id"),
87
+ headers: HeaderParams = Depends(get_headers),
88
88
  server: "SyncServer" = Depends(get_letta_server),
89
89
  ):
90
90
  """
@@ -93,7 +93,7 @@ async def cancel_job(
93
93
  This endpoint marks a job as cancelled, which will cause any associated
94
94
  agent execution to terminate as soon as possible.
95
95
  """
96
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
96
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
97
97
  if not settings.track_agent_run:
98
98
  raise HTTPException(status_code=400, detail="Agent run tracking is disabled")
99
99
 
@@ -113,13 +113,13 @@ async def cancel_job(
113
113
  @router.delete("/{job_id}", response_model=Job, operation_id="delete_job")
114
114
  async def delete_job(
115
115
  job_id: str,
116
- actor_id: Optional[str] = Header(None, alias="user_id"),
116
+ headers: HeaderParams = Depends(get_headers),
117
117
  server: "SyncServer" = Depends(get_letta_server),
118
118
  ):
119
119
  """
120
120
  Delete a job by its job_id.
121
121
  """
122
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
122
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
123
123
 
124
124
  try:
125
125
  job = await server.job_manager.delete_job_by_id_async(job_id=job_id, actor=actor)
@@ -1,11 +1,11 @@
1
1
  from typing import TYPE_CHECKING, List, Optional
2
2
 
3
- from fastapi import APIRouter, Depends, Header, Query
3
+ from fastapi import APIRouter, Depends, Query
4
4
 
5
5
  from letta.schemas.embedding_config import EmbeddingConfig
6
6
  from letta.schemas.enums import ProviderCategory, ProviderType
7
7
  from letta.schemas.llm_config import LLMConfig
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
 
10
10
  if TYPE_CHECKING:
11
11
  from letta.server.server import SyncServer
@@ -19,11 +19,10 @@ async def list_llm_models(
19
19
  provider_name: Optional[str] = Query(None),
20
20
  provider_type: Optional[ProviderType] = Query(None),
21
21
  server: "SyncServer" = Depends(get_letta_server),
22
- actor_id: Optional[str] = Header(None, alias="user_id"),
23
- # Extract user_id from header, default to None if not present
22
+ headers: HeaderParams = Depends(get_headers),
24
23
  ):
25
24
  """List available LLM models using the asynchronous implementation for improved performance"""
26
- 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)
27
26
 
28
27
  models = await server.list_llm_models_async(
29
28
  provider_category=provider_category,
@@ -38,11 +37,10 @@ async def list_llm_models(
38
37
  @router.get("/embedding", response_model=List[EmbeddingConfig], operation_id="list_embedding_models")
39
38
  async def list_embedding_models(
40
39
  server: "SyncServer" = Depends(get_letta_server),
41
- actor_id: Optional[str] = Header(None, alias="user_id"),
42
- # Extract user_id from header, default to None if not present
40
+ headers: HeaderParams = Depends(get_headers),
43
41
  ):
44
42
  """List available embedding models using the asynchronous implementation for improved performance"""
45
- 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)
46
44
  models = await server.list_embedding_models_async(actor=actor)
47
45
 
48
46
  return models
@@ -1,6 +1,6 @@
1
1
  from typing import List, Literal, Optional
2
2
 
3
- from fastapi import APIRouter, Body, Depends, Header, Query
3
+ from fastapi import APIRouter, Body, Depends, Query
4
4
  from fastapi.exceptions import HTTPException
5
5
  from starlette.requests import Request
6
6
 
@@ -10,7 +10,7 @@ from letta.orm.errors import NoResultFound
10
10
  from letta.schemas.job import BatchJob, JobStatus, JobType, JobUpdate
11
11
  from letta.schemas.letta_request import CreateBatch
12
12
  from letta.schemas.letta_response import LettaBatchMessages
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
  from letta.settings import settings
16
16
 
@@ -19,23 +19,22 @@ router = APIRouter(prefix="/messages", tags=["messages"])
19
19
  logger = get_logger(__name__)
20
20
 
21
21
 
22
- # Batch APIs
23
-
24
-
25
22
  @router.post(
26
23
  "/batches",
27
24
  response_model=BatchJob,
28
- operation_id="create_batch_run",
25
+ operation_id="create_batch",
29
26
  )
30
- async def create_batch_run(
27
+ async def create_batch(
31
28
  request: Request,
32
29
  payload: CreateBatch = Body(..., description="Messages and config for all agents"),
33
30
  server: SyncServer = Depends(get_letta_server),
34
- actor_id: Optional[str] = Header(None, alias="user_id"),
31
+ headers: HeaderParams = Depends(get_headers),
35
32
  ):
36
33
  """
37
- Submit a batch of agent messages for asynchronous processing.
34
+ Submit a batch of agent runs for asynchronous processing.
35
+
38
36
  Creates a job that will fan out messages to all listed agents and process them in parallel.
37
+ The request will be rejected if it exceeds 256MB.
39
38
  """
40
39
  # Reject requests greater than 256Mbs
41
40
  max_bytes = 256 * 1024 * 1024
@@ -48,7 +47,7 @@ async def create_batch_run(
48
47
  if not settings.enable_batch_job_polling:
49
48
  logger.warning("Batch job polling is disabled. Enable batch processing by setting LETTA_ENABLE_BATCH_JOB_POLLING to True.")
50
49
 
51
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
50
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
52
51
  batch_job = BatchJob(
53
52
  user_id=actor.id,
54
53
  status=JobStatus.running,
@@ -76,10 +75,7 @@ async def create_batch_run(
76
75
 
77
76
  # TODO: update run metadata
78
77
  except Exception as e:
79
- import traceback
80
-
81
- print("Error creating batch job", e)
82
- traceback.print_exc()
78
+ logger.error(f"Error creating batch job: {e}")
83
79
 
84
80
  # mark job as failed
85
81
  await server.job_manager.update_job_by_id_async(job_id=batch_job.id, job_update=JobUpdate(status=JobStatus.failed), actor=actor)
@@ -87,16 +83,16 @@ async def create_batch_run(
87
83
  return batch_job
88
84
 
89
85
 
90
- @router.get("/batches/{batch_id}", response_model=BatchJob, operation_id="retrieve_batch_run")
91
- async def retrieve_batch_run(
86
+ @router.get("/batches/{batch_id}", response_model=BatchJob, operation_id="retrieve_batch")
87
+ async def retrieve_batch(
92
88
  batch_id: str,
93
- actor_id: Optional[str] = Header(None, alias="user_id"),
89
+ headers: HeaderParams = Depends(get_headers),
94
90
  server: "SyncServer" = Depends(get_letta_server),
95
91
  ):
96
92
  """
97
- Get the status of a batch run.
93
+ Retrieve the status and details of a batch run.
98
94
  """
99
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
95
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
100
96
 
101
97
  try:
102
98
  job = await server.job_manager.get_job_by_id_async(job_id=batch_id, actor=actor)
@@ -105,27 +101,45 @@ async def retrieve_batch_run(
105
101
  raise HTTPException(status_code=404, detail="Batch not found")
106
102
 
107
103
 
108
- @router.get("/batches", response_model=List[BatchJob], operation_id="list_batch_runs")
109
- async def list_batch_runs(
110
- actor_id: Optional[str] = Header(None, alias="user_id"),
104
+ @router.get("/batches", response_model=List[BatchJob], operation_id="list_batches")
105
+ async def list_batches(
106
+ before: Optional[str] = Query(
107
+ None, description="Job ID cursor for pagination. Returns jobs that come before this job ID in the specified sort order"
108
+ ),
109
+ after: Optional[str] = Query(
110
+ None, description="Job ID cursor for pagination. Returns jobs that come after this job ID in the specified sort order"
111
+ ),
112
+ limit: Optional[int] = Query(100, description="Maximum number of jobs to return"),
113
+ order: Literal["asc", "desc"] = Query(
114
+ "desc", description="Sort order for jobs by creation time. 'asc' for oldest first, 'desc' for newest first"
115
+ ),
116
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
117
+ headers: HeaderParams = Depends(get_headers),
111
118
  server: "SyncServer" = Depends(get_letta_server),
112
119
  ):
113
120
  """
114
121
  List all batch runs.
115
122
  """
116
- # TODO: filter
117
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
118
-
119
- jobs = server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running], job_type=JobType.BATCH)
123
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
124
+
125
+ jobs = server.job_manager.list_jobs(
126
+ actor=actor,
127
+ statuses=[JobStatus.created, JobStatus.running],
128
+ job_type=JobType.BATCH,
129
+ before=before,
130
+ after=after,
131
+ limit=limit,
132
+ ascending=(order == "asc"),
133
+ )
120
134
  return [BatchJob.from_job(job) for job in jobs]
121
135
 
122
136
 
123
137
  @router.get(
124
138
  "/batches/{batch_id}/messages",
125
139
  response_model=LettaBatchMessages,
126
- operation_id="list_batch_messages",
140
+ operation_id="list_messages_for_batch",
127
141
  )
128
- async def list_batch_messages(
142
+ async def list_messages_for_batch(
129
143
  batch_id: str,
130
144
  before: Optional[str] = Query(
131
145
  None, description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order"
@@ -137,14 +151,17 @@ async def list_batch_messages(
137
151
  order: Literal["asc", "desc"] = Query(
138
152
  "desc", description="Sort order for messages by creation time. 'asc' for oldest first, 'desc' for newest first"
139
153
  ),
154
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
140
155
  agent_id: Optional[str] = Query(None, description="Filter messages by agent ID"),
141
- actor_id: Optional[str] = Header(None, alias="user_id"),
156
+ headers: HeaderParams = Depends(get_headers),
142
157
  server: SyncServer = Depends(get_letta_server),
143
158
  ):
144
- """Get response messages for a specific batch job."""
145
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
159
+ """
160
+ Get response messages for a specific batch job.
161
+ """
162
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
146
163
 
147
- # First, verify the batch job exists and the user has access to it
164
+ # Verify the batch job exists and the user has access to it
148
165
  try:
149
166
  job = await server.job_manager.get_job_by_id_async(job_id=batch_id, actor=actor)
150
167
  BatchJob.from_job(job)
@@ -159,16 +176,16 @@ async def list_batch_messages(
159
176
  return LettaBatchMessages(messages=messages)
160
177
 
161
178
 
162
- @router.patch("/batches/{batch_id}/cancel", operation_id="cancel_batch_run")
163
- async def cancel_batch_run(
179
+ @router.patch("/batches/{batch_id}/cancel", operation_id="cancel_batch")
180
+ async def cancel_batch(
164
181
  batch_id: str,
165
182
  server: "SyncServer" = Depends(get_letta_server),
166
- actor_id: Optional[str] = Header(None, alias="user_id"),
183
+ headers: HeaderParams = Depends(get_headers),
167
184
  ):
168
185
  """
169
186
  Cancel a batch run.
170
187
  """
171
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
188
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
172
189
 
173
190
  try:
174
191
  job = await server.job_manager.get_job_by_id_async(job_id=batch_id, actor=actor)
@@ -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.organization import Organization, OrganizationCreate, OrganizationUpdate
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.server.server import SyncServer
@@ -1,13 +1,13 @@
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, status
3
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
4
4
  from fastapi.responses import JSONResponse
5
5
 
6
6
  from letta.errors import LLMAuthenticationError
7
7
  from letta.orm.errors import NoResultFound
8
8
  from letta.schemas.enums import ProviderType
9
9
  from letta.schemas.providers import Provider, ProviderCheck, ProviderCreate, ProviderUpdate
10
- from letta.server.rest_api.utils import get_letta_server
10
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from letta.server.server import SyncServer
@@ -17,20 +17,31 @@ router = APIRouter(prefix="/providers", tags=["providers"])
17
17
 
18
18
  @router.get("/", response_model=List[Provider], operation_id="list_providers")
19
19
  async def list_providers(
20
- name: Optional[str] = Query(None),
21
- provider_type: Optional[ProviderType] = Query(None),
22
- after: Optional[str] = Query(None),
23
- limit: Optional[int] = Query(50),
24
- actor_id: Optional[str] = Header(None, alias="user_id"),
20
+ before: Optional[str] = Query(
21
+ None,
22
+ description="Provider ID cursor for pagination. Returns providers that come before this provider ID in the specified sort order",
23
+ ),
24
+ after: Optional[str] = Query(
25
+ None,
26
+ description="Provider ID cursor for pagination. Returns providers that come after this provider ID in the specified sort order",
27
+ ),
28
+ limit: Optional[int] = Query(50, description="Maximum number of providers to return"),
29
+ order: Literal["asc", "desc"] = Query(
30
+ "desc", description="Sort order for providers by creation time. 'asc' for oldest first, 'desc' for newest first"
31
+ ),
32
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
33
+ name: Optional[str] = Query(None, description="Filter providers by name"),
34
+ provider_type: Optional[ProviderType] = Query(None, description="Filter providers by type"),
35
+ headers: HeaderParams = Depends(get_headers),
25
36
  server: "SyncServer" = Depends(get_letta_server),
26
37
  ):
27
38
  """
28
- Get a list of all custom providers in the database
39
+ Get a list of all custom providers.
29
40
  """
30
41
  try:
31
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
42
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
32
43
  providers = await server.provider_manager.list_providers_async(
33
- after=after, limit=limit, actor=actor, name=name, provider_type=provider_type
44
+ before=before, after=after, limit=limit, actor=actor, name=name, provider_type=provider_type, ascending=(order == "asc")
34
45
  )
35
46
  except HTTPException:
36
47
  raise
@@ -39,16 +50,29 @@ async def list_providers(
39
50
  return providers
40
51
 
41
52
 
53
+ @router.get("/{provider_id}", response_model=Provider, operation_id="retrieve_provider")
54
+ async def retrieve_provider(
55
+ provider_id: str,
56
+ headers: HeaderParams = Depends(get_headers),
57
+ server: "SyncServer" = Depends(get_letta_server),
58
+ ):
59
+ """
60
+ Get a provider by ID.
61
+ """
62
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
63
+ return await server.provider_manager.get_provider_async(provider_id=provider_id, actor=actor)
64
+
65
+
42
66
  @router.post("/", response_model=Provider, operation_id="create_provider")
43
67
  async def create_provider(
44
68
  request: ProviderCreate = Body(...),
45
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
69
+ headers: HeaderParams = Depends(get_headers),
46
70
  server: "SyncServer" = Depends(get_letta_server),
47
71
  ):
48
72
  """
49
- Create a new custom provider
73
+ Create a new custom provider.
50
74
  """
51
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
75
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
52
76
  for field_name in request.model_fields:
53
77
  value = getattr(request, field_name, None)
54
78
  if isinstance(value, str) and value == "":
@@ -64,13 +88,13 @@ async def create_provider(
64
88
  async def modify_provider(
65
89
  provider_id: str,
66
90
  request: ProviderUpdate = Body(...),
67
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
91
+ headers: HeaderParams = Depends(get_headers),
68
92
  server: "SyncServer" = Depends(get_letta_server),
69
93
  ):
70
94
  """
71
- Update an existing custom provider
95
+ Update an existing custom provider.
72
96
  """
73
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
97
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
74
98
  return await server.provider_manager.update_provider_async(provider_id=provider_id, provider_update=request, actor=actor)
75
99
 
76
100
 
@@ -79,6 +103,9 @@ async def check_provider(
79
103
  request: ProviderCheck = Body(...),
80
104
  server: "SyncServer" = Depends(get_letta_server),
81
105
  ):
106
+ """
107
+ Verify the API key and additional parameters for a provider.
108
+ """
82
109
  try:
83
110
  if request.base_url and len(request.base_url) == 0:
84
111
  # set to null if empty string
@@ -96,14 +123,14 @@ async def check_provider(
96
123
  @router.delete("/{provider_id}", response_model=None, operation_id="delete_provider")
97
124
  async def delete_provider(
98
125
  provider_id: str,
99
- actor_id: Optional[str] = Header(None, alias="user_id"),
126
+ headers: HeaderParams = Depends(get_headers),
100
127
  server: "SyncServer" = Depends(get_letta_server),
101
128
  ):
102
129
  """
103
- Delete an existing custom provider
130
+ Delete an existing custom provider.
104
131
  """
105
132
  try:
106
- 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)
107
134
  await server.provider_manager.delete_provider_by_id_async(provider_id=provider_id, actor=actor)
108
135
  return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Provider id={provider_id} successfully deleted"})
109
136
  except NoResultFound:
@@ -1,25 +1,25 @@
1
1
  from datetime import timedelta
2
2
  from typing import Annotated, List, Literal, Optional
3
3
 
4
- from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
4
+ from fastapi import APIRouter, Body, Depends, HTTPException, Query
5
5
  from pydantic import Field
6
6
 
7
7
  from letta.data_sources.redis_client import NoopAsyncRedisClient, get_redis_client
8
8
  from letta.helpers.datetime_helpers import get_utc_time
9
9
  from letta.orm.errors import NoResultFound
10
- from letta.schemas.enums import JobStatus, JobType, MessageRole
10
+ from letta.schemas.enums import JobStatus, JobType
11
11
  from letta.schemas.letta_message import LettaMessageUnion
12
12
  from letta.schemas.letta_request import RetrieveStreamRequest
13
13
  from letta.schemas.openai.chat_completion_response import UsageStatistics
14
14
  from letta.schemas.run import Run
15
15
  from letta.schemas.step import Step
16
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
16
17
  from letta.server.rest_api.redis_stream_manager import redis_sse_stream_generator
17
18
  from letta.server.rest_api.streaming_response import (
18
19
  StreamingResponseWithStatusCode,
19
20
  add_keepalive_to_stream,
20
21
  cancellation_aware_stream_wrapper,
21
22
  )
22
- from letta.server.rest_api.utils import get_letta_server
23
23
  from letta.server.server import SyncServer
24
24
  from letta.settings import settings
25
25
 
@@ -38,12 +38,12 @@ def list_runs(
38
38
  False,
39
39
  description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default)",
40
40
  ),
41
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
41
+ headers: HeaderParams = Depends(get_headers),
42
42
  ):
43
43
  """
44
44
  List all runs.
45
45
  """
46
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
46
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
47
47
 
48
48
  runs = [
49
49
  Run.from_job(job)
@@ -68,12 +68,12 @@ def list_active_runs(
68
68
  server: "SyncServer" = Depends(get_letta_server),
69
69
  agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
70
70
  background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
71
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
71
+ headers: HeaderParams = Depends(get_headers),
72
72
  ):
73
73
  """
74
74
  List all active runs.
75
75
  """
76
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
76
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
77
77
 
78
78
  active_runs = server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running], job_type=JobType.RUN)
79
79
  active_runs = [Run.from_job(job) for job in active_runs]
@@ -90,13 +90,13 @@ def list_active_runs(
90
90
  @router.get("/{run_id}", response_model=Run, operation_id="retrieve_run")
91
91
  def retrieve_run(
92
92
  run_id: str,
93
- actor_id: Optional[str] = Header(None, alias="user_id"),
93
+ headers: HeaderParams = Depends(get_headers),
94
94
  server: "SyncServer" = Depends(get_letta_server),
95
95
  ):
96
96
  """
97
97
  Get the status of a run.
98
98
  """
99
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
99
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
100
100
 
101
101
  try:
102
102
  job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
@@ -118,7 +118,7 @@ RunMessagesResponse = Annotated[
118
118
  async def list_run_messages(
119
119
  run_id: str,
120
120
  server: "SyncServer" = Depends(get_letta_server),
121
- actor_id: Optional[str] = Header(None, alias="user_id"),
121
+ headers: HeaderParams = Depends(get_headers),
122
122
  before: Optional[str] = Query(
123
123
  None, description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order"
124
124
  ),
@@ -131,7 +131,7 @@ async def list_run_messages(
131
131
  ),
132
132
  ):
133
133
  """Get response messages associated with a run."""
134
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
134
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
135
135
 
136
136
  try:
137
137
  messages = server.job_manager.get_run_messages(
@@ -150,13 +150,13 @@ async def list_run_messages(
150
150
  @router.get("/{run_id}/usage", response_model=UsageStatistics, operation_id="retrieve_run_usage")
151
151
  def retrieve_run_usage(
152
152
  run_id: str,
153
- actor_id: Optional[str] = Header(None, alias="user_id"),
153
+ headers: HeaderParams = Depends(get_headers),
154
154
  server: "SyncServer" = Depends(get_letta_server),
155
155
  ):
156
156
  """
157
157
  Get usage statistics for a run.
158
158
  """
159
- actor = server.user_manager.get_user_or_default(user_id=actor_id)
159
+ actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
160
160
 
161
161
  try:
162
162
  usage = server.job_manager.get_job_usage(job_id=run_id, actor=actor)
@@ -173,7 +173,7 @@ def retrieve_run_usage(
173
173
  async def list_run_steps(
174
174
  run_id: str,
175
175
  server: "SyncServer" = Depends(get_letta_server),
176
- actor_id: Optional[str] = Header(None, alias="user_id"),
176
+ headers: HeaderParams = Depends(get_headers),
177
177
  before: Optional[str] = Query(None, description="Cursor for pagination"),
178
178
  after: Optional[str] = Query(None, description="Cursor for pagination"),
179
179
  limit: Optional[int] = Query(100, description="Maximum number of messages to return"),
@@ -197,7 +197,7 @@ async def list_run_steps(
197
197
  if order not in ["asc", "desc"]:
198
198
  raise HTTPException(status_code=400, detail="Order must be 'asc' or 'desc'")
199
199
 
200
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
200
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
201
201
 
202
202
  try:
203
203
  steps = server.job_manager.get_job_steps(
@@ -216,13 +216,13 @@ async def list_run_steps(
216
216
  @router.delete("/{run_id}", response_model=Run, operation_id="delete_run")
217
217
  async def delete_run(
218
218
  run_id: str,
219
- actor_id: Optional[str] = Header(None, alias="user_id"),
219
+ headers: HeaderParams = Depends(get_headers),
220
220
  server: "SyncServer" = Depends(get_letta_server),
221
221
  ):
222
222
  """
223
223
  Delete a run by its run_id.
224
224
  """
225
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
225
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
226
226
 
227
227
  try:
228
228
  job = await server.job_manager.delete_job_by_id_async(job_id=run_id, actor=actor)
@@ -266,10 +266,10 @@ async def delete_run(
266
266
  async def retrieve_stream(
267
267
  run_id: str,
268
268
  request: RetrieveStreamRequest = Body(None),
269
- actor_id: Optional[str] = Header(None, alias="user_id"),
269
+ headers: HeaderParams = Depends(get_headers),
270
270
  server: "SyncServer" = Depends(get_letta_server),
271
271
  ):
272
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
272
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
273
273
  try:
274
274
  job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
275
275
  except NoResultFound: