letta-nightly 0.11.7.dev20250915104130__py3-none-any.whl → 0.11.7.dev20250917104122__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 (99) hide show
  1. letta/__init__.py +10 -2
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +0 -1
  4. letta/agent.py +1 -1
  5. letta/agents/letta_agent.py +1 -4
  6. letta/agents/letta_agent_v2.py +2 -1
  7. letta/agents/voice_agent.py +1 -1
  8. letta/functions/function_sets/multi_agent.py +1 -1
  9. letta/functions/helpers.py +1 -1
  10. letta/helpers/converters.py +8 -2
  11. letta/helpers/crypto_utils.py +144 -0
  12. letta/llm_api/llm_api_tools.py +0 -1
  13. letta/llm_api/llm_client_base.py +0 -2
  14. letta/orm/__init__.py +1 -0
  15. letta/orm/agent.py +5 -1
  16. letta/orm/job.py +3 -1
  17. letta/orm/mcp_oauth.py +6 -0
  18. letta/orm/mcp_server.py +7 -1
  19. letta/orm/sqlalchemy_base.py +2 -1
  20. letta/prompts/gpt_system.py +13 -15
  21. letta/prompts/system_prompts/__init__.py +27 -0
  22. letta/prompts/{system/memgpt_chat.txt → system_prompts/memgpt_chat.py} +2 -0
  23. letta/prompts/{system/memgpt_generate_tool.txt → system_prompts/memgpt_generate_tool.py} +4 -2
  24. letta/prompts/{system/memgpt_v2_chat.txt → system_prompts/memgpt_v2_chat.py} +2 -0
  25. letta/prompts/{system/react.txt → system_prompts/react.py} +2 -0
  26. letta/prompts/{system/sleeptime_doc_ingest.txt → system_prompts/sleeptime_doc_ingest.py} +2 -0
  27. letta/prompts/{system/sleeptime_v2.txt → system_prompts/sleeptime_v2.py} +2 -0
  28. letta/prompts/{system/summary_system_prompt.txt → system_prompts/summary_system_prompt.py} +2 -0
  29. letta/prompts/{system/voice_chat.txt → system_prompts/voice_chat.py} +2 -0
  30. letta/prompts/{system/voice_sleeptime.txt → system_prompts/voice_sleeptime.py} +2 -0
  31. letta/prompts/{system/workflow.txt → system_prompts/workflow.py} +2 -0
  32. letta/schemas/agent.py +10 -7
  33. letta/schemas/job.py +10 -0
  34. letta/schemas/mcp.py +146 -6
  35. letta/schemas/provider_trace.py +0 -2
  36. letta/schemas/run.py +2 -0
  37. letta/schemas/secret.py +378 -0
  38. letta/serialize_schemas/marshmallow_agent.py +4 -0
  39. letta/server/rest_api/dependencies.py +37 -0
  40. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +4 -3
  41. letta/server/rest_api/routers/v1/__init__.py +2 -0
  42. letta/server/rest_api/routers/v1/agents.py +115 -107
  43. letta/server/rest_api/routers/v1/archives.py +113 -0
  44. letta/server/rest_api/routers/v1/blocks.py +44 -20
  45. letta/server/rest_api/routers/v1/embeddings.py +3 -3
  46. letta/server/rest_api/routers/v1/folders.py +107 -47
  47. letta/server/rest_api/routers/v1/groups.py +52 -32
  48. letta/server/rest_api/routers/v1/identities.py +110 -21
  49. letta/server/rest_api/routers/v1/internal_templates.py +28 -13
  50. letta/server/rest_api/routers/v1/jobs.py +19 -14
  51. letta/server/rest_api/routers/v1/llms.py +6 -8
  52. letta/server/rest_api/routers/v1/messages.py +14 -14
  53. letta/server/rest_api/routers/v1/organizations.py +1 -1
  54. letta/server/rest_api/routers/v1/providers.py +40 -16
  55. letta/server/rest_api/routers/v1/runs.py +28 -20
  56. letta/server/rest_api/routers/v1/sandbox_configs.py +25 -25
  57. letta/server/rest_api/routers/v1/sources.py +44 -45
  58. letta/server/rest_api/routers/v1/steps.py +27 -25
  59. letta/server/rest_api/routers/v1/tags.py +11 -7
  60. letta/server/rest_api/routers/v1/telemetry.py +11 -6
  61. letta/server/rest_api/routers/v1/tools.py +78 -80
  62. letta/server/rest_api/routers/v1/users.py +1 -1
  63. letta/server/rest_api/routers/v1/voice.py +6 -5
  64. letta/server/rest_api/utils.py +1 -18
  65. letta/services/agent_manager.py +17 -9
  66. letta/services/agent_serialization_manager.py +11 -3
  67. letta/services/archive_manager.py +73 -0
  68. letta/services/file_manager.py +6 -0
  69. letta/services/group_manager.py +2 -1
  70. letta/services/helpers/agent_manager_helper.py +6 -1
  71. letta/services/identity_manager.py +67 -0
  72. letta/services/job_manager.py +18 -2
  73. letta/services/mcp_manager.py +198 -82
  74. letta/services/provider_manager.py +14 -1
  75. letta/services/source_manager.py +11 -1
  76. letta/services/telemetry_manager.py +2 -0
  77. letta/services/tool_executor/composio_tool_executor.py +1 -1
  78. letta/services/tool_manager.py +46 -9
  79. letta/services/tool_sandbox/base.py +2 -3
  80. letta/utils.py +4 -2
  81. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/METADATA +5 -2
  82. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/RECORD +85 -94
  83. letta/prompts/system/memgpt_base.txt +0 -54
  84. letta/prompts/system/memgpt_chat_compressed.txt +0 -13
  85. letta/prompts/system/memgpt_chat_fstring.txt +0 -51
  86. letta/prompts/system/memgpt_convo_only.txt +0 -12
  87. letta/prompts/system/memgpt_doc.txt +0 -50
  88. letta/prompts/system/memgpt_gpt35_extralong.txt +0 -53
  89. letta/prompts/system/memgpt_intuitive_knowledge.txt +0 -31
  90. letta/prompts/system/memgpt_memory_only.txt +0 -29
  91. letta/prompts/system/memgpt_modified_chat.txt +0 -23
  92. letta/prompts/system/memgpt_modified_o1.txt +0 -31
  93. letta/prompts/system/memgpt_offline_memory.txt +0 -23
  94. letta/prompts/system/memgpt_offline_memory_chat.txt +0 -35
  95. letta/prompts/system/memgpt_sleeptime_chat.txt +0 -52
  96. letta/prompts/system/sleeptime.txt +0 -37
  97. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/WHEEL +0 -0
  98. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/entry_points.txt +0 -0
  99. {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/licenses/LICENSE +0 -0
letta/schemas/agent.py CHANGED
@@ -86,6 +86,11 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
86
86
  sources: List[Source] = Field(..., description="The sources used by the agent.")
87
87
  tags: List[str] = Field(..., description="The tags associated with the agent.")
88
88
  tool_exec_environment_variables: List[AgentEnvironmentVariable] = Field(
89
+ default_factory=list,
90
+ description="Deprecated: use `secrets` field instead.",
91
+ deprecated=True,
92
+ )
93
+ secrets: List[AgentEnvironmentVariable] = Field(
89
94
  default_factory=list, description="The environment variables for tool execution specific to this agent."
90
95
  )
91
96
  project_id: Optional[str] = Field(None, description="The id of the project the agent belongs to.")
@@ -133,7 +138,7 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
133
138
  def get_agent_env_vars_as_dict(self) -> Dict[str, str]:
134
139
  # Get environment variables for this agent specifically
135
140
  per_agent_env_vars = {}
136
- for agent_env_var_obj in self.tool_exec_environment_variables:
141
+ for agent_env_var_obj in self.secrets:
137
142
  per_agent_env_vars[agent_env_var_obj.key] = agent_env_var_obj.value
138
143
  return per_agent_env_vars
139
144
 
@@ -222,9 +227,8 @@ class CreateAgent(BaseModel, validate_assignment=True): #
222
227
  deprecated=True,
223
228
  description="Deprecated: Project should now be passed via the X-Project header instead of in the request body. If using the sdk, this can be done via the new x_project field below.",
224
229
  )
