letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.12.0.dev20251009104148__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 (151) hide show
  1. letta/__init__.py +1 -1
  2. letta/adapters/letta_llm_adapter.py +1 -0
  3. letta/adapters/letta_llm_request_adapter.py +0 -1
  4. letta/adapters/letta_llm_stream_adapter.py +7 -2
  5. letta/adapters/simple_llm_request_adapter.py +88 -0
  6. letta/adapters/simple_llm_stream_adapter.py +192 -0
  7. letta/agents/agent_loop.py +6 -0
  8. letta/agents/ephemeral_summary_agent.py +2 -1
  9. letta/agents/helpers.py +142 -6
  10. letta/agents/letta_agent.py +13 -33
  11. letta/agents/letta_agent_batch.py +2 -4
  12. letta/agents/letta_agent_v2.py +87 -77
  13. letta/agents/letta_agent_v3.py +927 -0
  14. letta/agents/voice_agent.py +2 -6
  15. letta/constants.py +8 -4
  16. letta/database_utils.py +161 -0
  17. letta/errors.py +40 -0
  18. letta/functions/function_sets/base.py +84 -4
  19. letta/functions/function_sets/multi_agent.py +0 -3
  20. letta/functions/schema_generator.py +113 -71
  21. letta/groups/dynamic_multi_agent.py +3 -2
  22. letta/groups/helpers.py +1 -2
  23. letta/groups/round_robin_multi_agent.py +3 -2
  24. letta/groups/sleeptime_multi_agent.py +3 -2
  25. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  26. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  27. letta/groups/supervisor_multi_agent.py +84 -80
  28. letta/helpers/converters.py +3 -0
  29. letta/helpers/message_helper.py +4 -0
  30. letta/helpers/tool_rule_solver.py +92 -5
  31. letta/interfaces/anthropic_streaming_interface.py +409 -0
  32. letta/interfaces/gemini_streaming_interface.py +296 -0
  33. letta/interfaces/openai_streaming_interface.py +752 -1
  34. letta/llm_api/anthropic_client.py +127 -16
  35. letta/llm_api/bedrock_client.py +4 -2
  36. letta/llm_api/deepseek_client.py +4 -1
  37. letta/llm_api/google_vertex_client.py +124 -42
  38. letta/llm_api/groq_client.py +4 -1
  39. letta/llm_api/llm_api_tools.py +11 -4
  40. letta/llm_api/llm_client_base.py +6 -2
  41. letta/llm_api/openai.py +32 -2
  42. letta/llm_api/openai_client.py +423 -18
  43. letta/llm_api/xai_client.py +4 -1
  44. letta/main.py +9 -5
  45. letta/memory.py +1 -0
  46. letta/orm/__init__.py +2 -1
  47. letta/orm/agent.py +10 -0
  48. letta/orm/block.py +7 -16
  49. letta/orm/blocks_agents.py +8 -2
  50. letta/orm/files_agents.py +2 -0
  51. letta/orm/job.py +7 -5
  52. letta/orm/mcp_oauth.py +1 -0
  53. letta/orm/message.py +21 -6
  54. letta/orm/organization.py +2 -0
  55. letta/orm/provider.py +6 -2
  56. letta/orm/run.py +71 -0
  57. letta/orm/run_metrics.py +82 -0
  58. letta/orm/sandbox_config.py +7 -1
  59. letta/orm/sqlalchemy_base.py +0 -306
  60. letta/orm/step.py +6 -5
  61. letta/orm/step_metrics.py +5 -5
  62. letta/otel/tracing.py +28 -3
  63. letta/plugins/defaults.py +4 -4
  64. letta/prompts/system_prompts/__init__.py +2 -0
  65. letta/prompts/system_prompts/letta_v1.py +25 -0
  66. letta/schemas/agent.py +3 -2
  67. letta/schemas/agent_file.py +9 -3
  68. letta/schemas/block.py +23 -10
  69. letta/schemas/enums.py +21 -2
  70. letta/schemas/job.py +17 -4
  71. letta/schemas/letta_message_content.py +71 -2
  72. letta/schemas/letta_stop_reason.py +5 -5
  73. letta/schemas/llm_config.py +53 -3
  74. letta/schemas/memory.py +1 -1
  75. letta/schemas/message.py +564 -117
  76. letta/schemas/openai/responses_request.py +64 -0
  77. letta/schemas/providers/__init__.py +2 -0
  78. letta/schemas/providers/anthropic.py +16 -0
  79. letta/schemas/providers/ollama.py +115 -33
  80. letta/schemas/providers/openrouter.py +52 -0
  81. letta/schemas/providers/vllm.py +2 -1
  82. letta/schemas/run.py +48 -42
  83. letta/schemas/run_metrics.py +21 -0
  84. letta/schemas/step.py +2 -2
  85. letta/schemas/step_metrics.py +1 -1
  86. letta/schemas/tool.py +15 -107
  87. letta/schemas/tool_rule.py +88 -5
  88. letta/serialize_schemas/marshmallow_agent.py +1 -0
  89. letta/server/db.py +79 -408
  90. letta/server/rest_api/app.py +61 -10
  91. letta/server/rest_api/dependencies.py +14 -0
  92. letta/server/rest_api/redis_stream_manager.py +19 -8
  93. letta/server/rest_api/routers/v1/agents.py +364 -292
  94. letta/server/rest_api/routers/v1/blocks.py +14 -20
  95. letta/server/rest_api/routers/v1/identities.py +45 -110
  96. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  97. letta/server/rest_api/routers/v1/jobs.py +23 -6
  98. letta/server/rest_api/routers/v1/messages.py +1 -1
  99. letta/server/rest_api/routers/v1/runs.py +149 -99
  100. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  101. letta/server/rest_api/routers/v1/tools.py +281 -594
  102. letta/server/rest_api/routers/v1/voice.py +1 -1
  103. letta/server/rest_api/streaming_response.py +29 -29
  104. letta/server/rest_api/utils.py +122 -64
  105. letta/server/server.py +160 -887
  106. letta/services/agent_manager.py +236 -919
  107. letta/services/agent_serialization_manager.py +16 -0
  108. letta/services/archive_manager.py +0 -100
  109. letta/services/block_manager.py +211 -168
  110. letta/services/context_window_calculator/token_counter.py +1 -1
  111. letta/services/file_manager.py +1 -1
  112. letta/services/files_agents_manager.py +24 -33
  113. letta/services/group_manager.py +0 -142
  114. letta/services/helpers/agent_manager_helper.py +7 -2
  115. letta/services/helpers/run_manager_helper.py +69 -0
  116. letta/services/job_manager.py +96 -411
  117. letta/services/lettuce/__init__.py +6 -0
  118. letta/services/lettuce/lettuce_client_base.py +86 -0
  119. letta/services/mcp_manager.py +38 -6
  120. letta/services/message_manager.py +165 -362
  121. letta/services/organization_manager.py +0 -36
  122. letta/services/passage_manager.py +0 -345
  123. letta/services/provider_manager.py +0 -80
  124. letta/services/run_manager.py +364 -0
  125. letta/services/sandbox_config_manager.py +0 -234
  126. letta/services/step_manager.py +62 -39
  127. letta/services/summarizer/summarizer.py +9 -7
  128. letta/services/telemetry_manager.py +0 -16
  129. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  130. letta/services/tool_executor/core_tool_executor.py +397 -2
  131. letta/services/tool_executor/files_tool_executor.py +3 -3
  132. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  133. letta/services/tool_executor/tool_execution_manager.py +6 -8
  134. letta/services/tool_executor/tool_executor_base.py +3 -3
  135. letta/services/tool_manager.py +85 -339
  136. letta/services/tool_sandbox/base.py +24 -13
  137. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  138. letta/services/tool_schema_generator.py +123 -0
  139. letta/services/user_manager.py +0 -99
  140. letta/settings.py +20 -4
  141. letta/system.py +5 -1
  142. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/METADATA +3 -5
  143. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/RECORD +146 -135
  144. letta/agents/temporal/activities/__init__.py +0 -4
  145. letta/agents/temporal/activities/example_activity.py +0 -7
  146. letta/agents/temporal/activities/prepare_messages.py +0 -10
  147. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  148. letta/agents/temporal/types.py +0 -25
  149. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/WHEEL +0 -0
  150. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/entry_points.txt +0 -0
  151. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/licenses/LICENSE +0 -0
