letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.11.7.dev20251008104128__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 (145) hide show
  1. letta/adapters/letta_llm_adapter.py +1 -0
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +7 -2
  4. letta/adapters/simple_llm_request_adapter.py +88 -0
  5. letta/adapters/simple_llm_stream_adapter.py +192 -0
  6. letta/agents/agent_loop.py +6 -0
  7. letta/agents/ephemeral_summary_agent.py +2 -1
  8. letta/agents/helpers.py +142 -6
  9. letta/agents/letta_agent.py +13 -33
  10. letta/agents/letta_agent_batch.py +2 -4
  11. letta/agents/letta_agent_v2.py +87 -77
  12. letta/agents/letta_agent_v3.py +899 -0
  13. letta/agents/voice_agent.py +2 -6
  14. letta/constants.py +8 -4
  15. letta/errors.py +40 -0
  16. letta/functions/function_sets/base.py +84 -4
  17. letta/functions/function_sets/multi_agent.py +0 -3
  18. letta/functions/schema_generator.py +113 -71
  19. letta/groups/dynamic_multi_agent.py +3 -2
  20. letta/groups/helpers.py +1 -2
  21. letta/groups/round_robin_multi_agent.py +3 -2
  22. letta/groups/sleeptime_multi_agent.py +3 -2
  23. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  24. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  25. letta/groups/supervisor_multi_agent.py +84 -80
  26. letta/helpers/converters.py +3 -0
  27. letta/helpers/message_helper.py +4 -0
  28. letta/helpers/tool_rule_solver.py +92 -5
  29. letta/interfaces/anthropic_streaming_interface.py +409 -0
  30. letta/interfaces/gemini_streaming_interface.py +296 -0
  31. letta/interfaces/openai_streaming_interface.py +752 -1
  32. letta/llm_api/anthropic_client.py +126 -16
  33. letta/llm_api/bedrock_client.py +4 -2
  34. letta/llm_api/deepseek_client.py +4 -1
  35. letta/llm_api/google_vertex_client.py +123 -42
  36. letta/llm_api/groq_client.py +4 -1
  37. letta/llm_api/llm_api_tools.py +11 -4
  38. letta/llm_api/llm_client_base.py +6 -2
  39. letta/llm_api/openai.py +32 -2
  40. letta/llm_api/openai_client.py +423 -18
  41. letta/llm_api/xai_client.py +4 -1
  42. letta/main.py +9 -5
  43. letta/memory.py +1 -0
  44. letta/orm/__init__.py +1 -1
  45. letta/orm/agent.py +10 -0
  46. letta/orm/block.py +7 -16
  47. letta/orm/blocks_agents.py +8 -2
  48. letta/orm/files_agents.py +2 -0
  49. letta/orm/job.py +7 -5
  50. letta/orm/mcp_oauth.py +1 -0
  51. letta/orm/message.py +21 -6
  52. letta/orm/organization.py +2 -0
  53. letta/orm/provider.py +6 -2
  54. letta/orm/run.py +71 -0
  55. letta/orm/sandbox_config.py +7 -1
  56. letta/orm/sqlalchemy_base.py +0 -306
  57. letta/orm/step.py +6 -5
  58. letta/orm/step_metrics.py +5 -5
  59. letta/otel/tracing.py +28 -3
  60. letta/plugins/defaults.py +4 -4
  61. letta/prompts/system_prompts/__init__.py +2 -0
  62. letta/prompts/system_prompts/letta_v1.py +25 -0
  63. letta/schemas/agent.py +3 -2
  64. letta/schemas/agent_file.py +9 -3
  65. letta/schemas/block.py +23 -10
  66. letta/schemas/enums.py +21 -2
  67. letta/schemas/job.py +17 -4
  68. letta/schemas/letta_message_content.py +71 -2
  69. letta/schemas/letta_stop_reason.py +5 -5
  70. letta/schemas/llm_config.py +53 -3
  71. letta/schemas/memory.py +1 -1
  72. letta/schemas/message.py +504 -117
  73. letta/schemas/openai/responses_request.py +64 -0
  74. letta/schemas/providers/__init__.py +2 -0
  75. letta/schemas/providers/anthropic.py +16 -0
  76. letta/schemas/providers/ollama.py +115 -33
  77. letta/schemas/providers/openrouter.py +52 -0
  78. letta/schemas/providers/vllm.py +2 -1
  79. letta/schemas/run.py +48 -42
  80. letta/schemas/step.py +2 -2
  81. letta/schemas/step_metrics.py +1 -1
  82. letta/schemas/tool.py +15 -107
  83. letta/schemas/tool_rule.py +88 -5
  84. letta/serialize_schemas/marshmallow_agent.py +1 -0
  85. letta/server/db.py +86 -408
  86. letta/server/rest_api/app.py +61 -10
  87. letta/server/rest_api/dependencies.py +14 -0
  88. letta/server/rest_api/redis_stream_manager.py +19 -8
  89. letta/server/rest_api/routers/v1/agents.py +364 -292
  90. letta/server/rest_api/routers/v1/blocks.py +14 -20
  91. letta/server/rest_api/routers/v1/identities.py +45 -110
  92. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  93. letta/server/rest_api/routers/v1/jobs.py +23 -6
  94. letta/server/rest_api/routers/v1/messages.py +1 -1
  95. letta/server/rest_api/routers/v1/runs.py +126 -85
  96. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  97. letta/server/rest_api/routers/v1/tools.py +281 -594
  98. letta/server/rest_api/routers/v1/voice.py +1 -1
  99. letta/server/rest_api/streaming_response.py +29 -29
  100. letta/server/rest_api/utils.py +122 -64
  101. letta/server/server.py +160 -887
  102. letta/services/agent_manager.py +236 -919
  103. letta/services/agent_serialization_manager.py +16 -0
  104. letta/services/archive_manager.py +0 -100
  105. letta/services/block_manager.py +211 -168
  106. letta/services/file_manager.py +1 -1
  107. letta/services/files_agents_manager.py +24 -33
  108. letta/services/group_manager.py +0 -142
  109. letta/services/helpers/agent_manager_helper.py +7 -2
  110. letta/services/helpers/run_manager_helper.py +85 -0
  111. letta/services/job_manager.py +96 -411
  112. letta/services/lettuce/__init__.py +6 -0
  113. letta/services/lettuce/lettuce_client_base.py +86 -0
  114. letta/services/mcp_manager.py +38 -6
  115. letta/services/message_manager.py +165 -362
  116. letta/services/organization_manager.py +0 -36
  117. letta/services/passage_manager.py +0 -345
  118. letta/services/provider_manager.py +0 -80
  119. letta/services/run_manager.py +301 -0
  120. letta/services/sandbox_config_manager.py +0 -234
  121. letta/services/step_manager.py +62 -39
  122. letta/services/summarizer/summarizer.py +9 -7
  123. letta/services/telemetry_manager.py +0 -16
  124. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  125. letta/services/tool_executor/core_tool_executor.py +397 -2
  126. letta/services/tool_executor/files_tool_executor.py +3 -3
  127. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  128. letta/services/tool_executor/tool_execution_manager.py +6 -8
  129. letta/services/tool_executor/tool_executor_base.py +3 -3
  130. letta/services/tool_manager.py +85 -339
  131. letta/services/tool_sandbox/base.py +24 -13
  132. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  133. letta/services/tool_schema_generator.py +123 -0
  134. letta/services/user_manager.py +0 -99
  135. letta/settings.py +20 -4
  136. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
  137. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
  138. letta/agents/temporal/activities/__init__.py +0 -4
  139. letta/agents/temporal/activities/example_activity.py +0 -7
  140. letta/agents/temporal/activities/prepare_messages.py +0 -10
  141. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  142. letta/agents/temporal/types.py +0 -25
  143. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
  144. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
  145. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,301 @@