225
- tool_exec_environment_variables: Optional[Dict[str, str]] = Field(
226
- None, description="The environment variables for tool execution specific to this agent."
227
- )
230
+ tool_exec_environment_variables: Optional[Dict[str, str]] = Field(None, description="Deprecated: use `secrets` field instead.")
231
+ secrets: Optional[Dict[str, str]] = Field(None, description="The environment variables for tool execution specific to this agent.")
228
232
  memory_variables: Optional[Dict[str, str]] = Field(None, description="The variables that should be set for the agent.")
229
233
  project_id: Optional[str] = Field(None, description="The id of the project the agent belongs to.")
230
234
  template_id: Optional[str] = Field(None, description="The id of the template the agent belongs to.")
@@ -328,9 +332,8 @@ class UpdateAgent(BaseModel):
328
332
  message_ids: Optional[List[str]] = Field(None, description="The ids of the messages in the agent's in-context memory.")
329
333
  description: Optional[str] = Field(None, description="The description of the agent.")
330
334
  metadata: Optional[Dict] = Field(None, description="The metadata of the agent.")
331
- tool_exec_environment_variables: Optional[Dict[str, str]] = Field(
332
- None, description="The environment variables for tool execution specific to this agent."
333
- )
335
+ tool_exec_environment_variables: Optional[Dict[str, str]] = Field(None, description="Deprecated: use `secrets` field instead")
336
+ secrets: Optional[Dict[str, str]] = Field(None, description="The environment variables for tool execution specific to this agent.")
334
337
  project_id: Optional[str] = Field(None, description="The id of the project the agent belongs to.")