@@ -7,12 +7,13 @@ from pydantic import Field
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
10
+ from letta.schemas.enums import RunStatus
11
11
  from letta.schemas.letta_message import LettaMessageUnion
12
12
  from letta.schemas.letta_request import RetrieveStreamRequest
13
13
  from letta.schemas.letta_stop_reason import StopReasonType
14
14
  from letta.schemas.openai.chat_completion_response import UsageStatistics
15
15
  from letta.schemas.run import Run
16
+ from letta.schemas.run_metrics import RunMetrics
16
17
  from letta.schemas.step import Step
17
18
  from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
18
19
  from letta.server.rest_api.redis_stream_manager import redis_sse_stream_generator
@@ -22,81 +23,124 @@ from letta.server.rest_api.streaming_response import (
22
23
  cancellation_aware_stream_wrapper,
23
24
  )
24
25
  from letta.server.server import SyncServer
26
+ from letta.services.lettuce import LettuceClient
27
+ from letta.services.run_manager import RunManager
25
28
  from letta.settings import settings
26
29
 
27
30
  router = APIRouter(prefix="/runs", tags=["runs"])
28
31
 
29
32
 
33
+ def convert_statuses_to_enum(statuses: Optional[List[str]]) -> Optional[List[RunStatus]]:
34
+ """Convert a list of status strings to RunStatus enum values.
35
+
36
+ Args:
37
+ statuses: List of status strings or None
38
+
39
+ Returns:
40
+ List of RunStatus enum values or None if input is None
41
+ """
42
+ if statuses is None:
43
+ return None
44
+ return [RunStatus(status) for status in statuses]
45
+
46
+
30
47
  @router.get("/", response_model=List[Run], operation_id="list_runs")