1
+ from datetime import datetime
2
+ from pickletools import pyunicode
3
+ from typing import List, Literal, Optional
4
+
5
+ from httpx import AsyncClient
6
+ from sqlalchemy import select
7
+ from sqlalchemy.orm import Session
8
+
9
+ from letta.helpers.datetime_helpers import get_utc_time
10
+ from letta.log import get_logger
11
+ from letta.orm.errors import NoResultFound
12
+ from letta.orm.message import Message as MessageModel
13
+ from letta.orm.run import Run as RunModel
14
+ from letta.orm.sqlalchemy_base import AccessType
15
+ from letta.orm.step import Step as StepModel
16
+ from letta.otel.tracing import log_event, trace_method
17
+ from letta.schemas.enums import AgentType, MessageRole, RunStatus
18
+ from letta.schemas.job import LettaRequestConfig
19
+ from letta.schemas.letta_message import LettaMessage, LettaMessageUnion
20
+ from letta.schemas.letta_response import LettaResponse
21
+ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
22
+ from letta.schemas.message import Message as PydanticMessage
23
+ from letta.schemas.run import Run as PydanticRun, RunUpdate
24
+ from letta.schemas.step import Step as PydanticStep
25
+ from letta.schemas.usage import LettaUsageStatistics
26
+ from letta.schemas.user import User as PydanticUser
27
+ from letta.server.db import db_registry
28
+ from letta.services.agent_manager import AgentManager
29
+ from letta.services.helpers.agent_manager_helper import validate_agent_exists_async
30
+ from letta.services.message_manager import MessageManager
31
+ from letta.services.step_manager import StepManager
32
+ from letta.utils import enforce_types
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ class RunManager:
38
+ """Manager class to handle business logic related to Runs."""
39
+
40
+ def __init__(self):
41
+ """Initialize the RunManager."""
42
+ self.step_manager = StepManager()
43
+ self.message_manager = MessageManager()
44
+ self.agent_manager = AgentManager()
45
+
46
+ @enforce_types
47
+ async def create_run(self, pydantic_run: PydanticRun, actor: PydanticUser) -> PydanticRun:
48
+ """Create a new run."""
49
+ async with db_registry.async_session() as session:
50
+ # Get agent_id from the pydantic object
51
+ agent_id = pydantic_run.agent_id
52
+
53
+ # Verify agent exists before creating the run
54
+ await validate_agent_exists_async(session, agent_id, actor)
55
+ organization_id = actor.organization_id
56
+
57
+ run_data = pydantic_run.model_dump(exclude_none=True)
58
+ # Handle metadata field mapping (Pydantic uses 'metadata', ORM uses 'metadata_')
59
+ if "metadata" in run_data:
60
+ run_data["metadata_"] = run_data.pop("metadata")
61
+
62
+ run = RunModel(**run_data)
63
+ run.organization_id = organization_id
64
+ run = await run.create_async(session, actor=actor, no_commit=True, no_refresh=True)
65
+ await session.commit()
66
+
67
+ return run.to_pydantic()
68
+
69
+ @enforce_types
70
+ async def get_run_by_id(self, run_id: str, actor: PydanticUser) -> PydanticRun:
71
+ """Get a run by its ID."""
72
+ async with db_registry.async_session() as session:
73
+ run = await RunModel.read_async(db_session=session, identifier=run_id, actor=actor, access_type=AccessType.ORGANIZATION)
74
+ if not run:
75
+ raise NoResultFound(f"Run with id {run_id} not found")
76
+ return run.to_pydantic()
77
+
78
+ @enforce_types
79
+ async def list_runs(
80
+ self,
81
+ actor: PydanticUser,
82
+ agent_id: Optional[str] = None,
83
+ agent_ids: Optional[List[str]] = None,
84
+ statuses: Optional[List[RunStatus]] = None,
85
+ limit: Optional[int] = 50,
86
+ before: Optional[str] = None,
87
+ after: Optional[str] = None,
88
+ ascending: bool = False,
89
+ stop_reason: Optional[str] = None,
90
+ background: Optional[bool] = None,
91
+ ) -> List[PydanticRun]:
92
+ """List runs with filtering options."""
93
+ async with db_registry.async_session() as session:
94
+ from sqlalchemy import select
95
+
96
+ query = select(RunModel).filter(RunModel.organization_id == actor.organization_id)
97
+
98
+ # Handle agent filtering
99
+ if agent_id:
100
+ agent_ids = [agent_id]
101
+ if agent_ids:
102
+ query = query.filter(RunModel.agent_id.in_(agent_ids))
103
+
104
+ # Filter by status
105
+ if statuses:
106
+ query = query.filter(RunModel.status.in_(statuses))
107
+
108
+ # Filter by stop reason
109
+ if stop_reason:
110
+ query = query.filter(RunModel.stop_reason == stop_reason)
111
+
112
+ # Filter by background
113
+ if background is not None:
114
+ query = query.filter(RunModel.background == background)
115
+
116
+ # Apply pagination
117
+ from letta.services.helpers.run_manager_helper import _apply_pagination_async
118
+
119
+ query = await _apply_pagination_async(query, before, after, session, ascending=ascending)
120
+
121
+ # Apply limit
122
+ if limit:
123
+ query = query.limit(limit)
124
+
125
+ result = await session.execute(query)
126
+ runs = result.scalars().all()
127
+ return [run.to_pydantic() for run in runs]
128
+
129
+ @enforce_types
130
+ async def delete_run(self, run_id: str, actor: PydanticUser) -> PydanticRun:
131
+ """Delete a run by its ID."""
132
+ async with db_registry.async_session() as session:
133
+ run = await RunModel.read_async(db_session=session, identifier=run_id, actor=actor, access_type=AccessType.ORGANIZATION)
134
+ if not run:
135
+ raise NoResultFound(f"Run with id {run_id} not found")
136
+
137
+ pydantic_run = run.to_pydantic()
138
+ await run.hard_delete_async(db_session=session, actor=actor)
139
+
140
+ return pydantic_run
141
+
142
+ @enforce_types
143
+ async def update_run_by_id_async(
144
+ self, run_id: str, update: RunUpdate, actor: PydanticUser, refresh_result_messages: bool = True
145
+ ) -> PydanticRun:
146
+ """Update a run using a RunUpdate object."""
147
+
148
+ async with db_registry.async_session() as session:
149
+ run = await RunModel.read_async(db_session=session, identifier=run_id, actor=actor)
150
+
151
+ # Check if this is a terminal update and whether we should dispatch a callback
152
+ needs_callback = False
153
+ callback_url = None
154
+ not_completed_before = not bool(run.completed_at)
155
+ is_terminal_update = update.status in {RunStatus.completed, RunStatus.failed}
156
+ if is_terminal_update and not_completed_before and run.callback_url:
157
+ needs_callback = True
158
+ callback_url = run.callback_url
159
+
160
+ # Housekeeping only when the run is actually completing
161
+ if not_completed_before and is_terminal_update:
162
+ if not update.stop_reason:
163
+ logger.warning(f"Run {run_id} completed without a stop reason")
164
+ if not update.completed_at:
165
+ logger.warning(f"Run {run_id} completed without a completed_at timestamp")
166
+ update.completed_at = get_utc_time().replace(tzinfo=None)
167
+
168
+ # Update job attributes with only the fields that were explicitly set
169
+ update_data = update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
170
+
171
+ # Automatically update the completion timestamp if status is set to 'completed'
172
+ for key, value in update_data.items():
173
+ # Ensure completed_at is timezone-naive for database compatibility
174
+ if key == "completed_at" and value is not None and hasattr(value, "replace"):
175
+ value = value.replace(tzinfo=None)
176
+ setattr(run, key, value)
177
+
178
+ await run.update_async(db_session=session, actor=actor, no_commit=True, no_refresh=True)
179
+ final_metadata = run.metadata_
180
+ pydantic_run = run.to_pydantic()
181
+ await session.commit()
182
+
183
+ # Dispatch callback outside of database session if needed
184
+ if needs_callback:
185
+ if refresh_result_messages:
186
+ result = LettaResponse(
187
+ messages=await self.get_run_messages(run_id=run_id, actor=actor),
188
+ stop_reason=LettaStopReason(stop_reason=pydantic_run.stop_reason),
189
+ usage=await self.get_run_usage(run_id=run_id, actor=actor),
190
+ )
191
+ final_metadata["result"] = result.model_dump()
192
+ callback_info = {
193
+ "run_id": run_id,
194
+ "callback_url": callback_url,
195
+ "status": update.status,
196
+ "completed_at": get_utc_time().replace(tzinfo=None),
197
+ "metadata": final_metadata,
198
+ }
199
+ callback_result = await self._dispatch_callback_async(callback_info)
200
+
201
+ # Update callback status in a separate transaction
202
+ async with db_registry.async_session() as session:
203
+ run = await RunModel.read_async(db_session=session, identifier=run_id, actor=actor)
204
+ run.callback_sent_at = callback_result["callback_sent_at"]
205
+ run.callback_status_code = callback_result.get("callback_status_code")
206
+ run.callback_error = callback_result.get("callback_error")
207
+ pydantic_run = run.to_pydantic()
208
+ await run.update_async(db_session=session, actor=actor, no_commit=True, no_refresh=True)
209
+ await session.commit()
210
+
211
+ return pydantic_run
212
+
213
+ @trace_method
214
+ async def _dispatch_callback_async(self, callback_info: dict) -> dict:
215
+ """
216
+ POST a standard JSON payload to callback_url and return callback status asynchronously.
217
+ """
218
+ payload = {
219
+ "run_id": callback_info["run_id"],
220
+ "status": callback_info["status"],
221
+ "completed_at": callback_info["completed_at"].isoformat() if callback_info["completed_at"] else None,
222
+ "metadata": callback_info["metadata"],
223
+ }
224
+
225
+ callback_sent_at = get_utc_time().replace(tzinfo=None)
226
+ result = {"callback_sent_at": callback_sent_at}
227
+
228
+ try:
229
+ async with AsyncClient() as client:
230
+ log_event("POST callback dispatched", payload)
231
+ resp = await client.post(callback_info["callback_url"], json=payload, timeout=5.0)
232
+ log_event("POST callback finished")
233
+ result["callback_status_code"] = resp.status_code
234
+ except Exception as e:
235
+ error_message = f"Failed to dispatch callback for run {callback_info['run_id']} to {callback_info['callback_url']}: {e!s}"
236
+ logger.error(error_message)
237
+ result["callback_error"] = error_message
238
+ # Continue silently - callback failures should not affect run completion
239
+ finally:
240
+ return result
241
+
242
+ @enforce_types
243
+ async def get_run_usage(self, run_id: str, actor: PydanticUser) -> LettaUsageStatistics:
244
+ """Get usage statistics for a run."""
245
+ async with db_registry.async_session() as session:
246
+ run = await RunModel.read_async(db_session=session, identifier=run_id, actor=actor, access_type=AccessType.ORGANIZATION)
247
+ if not run:
248
+ raise NoResultFound(f"Run with id {run_id} not found")
249
+
250
+ steps = await self.step_manager.list_steps_async(run_id=run_id, actor=actor)
251
+ total_usage = LettaUsageStatistics()
252
+ for step in steps:
253
+ total_usage.prompt_tokens += step.prompt_tokens
254
+ total_usage.completion_tokens += step.completion_tokens
255
+ total_usage.total_tokens += step.total_tokens
256
+ total_usage.step_count += 1
257
+ return total_usage
258
+
259
+ @enforce_types
260
+ async def get_run_messages(
261
+ self,
262
+ run_id: str,
263
+ actor: PydanticUser,
264
+ limit: Optional[int] = 100,
265
+ before: Optional[str] = None,
266
+ after: Optional[str] = None,
267
+ order: Literal["asc", "desc"] = "asc",
268
+ ) -> List[LettaMessage]:
269
+ """Get the result of a run."""
270
+ run = await self.get_run_by_id(run_id=run_id, actor=actor)
271
+ request_config = run.request_config
272
+ agent = await self.agent_manager.get_agent_by_id_async(agent_id=run.agent_id, actor=actor, include_relationships=[])
273
+ text_is_assistant_message = agent.agent_type == AgentType.letta_v1_agent
274
+
275
+ messages = await self.message_manager.list_messages(
276
+ actor=actor,
277
+ run_id=run_id,
278
+ limit=limit,
279
+ before=before,
280
+ after=after,
281
+ ascending=(order == "asc"),
282
+ )
283
+ letta_messages = PydanticMessage.to_letta_messages_from_list(
284
+ messages, reverse=(order != "asc"), text_is_assistant_message=text_is_assistant_message
285
+ )
286
+
287
+ if request_config and request_config.include_return_message_types:
288
+ include_return_message_types_set = set(request_config.include_return_message_types)
289
+ letta_messages = [msg for msg in letta_messages if msg.message_type in include_return_message_types_set]
290
+
291
+ return letta_messages
292
+
293
+ @enforce_types
294
+ async def get_run_request_config(self, run_id: str, actor: PydanticUser) -> Optional[LettaRequestConfig]:
295
+ """Get the letta request config from a run."""
296
+ async with db_registry.async_session() as session:
297
+ run = await RunModel.read_async(db_session=session, identifier=run_id, actor=actor, access_type=AccessType.ORGANIZATION)
298
+ if not run:
299
+ raise NoResultFound(f"Run with id {run_id} not found")
300
+ pydantic_run = run.to_pydantic()
301
+ return pydantic_run.request_config
@@ -45,40 +45,6 @@ class SandboxConfigManager:
45
45
  sandbox_config = self.create_or_update_sandbox_config(SandboxConfigCreate(config=default_config), actor=actor)