335
338
  template_id: Optional[str] = Field(None, description="The id of the template the agent belongs to.")
336
339
  base_template_id: Optional[str] = Field(None, description="The base template id of the agent.")
letta/schemas/job.py CHANGED
@@ -8,16 +8,26 @@ from letta.helpers.datetime_helpers import get_utc_time
8
8
  from letta.schemas.enums import JobStatus, JobType
9
9
  from letta.schemas.letta_base import OrmMetadataBase
10
10
  from letta.schemas.letta_message import MessageType
11
+ from letta.schemas.letta_stop_reason import StopReasonType
11
12
 
12
13
 
13
14
  class JobBase(OrmMetadataBase):
14
15
  __id_prefix__ = "job"
15
16
  status: JobStatus = Field(default=JobStatus.created, description="The status of the job.")
16
17
  created_at: datetime = Field(default_factory=get_utc_time, description="The unix timestamp of when the job was created.")
18
+
19
+ # completion related
17
20
  completed_at: Optional[datetime] = Field(None, description="The unix timestamp of when the job was completed.")
21
+ stop_reason: Optional[StopReasonType] = Field(None, description="The reason why the job was stopped.")
22
+
23
+ # metadata
18
24
  metadata: Optional[dict] = Field(None, validation_alias="metadata_", description="The metadata of the job.")
19
25
  job_type: JobType = Field(default=JobType.JOB, description="The type of the job.")
20
26
 
27
+ ## TODO: Run-specific fields
28
+ # background: Optional[bool] = Field(None, description="Whether the job was created in background mode.")
29
+ # agent_id: Optional[str] = Field(None, description="The agent associated with this job/run.")
30
+
21
31
  callback_url: Optional[str] = Field(None, description="If set, POST to this URL when the job completes.")
22
32
  callback_sent_at: Optional[datetime] = Field(None, description="Timestamp when the callback was last attempted.")
23
33
  callback_status_code: Optional[int] = Field(None, description="HTTP status code returned by the callback endpoint.")
letta/schemas/mcp.py CHANGED
@@ -13,6 +13,8 @@ from letta.functions.mcp_client.types import (
13
13
  )
14
14
  from letta.orm.mcp_oauth import OAuthSessionStatus
15
15
  from letta.schemas.letta_base import LettaBase
16
+ from letta.schemas.secret import Secret, SecretDict
17
+ from letta.settings import settings
16
18
 
17
19
 
18
20
  class BaseMCPServer(LettaBase):
@@ -29,6 +31,9 @@ class MCPServer(BaseMCPServer):
29
31
  token: Optional[str] = Field(None, description="The access token or API key for the MCP server (used for authentication)")
30
32
  custom_headers: Optional[Dict[str, str]] = Field(None, description="Custom authentication headers as key-value pairs")