31
- def list_runs(
48
+ async def list_runs(
32
49
  server: "SyncServer" = Depends(get_letta_server),
33
- agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
50
+ agent_id: Optional[str] = Query(None, description="The unique identifier of the agent associated with the run."),
51
+ agent_ids: Optional[List[str]] = Query(
52
+ None,
53
+ description="The unique identifiers of the agents associated with the run. Deprecated in favor of agent_id field.",
54
+ deprecated=True,
55
+ ),
56
+ statuses: Optional[List[str]] = Query(None, description="Filter runs by status. Can specify multiple statuses."),
34
57
  background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
35
58
  stop_reason: Optional[StopReasonType] = Query(None, description="Filter runs by stop reason."),
36
- after: Optional[str] = Query(None, description="Cursor for pagination"),
37
- before: Optional[str] = Query(None, description="Cursor for pagination"),
38
- limit: Optional[int] = Query(50, description="Maximum number of runs to return"),
59
+ before: Optional[str] = Query(
60
+ None, description="Run ID cursor for pagination. Returns runs that come before this run ID in the specified sort order"
61
+ ),
62
+ after: Optional[str] = Query(
63
+ None, description="Run ID cursor for pagination. Returns runs that come after this run ID in the specified sort order"
64
+ ),
65
+ limit: Optional[int] = Query(100, description="Maximum number of runs to return"),
66
+ order: Literal["asc", "desc"] = Query(
67
+ "desc", description="Sort order for runs by creation time. 'asc' for oldest first, 'desc' for newest first"
68
+ ),
69
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
39
70
  active: bool = Query(False, description="Filter for active runs."),
40
71
  ascending: bool = Query(
41
72
  False,
42
- description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default)",
73
+ description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default). Deprecated in favor of order field.",
74
+ deprecated=True,
43
75
  ),
44
76
  headers: HeaderParams = Depends(get_headers),
45
77
  ):
46
78
  """
47
79
  List all runs.
48
80
  """