46
46
  return sandbox_config
47
47
 
48
- @enforce_types
49
- @trace_method
50
- def create_or_update_sandbox_config(self, sandbox_config_create: SandboxConfigCreate, actor: PydanticUser) -> PydanticSandboxConfig:
51
- """Create or update a sandbox configuration based on the PydanticSandboxConfig schema."""
52
- config = sandbox_config_create.config
53
- sandbox_type = config.type
54
- sandbox_config = PydanticSandboxConfig(
55
- type=sandbox_type, config=config.model_dump(exclude_none=True), organization_id=actor.organization_id
56
- )
57
-
58
- # Attempt to retrieve the existing sandbox configuration by type within the organization
59
- db_sandbox = self.get_sandbox_config_by_type(sandbox_config.type, actor=actor)
60
- if db_sandbox:
61
- # Prepare the update data, excluding fields that should not be reset
62
- update_data = sandbox_config.model_dump(exclude_unset=True, exclude_none=True)
63
- update_data = {key: value for key, value in update_data.items() if getattr(db_sandbox, key) != value}
64
-
65
- # If there are changes, update the sandbox configuration
66
- if update_data:
67
- db_sandbox = self.update_sandbox_config(db_sandbox.id, SandboxConfigUpdate(**update_data), actor)
68
- else:
69
- printd(
70
- f"`create_or_update_sandbox_config` was called with user_id={actor.id}, organization_id={actor.organization_id}, "
71
- f"type={sandbox_config.type}, but found existing configuration with nothing to update."
72
- )
73
-
74
- return db_sandbox
75
- else:
76
- # If the sandbox configuration doesn't exist, create a new one
77
- with db_registry.session() as session:
78
- db_sandbox = SandboxConfigModel(**sandbox_config.model_dump(exclude_none=True))
79
- db_sandbox.create(session, actor=actor)
80
- return db_sandbox.to_pydantic()
81
-
82
48
  @enforce_types
