letta-nightly 0.12.1.dev20251024104217__py3-none-any.whl → 0.13.0.dev20251025104015__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.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (159) hide show
  1. letta/__init__.py +2 -3
  2. letta/adapters/letta_llm_adapter.py +1 -0
  3. letta/adapters/simple_llm_request_adapter.py +8 -5
  4. letta/adapters/simple_llm_stream_adapter.py +22 -6
  5. letta/agents/agent_loop.py +10 -3
  6. letta/agents/base_agent.py +4 -1
  7. letta/agents/helpers.py +41 -9
  8. letta/agents/letta_agent.py +11 -10
  9. letta/agents/letta_agent_v2.py +47 -37
  10. letta/agents/letta_agent_v3.py +395 -300
  11. letta/agents/voice_agent.py +8 -6
  12. letta/agents/voice_sleeptime_agent.py +3 -3
  13. letta/constants.py +30 -7
  14. letta/errors.py +20 -0
  15. letta/functions/function_sets/base.py +55 -3
  16. letta/functions/mcp_client/types.py +33 -57
  17. letta/functions/schema_generator.py +135 -23
  18. letta/groups/sleeptime_multi_agent_v3.py +6 -11
  19. letta/groups/sleeptime_multi_agent_v4.py +227 -0
  20. letta/helpers/converters.py +78 -4
  21. letta/helpers/crypto_utils.py +6 -2
  22. letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
  23. letta/interfaces/anthropic_streaming_interface.py +3 -4
  24. letta/interfaces/gemini_streaming_interface.py +4 -6
  25. letta/interfaces/openai_streaming_interface.py +63 -28
  26. letta/llm_api/anthropic_client.py +7 -4
  27. letta/llm_api/deepseek_client.py +6 -4
  28. letta/llm_api/google_ai_client.py +3 -12
  29. letta/llm_api/google_vertex_client.py +1 -1
  30. letta/llm_api/helpers.py +90 -61
  31. letta/llm_api/llm_api_tools.py +4 -1
  32. letta/llm_api/openai.py +12 -12
  33. letta/llm_api/openai_client.py +53 -16
  34. letta/local_llm/constants.py +4 -3
  35. letta/local_llm/json_parser.py +5 -2
  36. letta/local_llm/utils.py +2 -3
  37. letta/log.py +171 -7
  38. letta/orm/agent.py +43 -9
  39. letta/orm/archive.py +4 -0
  40. letta/orm/custom_columns.py +15 -0
  41. letta/orm/identity.py +11 -11
  42. letta/orm/mcp_server.py +9 -0
  43. letta/orm/message.py +6 -1
  44. letta/orm/run_metrics.py +7 -2
  45. letta/orm/sqlalchemy_base.py +2 -2
  46. letta/orm/tool.py +3 -0
  47. letta/otel/tracing.py +2 -0
  48. letta/prompts/prompt_generator.py +7 -2
  49. letta/schemas/agent.py +41 -10
  50. letta/schemas/agent_file.py +3 -0
  51. letta/schemas/archive.py +4 -2
  52. letta/schemas/block.py +2 -1
  53. letta/schemas/enums.py +36 -3
  54. letta/schemas/file.py +3 -3
  55. letta/schemas/folder.py +2 -1
  56. letta/schemas/group.py +2 -1
  57. letta/schemas/identity.py +18 -9
  58. letta/schemas/job.py +3 -1
  59. letta/schemas/letta_message.py +71 -12
  60. letta/schemas/letta_request.py +7 -3
  61. letta/schemas/letta_stop_reason.py +0 -25
  62. letta/schemas/llm_config.py +8 -2
  63. letta/schemas/mcp.py +80 -83
  64. letta/schemas/mcp_server.py +349 -0
  65. letta/schemas/memory.py +20 -8
  66. letta/schemas/message.py +212 -67
  67. letta/schemas/providers/anthropic.py +13 -6
  68. letta/schemas/providers/azure.py +6 -4
  69. letta/schemas/providers/base.py +8 -4
  70. letta/schemas/providers/bedrock.py +6 -2
  71. letta/schemas/providers/cerebras.py +7 -3
  72. letta/schemas/providers/deepseek.py +2 -1
  73. letta/schemas/providers/google_gemini.py +15 -6
  74. letta/schemas/providers/groq.py +2 -1
  75. letta/schemas/providers/lmstudio.py +9 -6
  76. letta/schemas/providers/mistral.py +2 -1
  77. letta/schemas/providers/openai.py +7 -2
  78. letta/schemas/providers/together.py +9 -3
  79. letta/schemas/providers/xai.py +7 -3
  80. letta/schemas/run.py +7 -2
  81. letta/schemas/run_metrics.py +2 -1
  82. letta/schemas/sandbox_config.py +2 -2
  83. letta/schemas/secret.py +3 -158
  84. letta/schemas/source.py +2 -2
  85. letta/schemas/step.py +2 -2
  86. letta/schemas/tool.py +24 -1
  87. letta/schemas/usage.py +0 -1
  88. letta/server/rest_api/app.py +123 -7
  89. letta/server/rest_api/dependencies.py +3 -0
  90. letta/server/rest_api/interface.py +7 -4
  91. letta/server/rest_api/redis_stream_manager.py +16 -1
  92. letta/server/rest_api/routers/v1/__init__.py +7 -0
  93. letta/server/rest_api/routers/v1/agents.py +332 -322
  94. letta/server/rest_api/routers/v1/archives.py +127 -40
  95. letta/server/rest_api/routers/v1/blocks.py +54 -6
  96. letta/server/rest_api/routers/v1/chat_completions.py +146 -0
  97. letta/server/rest_api/routers/v1/folders.py +27 -35
  98. letta/server/rest_api/routers/v1/groups.py +23 -35
  99. letta/server/rest_api/routers/v1/identities.py +24 -10
  100. letta/server/rest_api/routers/v1/internal_runs.py +107 -0
  101. letta/server/rest_api/routers/v1/internal_templates.py +162 -179
  102. letta/server/rest_api/routers/v1/jobs.py +15 -27
  103. letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
  104. letta/server/rest_api/routers/v1/messages.py +23 -34
  105. letta/server/rest_api/routers/v1/organizations.py +6 -27
  106. letta/server/rest_api/routers/v1/providers.py +35 -62
  107. letta/server/rest_api/routers/v1/runs.py +30 -43
  108. letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
  109. letta/server/rest_api/routers/v1/sources.py +26 -42
  110. letta/server/rest_api/routers/v1/steps.py +16 -29
  111. letta/server/rest_api/routers/v1/tools.py +17 -13
  112. letta/server/rest_api/routers/v1/users.py +5 -17
  113. letta/server/rest_api/routers/v1/voice.py +18 -27
  114. letta/server/rest_api/streaming_response.py +5 -2
  115. letta/server/rest_api/utils.py +187 -25
  116. letta/server/server.py +27 -22
  117. letta/server/ws_api/server.py +5 -4
  118. letta/services/agent_manager.py +148 -26
  119. letta/services/agent_serialization_manager.py +6 -1
  120. letta/services/archive_manager.py +168 -15
  121. letta/services/block_manager.py +14 -4
  122. letta/services/file_manager.py +33 -29
  123. letta/services/group_manager.py +10 -0
  124. letta/services/helpers/agent_manager_helper.py +65 -11
  125. letta/services/identity_manager.py +105 -4
  126. letta/services/job_manager.py +11 -1
  127. letta/services/mcp/base_client.py +2 -2
  128. letta/services/mcp/oauth_utils.py +33 -8
  129. letta/services/mcp_manager.py +174 -78
  130. letta/services/mcp_server_manager.py +1331 -0
  131. letta/services/message_manager.py +109 -4
  132. letta/services/organization_manager.py +4 -4
  133. letta/services/passage_manager.py +9 -25
  134. letta/services/provider_manager.py +91 -15
  135. letta/services/run_manager.py +72 -15
  136. letta/services/sandbox_config_manager.py +45 -3
  137. letta/services/source_manager.py +15 -8
  138. letta/services/step_manager.py +24 -1
  139. letta/services/streaming_service.py +581 -0
  140. letta/services/summarizer/summarizer.py +1 -1
  141. letta/services/tool_executor/core_tool_executor.py +111 -0
  142. letta/services/tool_executor/files_tool_executor.py +5 -3
  143. letta/services/tool_executor/sandbox_tool_executor.py +2 -2
  144. letta/services/tool_executor/tool_execution_manager.py +1 -1
  145. letta/services/tool_manager.py +10 -3
  146. letta/services/tool_sandbox/base.py +61 -1
  147. letta/services/tool_sandbox/local_sandbox.py +1 -3
  148. letta/services/user_manager.py +2 -2
  149. letta/settings.py +49 -5
  150. letta/system.py +14 -5
  151. letta/utils.py +73 -1
  152. letta/validators.py +105 -0
  153. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/METADATA +4 -2
  154. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/RECORD +157 -151
  155. letta/schemas/letta_ping.py +0 -28
  156. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  157. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/WHEEL +0 -0
  158. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/entry_points.txt +0 -0
  159. {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ from letta.log import get_logger
5
5
  from letta.orm.errors import NoResultFound
6
6
  from letta.orm.sandbox_config import SandboxConfig as SandboxConfigModel, SandboxEnvironmentVariable as SandboxEnvVarModel
7
7
  from letta.otel.tracing import trace_method
8
- from letta.schemas.enums import SandboxType
8
+ from letta.schemas.enums import PrimitiveType, SandboxType
9
9
  from letta.schemas.environment_variables import (
10
10
  SandboxEnvironmentVariable as PydanticEnvVar,
11
11
  SandboxEnvironmentVariableCreate,
@@ -20,6 +20,7 @@ from letta.schemas.sandbox_config import (
20
20
  from letta.schemas.user import User as PydanticUser
21
21
  from letta.server.db import db_registry
22
22
  from letta.utils import enforce_types, printd
23
+ from letta.validators import raise_on_invalid_id
23
24
 
24
25
  logger = get_logger(__name__)
25
26
 
@@ -101,6 +102,7 @@ class SandboxConfigManager:
101
102
 
102
103
  @enforce_types
103
104
  @trace_method
105
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
104
106
  async def update_sandbox_config_async(
105
107
  self, sandbox_config_id: str, sandbox_update: SandboxConfigUpdate, actor: PydanticUser
106
108
  ) -> PydanticSandboxConfig:
@@ -129,6 +131,7 @@ class SandboxConfigManager:
129
131
 
130
132
  @enforce_types
131
133
  @trace_method
134
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
132
135
  async def delete_sandbox_config_async(self, sandbox_config_id: str, actor: PydanticUser) -> PydanticSandboxConfig:
133
136
  """Delete a sandbox configuration by its ID."""
134
137
  async with db_registry.async_session() as session:
@@ -176,6 +179,7 @@ class SandboxConfigManager:
176
179
 
177
180
  @enforce_types
178
181
  @trace_method
182
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
179
183
  async def create_sandbox_env_var_async(
180
184
  self, env_var_create: SandboxEnvironmentVariableCreate, sandbox_config_id: str, actor: PydanticUser
181
185
  ) -> PydanticEnvVar:
@@ -198,6 +202,12 @@ class SandboxConfigManager:
198
202
  return db_env_var
199
203
  else:
200
204
  async with db_registry.async_session() as session:
205
+ # Explicitly encrypt the value before storing
206
+ from letta.schemas.secret import Secret
207
+
208
+ if env_var.value is not None:
209
+ env_var.value_enc = Secret.from_plaintext(env_var.value)
210
+
201
211
  env_var = SandboxEnvVarModel(**env_var.model_dump(to_orm=True, exclude_none=True))
202
212
  await env_var.create_async(session, actor=actor)
203
213
  return env_var.to_pydantic()
@@ -211,6 +221,31 @@ class SandboxConfigManager:
211
221
  async with db_registry.async_session() as session:
212
222
  env_var = await SandboxEnvVarModel.read_async(db_session=session, identifier=env_var_id, actor=actor)
213
223
  update_data = env_var_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
224
+
225
+ # Handle encryption for value if provided
226
+ # Only re-encrypt if the value has actually changed
227
+ if "value" in update_data and update_data["value"] is not None:
228
+ from letta.schemas.secret import Secret
229
+
230
+ # Check if value changed
231
+ existing_value = None
232
+ if env_var.value_enc:
233
+ existing_secret = Secret.from_encrypted(env_var.value_enc)
234
+ existing_value = existing_secret.get_plaintext()
235
+ elif env_var.value:
236
+ existing_value = env_var.value
237
+
238
+ # Only re-encrypt if different
239
+ if existing_value != update_data["value"]:
240
+ env_var.value_enc = Secret.from_plaintext(update_data["value"]).get_encrypted()
241
+ # Keep plaintext for dual-write during migration
242
+ env_var.value = update_data["value"]
243
+
244
+ # Remove from update_data since we set directly on env_var
245
+ update_data.pop("value", None)
246
+ update_data.pop("value_enc", None)
247
+
248
+ # Apply remaining updates
214
249
  update_data = {key: value for key, value in update_data.items() if getattr(env_var, key) != value}
215
250
 
216
251
  if update_data:
@@ -235,6 +270,7 @@ class SandboxConfigManager:
235
270
 
236
271
  @enforce_types
237
272
  @trace_method
273
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
238
274
  async def list_sandbox_env_vars_async(
239
275
  self,
240
276
  sandbox_config_id: str,
@@ -271,25 +307,31 @@ class SandboxConfigManager:
271
307
 
272
308
  @enforce_types
273
309
  @trace_method
310
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
274
311
  def get_sandbox_env_vars_as_dict(
275
312
  self, sandbox_config_id: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
276
313
  ) -> Dict[str, str]:
277
314
  env_vars = self.list_sandbox_env_vars(sandbox_config_id, actor, after, limit)
278
315
  result = {}
279
316
  for env_var in env_vars:
280
- result[env_var.key] = env_var.value
317
+ # Decrypt the value before returning
318
+ value_secret = env_var.get_value_secret()
319
+ result[env_var.key] = value_secret.get_plaintext()
281
320
  return result
282
321
 
283
322
  @enforce_types
284
323
  @trace_method
324
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
285
325
  async def get_sandbox_env_vars_as_dict_async(
286
326
  self, sandbox_config_id: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
287
327
  ) -> Dict[str, str]:
288
328
  env_vars = await self.list_sandbox_env_vars_async(sandbox_config_id, actor, after, limit)
289
- return {env_var.key: env_var.value for env_var in env_vars}
329
+ # Decrypt values before returning
330
+ return {env_var.key: env_var.get_value_secret().get_plaintext() for env_var in env_vars}
290
331
 
291
332
  @enforce_types
292
333
  @trace_method
334
+ @raise_on_invalid_id(param_name="sandbox_config_id", expected_prefix=PrimitiveType.SANDBOX_CONFIG)
293
335
  async def get_sandbox_env_var_by_key_and_sandbox_config_id_async(
294
336
  self, key: str, sandbox_config_id: str, actor: Optional[PydanticUser] = None
295
337
  ) -> Optional[PydanticEnvVar]:
@@ -11,11 +11,12 @@ from letta.orm.source import Source as SourceModel
11
11
  from letta.orm.sources_agents import SourcesAgents
12
12
  from letta.otel.tracing import trace_method
13
13
  from letta.schemas.agent import AgentState as PydanticAgentState
14
- from letta.schemas.enums import VectorDBProvider
14
+ from letta.schemas.enums import PrimitiveType, VectorDBProvider
15
15
  from letta.schemas.source import Source as PydanticSource, SourceUpdate
16
16
  from letta.schemas.user import User as PydanticUser
17
17
  from letta.server.db import db_registry
18
18
  from letta.utils import enforce_types, printd
19
+ from letta.validators import raise_on_invalid_id
19
20
 
20
21
 
21
22
  class SourceManager:
@@ -61,7 +62,11 @@ class SourceManager:
61
62
  @trace_method
62
63
  async def create_source(self, source: PydanticSource, actor: PydanticUser) -> PydanticSource:
63
64
  """Create a new source based on the PydanticSource schema."""
64
- db_source = await self.get_source_by_id(source.id, actor=actor)
65
+ try:
66
+ db_source = await self.get_source_by_id(source.id, actor=actor)
67
+ except NoResultFound:
68
+ db_source = None
69
+
65
70
  if db_source:
66
71
  return db_source
67
72
  else:
@@ -197,6 +202,7 @@ class SourceManager:
197
202
 
198
203
  @enforce_types
199
204
  @trace_method
205
+ @raise_on_invalid_id(param_name="source_id", expected_prefix=PrimitiveType.SOURCE)
200
206
  async def update_source(self, source_id: str, source_update: SourceUpdate, actor: PydanticUser) -> PydanticSource:
201
207
  """Update a source by its ID with the given SourceUpdate object."""
202
208
  async with db_registry.async_session() as session:
@@ -220,6 +226,7 @@ class SourceManager:
220
226
 
221
227
  @enforce_types
222
228
  @trace_method
229
+ @raise_on_invalid_id(param_name="source_id", expected_prefix=PrimitiveType.SOURCE)
223
230
  async def delete_source(self, source_id: str, actor: PydanticUser) -> PydanticSource:
224
231
  """Delete a source by its ID."""
225
232
  async with db_registry.async_session() as session:
@@ -264,6 +271,7 @@ class SourceManager:
264
271
 
265
272
  @enforce_types
266
273
  @trace_method
274
+ @raise_on_invalid_id(param_name="source_id", expected_prefix=PrimitiveType.SOURCE)
267
275
  async def list_attached_agents(
268
276
  self, source_id: str, actor: PydanticUser, ids_only: bool = False
269
277
  ) -> Union[List[PydanticAgentState], List[str]]:
@@ -317,6 +325,7 @@ class SourceManager:
317
325
 
318
326
  @enforce_types
319
327
  @trace_method
328
+ @raise_on_invalid_id(param_name="source_id", expected_prefix=PrimitiveType.SOURCE)
320
329
  async def get_agents_for_source_id(self, source_id: str, actor: PydanticUser) -> List[str]:
321
330
  """
322
331
  Get all agent IDs associated with a given source ID.
@@ -343,14 +352,12 @@ class SourceManager:
343
352
  # TODO: We make actor optional for now, but should most likely be enforced due to security reasons
344
353
  @enforce_types
345
354
  @trace_method
355
+ @raise_on_invalid_id(param_name="source_id", expected_prefix=PrimitiveType.SOURCE)
346
356
  async def get_source_by_id(self, source_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticSource]:
347
357
  """Retrieve a source by its ID."""
348
358
  async with db_registry.async_session() as session:
349
- try:
350
- source = await SourceModel.read_async(db_session=session, identifier=source_id, actor=actor)
351
- return source.to_pydantic()
352
- except NoResultFound:
353
- return None
359
+ source = await SourceModel.read_async(db_session=session, identifier=source_id, actor=actor)
360
+ return source.to_pydantic()
354
361
 
355
362
  @enforce_types
356
363
  @trace_method
@@ -364,7 +371,7 @@ class SourceManager:
364
371
  limit=1,
365
372
  )
366
373
  if not sources:
367
- return None
374
+ raise NoResultFound(f"Source with name={source_name} not found.")
368
375
  else:
369
376
  return sources[0].to_pydantic()
370
377
 
@@ -13,7 +13,7 @@ from letta.orm.sqlalchemy_base import AccessType
13
13
  from letta.orm.step import Step as StepModel
14
14
  from letta.orm.step_metrics import StepMetrics as StepMetricsModel
15
15
  from letta.otel.tracing import get_trace_id, trace_method
16
- from letta.schemas.enums import StepStatus
16
+ from letta.schemas.enums import PrimitiveType, StepStatus
17
17
  from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
18
18
  from letta.schemas.message import Message as PydanticMessage
19
19
  from letta.schemas.openai.chat_completion_response import UsageStatistics
@@ -22,6 +22,7 @@ from letta.schemas.step_metrics import StepMetrics as PydanticStepMetrics
22
22
  from letta.schemas.user import User as PydanticUser
23
23
  from letta.server.db import db_registry
24
24
  from letta.utils import enforce_types
25
+ from letta.validators import raise_on_invalid_id
25
26
 
26
27
 
27
28
  class FeedbackType(str, Enum):
@@ -32,6 +33,8 @@ class FeedbackType(str, Enum):
32
33
  class StepManager:
33
34
  @enforce_types
34
35
  @trace_method
36
+ @raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
37
+ @raise_on_invalid_id(param_name="run_id", expected_prefix=PrimitiveType.RUN)
35
38
  async def list_steps_async(
36
39
  self,
37
40
  actor: PydanticUser,
@@ -79,6 +82,10 @@ class StepManager:
79
82
 
80
83
  @enforce_types
81
84
  @trace_method
85
+ @raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
86
+ @raise_on_invalid_id(param_name="provider_id", expected_prefix=PrimitiveType.PROVIDER)
87
+ @raise_on_invalid_id(param_name="run_id", expected_prefix=PrimitiveType.RUN)
88
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
82
89
  def log_step(
83
90
  self,
84
91
  actor: PydanticUser,
@@ -133,6 +140,10 @@ class StepManager:
133
140
 
134
141
  @enforce_types
135
142
  @trace_method
143
+ @raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
144
+ @raise_on_invalid_id(param_name="provider_id", expected_prefix=PrimitiveType.PROVIDER)
145
+ @raise_on_invalid_id(param_name="run_id", expected_prefix=PrimitiveType.RUN)
146
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
136
147
  async def log_step_async(
137
148
  self,
138
149
  actor: PydanticUser,
@@ -196,6 +207,7 @@ class StepManager:
196
207
 
197
208
  @enforce_types
198
209
  @trace_method
210
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
199
211
  async def get_step_async(self, step_id: str, actor: PydanticUser) -> PydanticStep:
200
212
  async with db_registry.async_session() as session:
201
213
  step = await StepModel.read_async(db_session=session, identifier=step_id, actor=actor)
@@ -203,6 +215,7 @@ class StepManager:
203
215
 
204
216
  @enforce_types
205
217
  @trace_method
218
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
206
219
  async def get_step_metrics_async(self, step_id: str, actor: PydanticUser) -> PydanticStepMetrics:
207
220
  async with db_registry.async_session() as session:
208
221
  metrics = await StepMetricsModel.read_async(db_session=session, identifier=step_id, actor=actor)
@@ -210,6 +223,7 @@ class StepManager:
210
223
 
211
224
  @enforce_types
212
225
  @trace_method
226
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
213
227
  async def add_feedback_async(
214
228
  self, step_id: str, feedback: FeedbackType | None, actor: PydanticUser, tags: list[str] | None = None
215
229
  ) -> PydanticStep:
@@ -225,6 +239,7 @@ class StepManager:
225
239
 
226
240
  @enforce_types
227
241
  @trace_method
242
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
228
243
  async def update_step_transaction_id(self, actor: PydanticUser, step_id: str, transaction_id: str) -> PydanticStep:
229
244
  """Update the transaction ID for a step.
230
245
 
@@ -252,6 +267,7 @@ class StepManager:
252
267
 
253
268
  @enforce_types
254
269
  @trace_method
270
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
255
271
  async def list_step_messages_async(
256
272
  self,
257
273
  step_id: str,
@@ -276,6 +292,7 @@ class StepManager:
276
292
 
277
293
  @enforce_types
278
294
  @trace_method
295
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
279
296
  async def update_step_stop_reason(self, actor: PydanticUser, step_id: str, stop_reason: StopReasonType) -> PydanticStep:
280
297
  """Update the stop reason for a step.
281
298
 
@@ -303,6 +320,7 @@ class StepManager:
303
320
 
304
321
  @enforce_types
305
322
  @trace_method
323
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
306
324
  async def update_step_error_async(
307
325
  self,
308
326
  actor: PydanticUser,
@@ -348,6 +366,7 @@ class StepManager:
348
366
 
349
367
  @enforce_types
350
368
  @trace_method
369
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
351
370
  async def update_step_success_async(
352
371
  self,
353
372
  actor: PydanticUser,
@@ -388,6 +407,7 @@ class StepManager:
388
407
 
389
408
  @enforce_types
390
409
  @trace_method
410
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
391
411
  async def update_step_cancelled_async(
392
412
  self,
393
413
  actor: PydanticUser,
@@ -423,6 +443,9 @@ class StepManager:
423
443
 
424
444
  @enforce_types
425
445
  @trace_method
446
+ @raise_on_invalid_id(param_name="step_id", expected_prefix=PrimitiveType.STEP)
447
+ @raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
448
+ @raise_on_invalid_id(param_name="run_id", expected_prefix=PrimitiveType.RUN)
426
449
  async def record_step_metrics_async(
427
450
  self,
428
451
  actor: PydanticUser,