49
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
50
- statuses = None
51
- if active:
52
- statuses = [JobStatus.created, JobStatus.running]
53
-
54
- runs = [
55
- Run.from_job(job)
56
- for job in server.job_manager.list_jobs(
57
- actor=actor,
58
- statuses=statuses,
59
- job_type=JobType.RUN,
60
- limit=limit,
61
- before=before,
62
- after=after,
63
- ascending=False,
64
- stop_reason=stop_reason,
65
- )
66
- ]
67
- if agent_ids:
68
- runs = [run for run in runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
69
- if background is not None:
70
- runs = [run for run in runs if "background" in run.metadata and run.metadata["background"] == background]
81
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
82
+ runs_manager = RunManager()
83
+
84
+ # Handle backwards compatibility: if statuses not provided but active=True, filter by active statuses
85
+ if statuses is None and active:
86
+ statuses = [RunStatus.created, RunStatus.running]
87
+
88
+ if agent_id:
89
+ # NOTE: we are deprecating agent_ids so this will the primary path soon
90
+ agent_ids = [agent_id]
91
+
92
+ # Handle backward compatibility: if ascending is explicitly set, use it; otherwise use order
93
+ if ascending is not False:
94
+ # ascending was explicitly set to True
95
+ sort_ascending = ascending
96
+ else:
97
+ # Use the new order parameter
98
+ sort_ascending = order == "asc"
99
+
100
+ # Convert string statuses to RunStatus enum
101
+ parsed_statuses = convert_statuses_to_enum(statuses)
102
+
103
+ runs = await runs_manager.list_runs(
104
+ actor=actor,
105
+ agent_ids=agent_ids,
106
+ statuses=parsed_statuses,
107
+ limit=limit,
108
+ before=before,
109
+ after=after,
110
+ ascending=sort_ascending,
111
+ stop_reason=stop_reason,
112
+ background=background,
113
+ )
71
114
  return runs
72
115
 
73
116
 
74
117
  @router.get("/active", response_model=List[Run], operation_id="list_active_runs", deprecated=True)
75
- def list_active_runs(
118
+ async def list_active_runs(
76
119
  server: "SyncServer" = Depends(get_letta_server),
77
- agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
120
+ agent_id: Optional[str] = Query(None, description="The unique identifier of the agent associated with the run."),
78
121
  background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
79
122
  headers: HeaderParams = Depends(get_headers),
80
123
  ):
81
124
  """
82
125
  List all active runs.
83
126
  """
84
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
85
-
86
- active_runs = server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running], job_type=JobType.RUN)
87
- active_runs = [Run.from_job(job) for job in active_runs]
127
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
128
+ runs_manager = RunManager()
88
129
 