83
49
  @trace_method
84
50
  async def get_or_create_default_sandbox_config_async(self, sandbox_type: SandboxType, actor: PydanticUser) -> PydanticSandboxConfig:
@@ -133,34 +99,6 @@ class SandboxConfigManager:
133
99
  await db_sandbox.create_async(session, actor=actor)
134
100
  return db_sandbox.to_pydantic()
135
101
 
136
- @enforce_types
137
- @trace_method
138
- def update_sandbox_config(
139
- self, sandbox_config_id: str, sandbox_update: SandboxConfigUpdate, actor: PydanticUser
140
- ) -> PydanticSandboxConfig:
141
- """Update an existing sandbox configuration."""
142
- with db_registry.session() as session:
143
- sandbox = SandboxConfigModel.read(db_session=session, identifier=sandbox_config_id, actor=actor)
144
- # We need to check that the sandbox_update provided is the same type as the original sandbox
145
- if sandbox.type != sandbox_update.config.type:
146
- raise ValueError(
147
- f"Mismatched type for sandbox config update: tried to update sandbox_config of type {sandbox.type} with config of type {sandbox_update.config.type}"
148
- )
149
-
150
- update_data = sandbox_update.model_dump(exclude_unset=True, exclude_none=True)
151
- update_data = {key: value for key, value in update_data.items() if getattr(sandbox, key) != value}
152
-
153
- if update_data:
154
- for key, value in update_data.items():
155
- setattr(sandbox, key, value)
156
- sandbox.update(db_session=session, actor=actor)
157
- else:
158
- printd(
159
- f"`update_sandbox_config` called with user_id={actor.id}, organization_id={actor.organization_id}, "
160
- f"name={sandbox.type}, but nothing to update."
161
- )
162
- return sandbox.to_pydantic()
163
-
164
102
  @enforce_types