31
33
 
34
+ token_enc: Optional[str] = Field(None, description="Encrypted token")
35
+ custom_headers_enc: Optional[str] = Field(None, description="Encrypted custom headers")
36
+
32
37
  # stdio config
33
38
  stdio_config: Optional[StdioServerConfig] = Field(
34
39
  None, description="The configuration for the server (MCP 'local' client will run this command)"
@@ -41,18 +46,76 @@ class MCPServer(BaseMCPServer):
41
46
  last_updated_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
42
47
  metadata_: Optional[Dict[str, Any]] = Field(default_factory=dict, description="A dictionary of additional metadata for the tool.")
43
48
 
49
+ def get_token_secret(self) -> Secret:
50
+ """Get the token as a Secret object, preferring encrypted over plaintext."""
51
+ return Secret.from_db(self.token_enc, self.token)
52
+
53
+ def get_custom_headers_secret(self) -> SecretDict:
54
+ """Get custom headers as a SecretDict object, preferring encrypted over plaintext."""
55
+ return SecretDict.from_db(self.custom_headers_enc, self.custom_headers)
56
+
57
+ def set_token_secret(self, secret: Secret) -> None:
58
+ """Set token from a Secret object, updating both encrypted and plaintext fields."""
59
+ secret_dict = secret.to_dict()
60
+ self.token_enc = secret_dict["encrypted"]
61
+ # Only set plaintext during migration phase
62
+ if not secret._was_encrypted:
63
+ self.token = secret_dict["plaintext"]
64
+ else:
65
+ self.token = None
66
+
67
+ def set_custom_headers_secret(self, secret: SecretDict) -> None:
68
+ """Set custom headers from a SecretDict object, updating both fields."""
69
+ secret_dict = secret.to_dict()
70
+ self.custom_headers_enc = secret_dict["encrypted"]
71
+ # Only set plaintext during migration phase
72
+ if not secret._was_encrypted:
73
+ self.custom_headers = secret_dict["plaintext"]
74
+ else:
75
+ self.custom_headers = None
76
+
77
+ def model_dump(self, to_orm: bool = False, **kwargs):
78
+ """Override model_dump to handle encryption when saving to database."""
79
+ data = super().model_dump(to_orm=to_orm, **kwargs)
80
+
81
+ if to_orm and settings.encryption_key:
82
+ # Encrypt token if present
83
+ if self.token is not None:
84
+ token_secret = Secret.from_plaintext(self.token)
85
+ secret_dict = token_secret.to_dict()
86
+ data["token_enc"] = secret_dict["encrypted"]
87
+ # Keep plaintext for dual-write during migration
88
+ data["token"] = secret_dict["plaintext"]
89
+
90
+ # Encrypt custom headers if present
91
+ if self.custom_headers is not None:
92
+ headers_secret = SecretDict.from_plaintext(self.custom_headers)
93
+ secret_dict = headers_secret.to_dict()
94
+ data["custom_headers_enc"] = secret_dict["encrypted"]
95
+ # Keep plaintext for dual-write during migration
96
+ data["custom_headers"] = secret_dict["plaintext"]
97
+
98
+ return data
99
+
44
100
  def to_config(
45
101
  self,
46
102
  environment_variables: Optional[Dict[str, str]] = None,
47
103
  resolve_variables: bool = True,
48
104
  ) -> Union[SSEServerConfig, StdioServerConfig, StreamableHTTPServerConfig]:
105
+ # Get decrypted values for use in config
106
+ token_secret = self.get_token_secret()
107
+ token_plaintext = token_secret.get_plaintext()
108
+
109
+ headers_secret = self.get_custom_headers_secret()
110
+ headers_plaintext = headers_secret.get_plaintext()
111
+
49
112
  if self.server_type == MCPServerType.SSE:
50
113
  config = SSEServerConfig(
51
114
  server_name=self.server_name,
52
115
  server_url=self.server_url,
53
- auth_header=MCP_AUTH_HEADER_AUTHORIZATION if self.token and not self.custom_headers else None,
54
- auth_token=f"{MCP_AUTH_TOKEN_BEARER_PREFIX} {self.token}" if self.token and not self.custom_headers else None,
55
- custom_headers=self.custom_headers,
116
+ auth_header=MCP_AUTH_HEADER_AUTHORIZATION if token_plaintext and not headers_plaintext else None,
117
+ auth_token=f"{MCP_AUTH_TOKEN_BEARER_PREFIX} {token_plaintext}" if token_plaintext and not headers_plaintext else None,
118
+ custom_headers=headers_plaintext,
56
119
  )
57
120
  if resolve_variables:
58
121
  config.resolve_environment_variables(environment_variables)
@@ -70,9 +133,9 @@ class MCPServer(BaseMCPServer):
70
133
  config = StreamableHTTPServerConfig(
71
134
  server_name=self.server_name,
72
135
  server_url=self.server_url,
73
- auth_header=MCP_AUTH_HEADER_AUTHORIZATION if self.token and not self.custom_headers else None,
74
- auth_token=f"{MCP_AUTH_TOKEN_BEARER_PREFIX} {self.token}" if self.token and not self.custom_headers else None,
75
- custom_headers=self.custom_headers,
136
+ auth_header=MCP_AUTH_HEADER_AUTHORIZATION if token_plaintext and not headers_plaintext else None,
137
+ auth_token=f"{MCP_AUTH_TOKEN_BEARER_PREFIX} {token_plaintext}" if token_plaintext and not headers_plaintext else None,
138
+ custom_headers=headers_plaintext,
76
139
  )
77
140
  if resolve_variables:
78
141
  config.resolve_environment_variables(environment_variables)
@@ -138,11 +201,18 @@ class MCPOAuthSession(BaseMCPOAuth):
138
201
  expires_at: Optional[datetime] = Field(None, description="Token expiry time")
139
202
  scope: Optional[str] = Field(None, description="OAuth scope")
140
203
 
204
+ # Encrypted token fields (for internal use)
205
+ access_token_enc: Optional[str] = Field(None, description="Encrypted OAuth access token")
206
+ refresh_token_enc: Optional[str] = Field(None, description="Encrypted OAuth refresh token")
207
+
141
208
  # Client configuration
142
209
  client_id: Optional[str] = Field(None, description="OAuth client ID")
143
210
  client_secret: Optional[str] = Field(None, description="OAuth client secret")
144
211
  redirect_uri: Optional[str] = Field(None, description="OAuth redirect URI")
145
212
 
213
+ # Encrypted client secret (for internal use)
214
+ client_secret_enc: Optional[str] = Field(None, description="Encrypted OAuth client secret")
215
+
146
216
  # Session state
147
217
  status: OAuthSessionStatus = Field(default=OAuthSessionStatus.PENDING, description="Session status")
148
218
 
@@ -150,6 +220,76 @@ class MCPOAuthSession(BaseMCPOAuth):
150
220
  created_at: datetime = Field(default_factory=datetime.now, description="Session creation time")
151
221
  updated_at: datetime = Field(default_factory=datetime.now, description="Last update time")
152
222
 
223
+ def get_access_token_secret(self) -> Secret:
224
+ """Get the access token as a Secret object, preferring encrypted over plaintext."""
225
+ return Secret.from_db(self.access_token_enc, self.access_token)
226
+
227
+ def get_refresh_token_secret(self) -> Secret:
228
+ """Get the refresh token as a Secret object, preferring encrypted over plaintext."""
229
+ return Secret.from_db(self.refresh_token_enc, self.refresh_token)
230
+
231
+ def get_client_secret_secret(self) -> Secret:
232
+ """Get the client secret as a Secret object, preferring encrypted over plaintext."""
233
+ return Secret.from_db(self.client_secret_enc, self.client_secret)
234
+
235
+ def set_access_token_secret(self, secret: Secret) -> None:
236
+ """Set access token from a Secret object."""
237
+ secret_dict = secret.to_dict()
238
+ self.access_token_enc = secret_dict["encrypted"]
239
+ if not secret._was_encrypted:
240
+ self.access_token = secret_dict["plaintext"]
241
+ else:
242
+ self.access_token = None
243
+
244
+ def set_refresh_token_secret(self, secret: Secret) -> None:
245
+ """Set refresh token from a Secret object."""
246
+ secret_dict = secret.to_dict()
247
+ self.refresh_token_enc = secret_dict["encrypted"]
248
+ if not secret._was_encrypted:
249
+ self.refresh_token = secret_dict["plaintext"]
250
+ else:
251
+ self.refresh_token = None
252
+
253
+ def set_client_secret_secret(self, secret: Secret) -> None:
254
+ """Set client secret from a Secret object."""
255
+ secret_dict = secret.to_dict()
256
+ self.client_secret_enc = secret_dict["encrypted"]
257
+ if not secret._was_encrypted:
258
+ self.client_secret = secret_dict["plaintext"]
259
+ else:
260
+ self.client_secret = None
261
+
262
+ def model_dump(self, to_orm: bool = False, **kwargs):
263
+ """Override model_dump to handle encryption when saving to database."""
264
+ data = super().model_dump(to_orm=to_orm, **kwargs)
265
+
266
+ if to_orm and settings.encryption_key:
267
+ # Encrypt access token if present
268
+ if self.access_token is not None:
269
+ token_secret = Secret.from_plaintext(self.access_token)
270
+ secret_dict = token_secret.to_dict()
271
+ data["access_token_enc"] = secret_dict["encrypted"]
272
+ # Keep plaintext for dual-write during migration
273
+ data["access_token"] = secret_dict["plaintext"]
274
+
275
+ # Encrypt refresh token if present
276
+ if self.refresh_token is not None:
277
+ token_secret = Secret.from_plaintext(self.refresh_token)
278
+ secret_dict = token_secret.to_dict()
279
+ data["refresh_token_enc"] = secret_dict["encrypted"]
280
+ # Keep plaintext for dual-write during migration
281
+ data["refresh_token"] = secret_dict["plaintext"]
282
+
283
+ # Encrypt client secret if present
284
+ if self.client_secret is not None:
285
+ secret = Secret.from_plaintext(self.client_secret)
286
+ secret_dict = secret.to_dict()
287
+ data["client_secret_enc"] = secret_dict["encrypted"]
288
+ # Keep plaintext for dual-write during migration
289
+ data["client_secret"] = secret_dict["plaintext"]
290
+
291
+ return data
292
+
153
293
 
154
294
  class MCPOAuthSessionCreate(BaseMCPOAuth):
155
295
  """Create a new OAuth session."""
@@ -19,7 +19,6 @@ class ProviderTraceCreate(BaseModel):
19
19
  request_json: dict[str, Any] = Field(..., description="JSON content of the provider request")
20
20
  response_json: dict[str, Any] = Field(..., description="JSON content of the provider response")
21
21
  step_id: str = Field(None, description="ID of the step that this trace is associated with")
22
- organization_id: str = Field(..., description="The unique identifier of the organization.")
23
22
 
24
23
 
25
24
  class ProviderTrace(BaseProviderTrace):
@@ -39,5 +38,4 @@ class ProviderTrace(BaseProviderTrace):
39
38
  request_json: Dict[str, Any] = Field(..., description="JSON content of the provider request")
40
39
  response_json: Dict[str, Any] = Field(..., description="JSON content of the provider response")
41
40
  step_id: Optional[str] = Field(None, description="ID of the step that this trace is associated with")
42
- organization_id: str = Field(..., description="The unique identifier of the organization.")
43
41
  created_at: datetime = Field(default_factory=get_utc_time, description="The timestamp when the object was created.")
letta/schemas/run.py CHANGED
@@ -4,6 +4,7 @@ from pydantic import Field
4
4
 
5
5
  from letta.schemas.enums import JobType
6
6
  from letta.schemas.job import Job, JobBase, LettaRequestConfig
7
+ from letta.schemas.letta_stop_reason import StopReasonType
7
8
 
8
9
 
9
10
  class RunBase(JobBase):
@@ -29,6 +30,7 @@ class Run(RunBase):
29
30
  id: str = RunBase.generate_id_field()
30
31
  user_id: Optional[str] = Field(None, description="The unique identifier of the user associated with the run.")
31
32
  request_config: Optional[LettaRequestConfig] = Field(None, description="The request configuration for the run.")
33
+ stop_reason: Optional[StopReasonType] = Field(None, description="The reason why the run was stopped.")
32
34
 
33
35
  @classmethod
34
36
  def from_job(cls, job: Job) -> "Run":