89
- if agent_ids:
90
- active_runs = [run for run in active_runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
130
+ if agent_id:
131
+ agent_ids = [agent_id]
132
+ else:
133
+ agent_ids = None
91
134
 
92
- if background is not None:
93
- active_runs = [run for run in active_runs if "background" in run.metadata and run.metadata["background"] == background]
135
+ active_runs = await runs_manager.list_runs(
136
+ actor=actor, statuses=[RunStatus.created, RunStatus.running], agent_ids=agent_ids, background=background
137
+ )
94
138
 
95
139
  return active_runs
96
140
 
97
141
 
98
142
  @router.get("/{run_id}", response_model=Run, operation_id="retrieve_run")
99
- def retrieve_run(
143
+ async def retrieve_run(
100
144
  run_id: str,
101
145
  headers: HeaderParams = Depends(get_headers),
102
146
  server: "SyncServer" = Depends(get_letta_server),
@@ -104,11 +148,29 @@ def retrieve_run(
104
148
  """
105
149
  Get the status of a run.
106
150
  """
107
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
151
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
152
+ runs_manager = RunManager()
108
153
 
109
154
  try:
110
- job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
111
- return Run.from_job(job)
155
+ run = await runs_manager.get_run_by_id(run_id=run_id, actor=actor)
156
+
157
+ use_lettuce = run.metadata and run.metadata.get("lettuce")
158
+ if use_lettuce and run.status not in [RunStatus.completed, RunStatus.failed, RunStatus.cancelled]:
159
+ lettuce_client = await LettuceClient.create()
160
+ status = await lettuce_client.get_status()
161
+
162
+ # Map the status to our enum
163
+ run_status = run.status
164
+ if status == "RUNNING":
165
+ run_status = RunStatus.running
166
+ elif status == "COMPLETED":
167
+ run_status = RunStatus.completed
168
+ elif status == "FAILED":
169
+ run_status = RunStatus.failed
170
+ elif status == "CANCELLED":
171
+ run_status = RunStatus.cancelled
172
+ run.status = run_status
173
+ return run
112
174
  except NoResultFound:
113
175
  raise HTTPException(status_code=404, detail="Run not found")
114
176
 
@@ -137,26 +199,15 @@ async def list_run_messages(
137
199
  order: Literal["asc", "desc"] = Query(
138
200
  "asc", description="Sort order for messages by creation time. 'asc' for oldest first, 'desc' for newest first"
139
201
  ),
202
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
140
203
  ):
141
204
  """Get response messages associated with a run."""
142
205
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
143
-
144
- try:
145
- messages = server.job_manager.get_run_messages(
146
- run_id=run_id,
147
- actor=actor,
148
- limit=limit,
149
- before=before,
150
- after=after,
151
- ascending=(order == "asc"),
152
- )
153
- return messages
154
- except NoResultFound as e:
155
- raise HTTPException(status_code=404, detail=str(e))
206
+ return await server.run_manager.get_run_messages(run_id=run_id, actor=actor, before=before, after=after, limit=limit, order=order)
156
207
 
157
208
 
158
209
  @router.get("/{run_id}/usage", response_model=UsageStatistics, operation_id="retrieve_run_usage")
159
- def retrieve_run_usage(
210
+ async def retrieve_run_usage(
160
211
  run_id: str,
161
212
  headers: HeaderParams = Depends(get_headers),
162
213
  server: "SyncServer" = Depends(get_letta_server),
@@ -164,15 +215,33 @@ def retrieve_run_usage(
164
215
  """
165
216
  Get usage statistics for a run.
166
217
  """
167
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
218
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
219
+ runs_manager = RunManager()
168
220
 
169
221
  try:
170
- usage = server.job_manager.get_job_usage(job_id=run_id, actor=actor)
222
+ usage = await runs_manager.get_run_usage(run_id=run_id, actor=actor)
171
223
  return usage
172
224
  except NoResultFound:
173
225
  raise HTTPException(status_code=404, detail=f"Run '{run_id}' not found")
174
226
 
175
227
 
228
+ @router.get("/{run_id}/metrics", response_model=RunMetrics, operation_id="retrieve_metrics_for_run")
229
+ async def retrieve_metrics_for_run(
230
+ run_id: str,
231
+ headers: HeaderParams = Depends(get_headers),
232
+ server: "SyncServer" = Depends(get_letta_server),
233
+ ):
234
+ """
235
+ Get run metrics by run ID.
236
+ """
237
+ try:
238
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
239
+ runs_manager = RunManager()
240
+ return await runs_manager.get_run_metrics_async(run_id=run_id, actor=actor)
241
+ except NoResultFound:
242
+ raise HTTPException(status_code=404, detail="Run metrics not found")
243
+
244
+
176
245
  @router.get(
177
246
  "/{run_id}/steps",
178
247
  response_model=List[Step],
@@ -185,40 +254,25 @@ async def list_run_steps(
185
254
  before: Optional[str] = Query(None, description="Cursor for pagination"),
186
255
  after: Optional[str] = Query(None, description="Cursor for pagination"),
187
256
  limit: Optional[int] = Query(100, description="Maximum number of messages to return"),
188
- order: str = Query(
189
- "desc", description="Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order."
257
+ order: Literal["asc", "desc"] = Query(
258
+ "desc", description="Sort order for steps by creation time. 'asc' for oldest first, 'desc' for newest first"
190
259
  ),
260
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
191
261
  ):
192
262
  """
193
- Get messages associated with a run with filtering options.
194
-
195
- Args:
196
- run_id: ID of the run
197
- before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.
198
- after: A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.
199
- limit: Maximum number of steps to return
200
- order: Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order.
201
-
202
- Returns:
203
- A list of steps associated with the run.
263
+ Get steps associated with a run with filtering options.
204
264
  """
205
- if order not in ["asc", "desc"]:
206
- raise HTTPException(status_code=400, detail="Order must be 'asc' or 'desc'")
207
-
208
265
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
266
+ runs_manager = RunManager()
209
267
 
210
- try:
211
- steps = server.job_manager.get_job_steps(
212
- job_id=run_id,
213
- actor=actor,
214
- limit=limit,
215
- before=before,
216
- after=after,
217
- ascending=(order == "asc"),
218
- )
219
- return steps
220
- except NoResultFound as e:
221
- raise HTTPException(status_code=404, detail=str(e))
268
+ return await runs_manager.get_run_steps(
269
+ run_id=run_id,
270
+ actor=actor,
271
+ limit=limit,
272
+ before=before,
273
+ after=after,
274
+ ascending=(order == "asc"),
275
+ )
222
276
 
223
277
 
224
278
  @router.delete("/{run_id}", response_model=Run, operation_id="delete_run")
@@ -231,12 +285,8 @@ async def delete_run(
231
285
  Delete a run by its run_id.
232
286
  """
233
287
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
234
-
235
- try:
236
- job = await server.job_manager.delete_job_by_id_async(job_id=run_id, actor=actor)
237
- return Run.from_job(job)
238
- except NoResultFound:
239
- raise HTTPException(status_code=404, detail="Run not found")
288
+ runs_manager = RunManager()
289
+ return await runs_manager.delete_run_by_id(run_id=run_id, actor=actor)
240
290
 
241
291
 
242
292
  @router.post(
@@ -278,14 +328,14 @@ async def retrieve_stream(
278
328
  server: "SyncServer" = Depends(get_letta_server),
279
329
  ):
280
330
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
331
+ runs_manager = RunManager()
332
+
281
333
  try:
282
- job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
334
+ run = await runs_manager.get_run_by_id(run_id=run_id, actor=actor)
283
335
  except NoResultFound:
284
336
  raise HTTPException(status_code=404, detail="Run not found")
285
337
 
286
- run = Run.from_job(job)
287
-
288
- if "background" not in run.metadata or not run.metadata["background"]:
338
+ if not run.background:
289
339
  raise HTTPException(status_code=400, detail="Run was not created in background mode, so it cannot be retrieved.")
290
340
 
291
341
  if run.created_at < get_utc_time() - timedelta(hours=3):
@@ -314,8 +364,8 @@ async def retrieve_stream(
314
364
  if settings.enable_cancellation_aware_streaming:
315
365
  stream = cancellation_aware_stream_wrapper(
316
366
  stream_generator=stream,
317
- job_manager=server.job_manager,
318
- job_id=run_id,
367
+ run_manager=server.run_manager,
368
+ run_id=run_id,
319
369
  actor=actor,
320
370
  )
321
371
 
@@ -2,8 +2,9 @@ import os
2
2
  import shutil
3
3
  from typing import List, Optional
4
4
 
5
- from fastapi import APIRouter, Depends, HTTPException, Query
5
+ from fastapi import APIRouter, Depends, Query
6
6
 
7
+ from letta.errors import LettaInvalidArgumentError
7
8
  from letta.log import get_logger
8
9
  from letta.schemas.enums import SandboxType
9
10
  from letta.schemas.environment_variables import (
@@ -68,9 +69,8 @@ async def create_custom_local_sandbox_config(
68
69
  """
69
70
  # Ensure the incoming config is of type LOCAL
70
71
  if local_sandbox_config.type != SandboxType.LOCAL:
71
- raise HTTPException(
72
- status_code=400,
73
- detail=f"Provided config must be of type '{SandboxType.LOCAL.value}'.",
72
+ raise LettaInvalidArgumentError(
73
+ f"Provided config must be of type '{SandboxType.LOCAL.value}'.", argument_name="local_sandbox_config.type"
74
74
  )
75
75
 
76
76
  # Retrieve the user (actor)
@@ -138,25 +138,16 @@ async def force_recreate_local_sandbox_venv(
138
138
 
139
139
  # Check if venv exists, and delete if necessary
140
140
  if os.path.isdir(venv_path):
141
- try:
142
- shutil.rmtree(venv_path)
143
- logger.info(f"Deleted existing virtual environment at: {venv_path}")
144
- except Exception as e:
145
- raise HTTPException(status_code=500, detail=f"Failed to delete existing venv: {e}")
141
+ shutil.rmtree(venv_path)
142
+ logger.info(f"Deleted existing virtual environment at: {venv_path}")
146
143
 
147
144
  # Recreate the virtual environment
148
- try:
149
- create_venv_for_local_sandbox(sandbox_dir_path=sandbox_dir, venv_path=str(venv_path), env=os.environ.copy(), force_recreate=True)
150
- logger.info(f"Successfully recreated virtual environment at: {venv_path}")
151
- except Exception as e:
152
- raise HTTPException(status_code=500, detail=f"Failed to recreate venv: {e}")
145
+ create_venv_for_local_sandbox(sandbox_dir_path=sandbox_dir, venv_path=str(venv_path), env=os.environ.copy(), force_recreate=True)
146
+ logger.info(f"Successfully recreated virtual environment at: {venv_path}")
153
147
 
154
148
  # Install pip requirements
155
- try:
156
- install_pip_requirements_for_sandbox(local_configs=local_configs, env=os.environ.copy())
157
- logger.info(f"Successfully installed pip requirements for venv at: {venv_path}")
158
- except Exception as e:
159
- raise HTTPException(status_code=500, detail=f"Failed to install pip requirements: {e}")
149
+ install_pip_requirements_for_sandbox(local_configs=local_configs, env=os.environ.copy())
150
+ logger.info(f"Successfully installed pip requirements for venv at: {venv_path}")
160
151
 
161
152
  return sbx_config
162
153