165
103
  @trace_method
166
104
  async def update_sandbox_config_async(
@@ -189,15 +127,6 @@ class SandboxConfigManager:
189
127
  )
190
128
  return sandbox.to_pydantic()
191
129
 
192
- @enforce_types
193
- @trace_method
194
- def delete_sandbox_config(self, sandbox_config_id: str, actor: PydanticUser) -> PydanticSandboxConfig:
195
- """Delete a sandbox configuration by its ID."""
196
- with db_registry.session() as session:
197
- sandbox = SandboxConfigModel.read(db_session=session, identifier=sandbox_config_id, actor=actor)
198
- sandbox.hard_delete(db_session=session, actor=actor)
199
- return sandbox.to_pydantic()
200
-
201
130
  @enforce_types
202
131
  @trace_method
203
132
  async def delete_sandbox_config_async(self, sandbox_config_id: str, actor: PydanticUser) -> PydanticSandboxConfig:
@@ -207,24 +136,6 @@ class SandboxConfigManager:
207
136
  await sandbox.hard_delete_async(db_session=session, actor=actor)
208
137
  return sandbox.to_pydantic()
209
138
 
210
- @enforce_types
211
- @trace_method
212
- def list_sandbox_configs(
213
- self,
214
- actor: PydanticUser,
215
- after: Optional[str] = None,
216
- limit: Optional[int] = 50,
217
- sandbox_type: Optional[SandboxType] = None,
218
- ) -> List[PydanticSandboxConfig]:
219
- """List all sandbox configurations with optional pagination."""
220
- kwargs = {"organization_id": actor.organization_id}
221
- if sandbox_type:
222
- kwargs.update({"type": sandbox_type})
223
-
224
- with db_registry.session() as session:
225
- sandboxes = SandboxConfigModel.list(db_session=session, after=after, limit=limit, **kwargs)
226
- return [sandbox.to_pydantic() for sandbox in sandboxes]
227
-
228
139
  @enforce_types
229
140
  @trace_method
230
141
  async def list_sandbox_configs_async(
@@ -243,35 +154,6 @@ class SandboxConfigManager:
243
154
  sandboxes = await SandboxConfigModel.list_async(db_session=session, after=after, limit=limit, **kwargs)
244
155
  return [sandbox.to_pydantic() for sandbox in sandboxes]
245
156
 
246
- @enforce_types
247
- @trace_method
248
- def get_sandbox_config_by_id(self, sandbox_config_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticSandboxConfig]:
249
- """Retrieve a sandbox configuration by its ID."""
250
- with db_registry.session() as session:
251
- try:
252
- sandbox = SandboxConfigModel.read(db_session=session, identifier=sandbox_config_id, actor=actor)
253
- return sandbox.to_pydantic()
254
- except NoResultFound:
255
- return None
256
-
257
- @enforce_types
258
- @trace_method
259
- def get_sandbox_config_by_type(self, type: SandboxType, actor: Optional[PydanticUser] = None) -> Optional[PydanticSandboxConfig]:
260
- """Retrieve a sandbox config by its type."""
261
- with db_registry.session() as session:
262
- try:
263
- sandboxes = SandboxConfigModel.list(
264
- db_session=session,
265
- type=type,
266
- organization_id=actor.organization_id,
267
- limit=1,
268
- )
269
- if sandboxes:
270
- return sandboxes[0].to_pydantic()
271
- return None
272
- except NoResultFound:
273
- return None
274
-
275
157
  @enforce_types
276
158
  @trace_method
277
159
  async def get_sandbox_config_by_type_async(
@@ -292,34 +174,6 @@ class SandboxConfigManager:
292
174
  except NoResultFound:
293
175
  return None
294
176
 
295
- @enforce_types
296
- @trace_method
297
- def create_sandbox_env_var(
298
- self, env_var_create: SandboxEnvironmentVariableCreate, sandbox_config_id: str, actor: PydanticUser
299
- ) -> PydanticEnvVar:
300
- """Create a new sandbox environment variable."""
301
- env_var = PydanticEnvVar(**env_var_create.model_dump(), sandbox_config_id=sandbox_config_id, organization_id=actor.organization_id)
302
-
303
- db_env_var = self.get_sandbox_env_var_by_key_and_sandbox_config_id(env_var.key, env_var.sandbox_config_id, actor=actor)
304
- if db_env_var:
305
- update_data = env_var.model_dump(exclude_unset=True, exclude_none=True)
306
- update_data = {key: value for key, value in update_data.items() if getattr(db_env_var, key) != value}
307
- # If there are changes, update the environment variable
308
- if update_data:
309
- db_env_var = self.update_sandbox_env_var(db_env_var.id, SandboxEnvironmentVariableUpdate(**update_data), actor)
310
- else:
311
- printd(
312
- f"`create_or_update_sandbox_env_var` was called with user_id={actor.id}, organization_id={actor.organization_id}, "
313
- f"key={env_var.key}, but found existing variable with nothing to update."
314
- )
315
-
316
- return db_env_var
317
- else:
318
- with db_registry.session() as session:
319
- env_var = SandboxEnvVarModel(**env_var.model_dump(to_orm=True, exclude_none=True))
320
- env_var.create(session, actor=actor)
321
- return env_var.to_pydantic()
322
-
323
177
  @enforce_types
324
178
  @trace_method
325
179
  async def create_sandbox_env_var_async(
@@ -348,28 +202,6 @@ class SandboxConfigManager:
348
202
  await env_var.create_async(session, actor=actor)
349
203
  return env_var.to_pydantic()
350
204
 
351
- @enforce_types
352
- @trace_method
353
- def update_sandbox_env_var(
354
- self, env_var_id: str, env_var_update: SandboxEnvironmentVariableUpdate, actor: PydanticUser
355
- ) -> PydanticEnvVar:
356
- """Update an existing sandbox environment variable."""
357
- with db_registry.session() as session:
358
- env_var = SandboxEnvVarModel.read(db_session=session, identifier=env_var_id, actor=actor)
359
- update_data = env_var_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
360
- update_data = {key: value for key, value in update_data.items() if getattr(env_var, key) != value}
361
-
362
- if update_data:
363
- for key, value in update_data.items():
364
- setattr(env_var, key, value)
365
- env_var.update(db_session=session, actor=actor)
366
- else:
367
- printd(
368
- f"`update_sandbox_env_var` called with user_id={actor.id}, organization_id={actor.organization_id}, "
369
- f"key={env_var.key}, but nothing to update."
370
- )
371
- return env_var.to_pydantic()
372
-
373
205
  @enforce_types
374
206
  @trace_method
375
207
  async def update_sandbox_env_var_async(
@@ -392,15 +224,6 @@ class SandboxConfigManager:
392
224
  )
393
225
  return env_var.to_pydantic()
394
226
 
395
- @enforce_types
396
- @trace_method
397
- def delete_sandbox_env_var(self, env_var_id: str, actor: PydanticUser) -> PydanticEnvVar:
398
- """Delete a sandbox environment variable by its ID."""
399
- with db_registry.session() as session:
400
- env_var = SandboxEnvVarModel.read(db_session=session, identifier=env_var_id, actor=actor)
401
- env_var.hard_delete(db_session=session, actor=actor)
402
- return env_var.to_pydantic()
403
-
404
227
  @enforce_types
405
228
  @trace_method
406
229
  async def delete_sandbox_env_var_async(self, env_var_id: str, actor: PydanticUser) -> PydanticEnvVar:
@@ -410,26 +233,6 @@ class SandboxConfigManager:
410
233
  await env_var.hard_delete_async(db_session=session, actor=actor)
411
234
  return env_var.to_pydantic()
412
235
 
413
- @enforce_types
414
- @trace_method
415
- def list_sandbox_env_vars(
416
- self,
417
- sandbox_config_id: str,
418
- actor: PydanticUser,
419
- after: Optional[str] = None,
420
- limit: Optional[int] = 50,
421
- ) -> List[PydanticEnvVar]:
422
- """List all sandbox environment variables with optional pagination."""
423
- with db_registry.session() as session:
424
- env_vars = SandboxEnvVarModel.list(
425
- db_session=session,
426
- after=after,
427
- limit=limit,
428
- organization_id=actor.organization_id,
429
- sandbox_config_id=sandbox_config_id,
430
- )
431
- return [env_var.to_pydantic() for env_var in env_vars]
432
-
433
236
  @enforce_types
434
237
  @trace_method
435
238
  async def list_sandbox_env_vars_async(
@@ -450,22 +253,6 @@ class SandboxConfigManager:
450
253
  )
451
254
  return [env_var.to_pydantic() for env_var in env_vars]
452
255
 
453
- @enforce_types
454
- @trace_method
455
- def list_sandbox_env_vars_by_key(
456
- self, key: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
457
- ) -> List[PydanticEnvVar]:
458
- """List all sandbox environment variables with optional pagination."""
459
- with db_registry.session() as session:
460
- env_vars = SandboxEnvVarModel.list(
461
- db_session=session,
462
- after=after,
463
- limit=limit,
464
- organization_id=actor.organization_id,
465
- key=key,
466
- )
467
- return [env_var.to_pydantic() for env_var in env_vars]
468
-
469
256
  @enforce_types
470
257
  @trace_method
471
258
  async def list_sandbox_env_vars_by_key_async(
@@ -501,27 +288,6 @@ class SandboxConfigManager:
501
288
  env_vars = await self.list_sandbox_env_vars_async(sandbox_config_id, actor, after, limit)
502
289
  return {env_var.key: env_var.value for env_var in env_vars}
503
290
 
504
- @enforce_types
505
- @trace_method
506
- def get_sandbox_env_var_by_key_and_sandbox_config_id(
507
- self, key: str, sandbox_config_id: str, actor: Optional[PydanticUser] = None
508
- ) -> Optional[PydanticEnvVar]:
509
- """Retrieve a sandbox environment variable by its key and sandbox_config_id."""
510
- with db_registry.session() as session:
511
- try:
512
- env_var = SandboxEnvVarModel.list(
513
- db_session=session,
514
- key=key,
515
- sandbox_config_id=sandbox_config_id,
516
- organization_id=actor.organization_id,
517
- limit=1,
518
- )
519
- if env_var:
520
- return env_var[0].to_pydantic()
521
- return None
522
- except NoResultFound:
523
- return None
524
-
525
291
  @enforce_types
526
292
  @trace_method
527
293
  async def get_sandbox_env_var_by_key_and_sandbox_config_id_async(