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.
- letta/__init__.py +10 -2
- letta/adapters/letta_llm_request_adapter.py +0 -1
- letta/adapters/letta_llm_stream_adapter.py +0 -1
- letta/agent.py +1 -1
- letta/agents/letta_agent.py +1 -4
- letta/agents/letta_agent_v2.py +2 -1
- letta/agents/voice_agent.py +1 -1
- letta/functions/function_sets/multi_agent.py +1 -1
- letta/functions/helpers.py +1 -1
- letta/helpers/converters.py +8 -2
- letta/helpers/crypto_utils.py +144 -0
- letta/llm_api/llm_api_tools.py +0 -1
- letta/llm_api/llm_client_base.py +0 -2
- letta/orm/__init__.py +1 -0
- letta/orm/agent.py +5 -1
- letta/orm/job.py +3 -1
- letta/orm/mcp_oauth.py +6 -0
- letta/orm/mcp_server.py +7 -1
- letta/orm/sqlalchemy_base.py +2 -1
- letta/prompts/gpt_system.py +13 -15
- letta/prompts/system_prompts/__init__.py +27 -0
- letta/prompts/{system/memgpt_chat.txt → system_prompts/memgpt_chat.py} +2 -0
- letta/prompts/{system/memgpt_generate_tool.txt → system_prompts/memgpt_generate_tool.py} +4 -2
- letta/prompts/{system/memgpt_v2_chat.txt → system_prompts/memgpt_v2_chat.py} +2 -0
- letta/prompts/{system/react.txt → system_prompts/react.py} +2 -0
- letta/prompts/{system/sleeptime_doc_ingest.txt → system_prompts/sleeptime_doc_ingest.py} +2 -0
- letta/prompts/{system/sleeptime_v2.txt → system_prompts/sleeptime_v2.py} +2 -0
- letta/prompts/{system/summary_system_prompt.txt → system_prompts/summary_system_prompt.py} +2 -0
- letta/prompts/{system/voice_chat.txt → system_prompts/voice_chat.py} +2 -0
- letta/prompts/{system/voice_sleeptime.txt → system_prompts/voice_sleeptime.py} +2 -0
- letta/prompts/{system/workflow.txt → system_prompts/workflow.py} +2 -0
- letta/schemas/agent.py +10 -7
- letta/schemas/job.py +10 -0
- letta/schemas/mcp.py +146 -6
- letta/schemas/provider_trace.py +0 -2
- letta/schemas/run.py +2 -0
- letta/schemas/secret.py +378 -0
- letta/serialize_schemas/marshmallow_agent.py +4 -0
- letta/server/rest_api/dependencies.py +37 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +4 -3
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +115 -107
- letta/server/rest_api/routers/v1/archives.py +113 -0
- letta/server/rest_api/routers/v1/blocks.py +44 -20
- letta/server/rest_api/routers/v1/embeddings.py +3 -3
- letta/server/rest_api/routers/v1/folders.py +107 -47
- letta/server/rest_api/routers/v1/groups.py +52 -32
- letta/server/rest_api/routers/v1/identities.py +110 -21
- letta/server/rest_api/routers/v1/internal_templates.py +28 -13
- letta/server/rest_api/routers/v1/jobs.py +19 -14
- letta/server/rest_api/routers/v1/llms.py +6 -8
- letta/server/rest_api/routers/v1/messages.py +14 -14
- letta/server/rest_api/routers/v1/organizations.py +1 -1
- letta/server/rest_api/routers/v1/providers.py +40 -16
- letta/server/rest_api/routers/v1/runs.py +28 -20
- letta/server/rest_api/routers/v1/sandbox_configs.py +25 -25
- letta/server/rest_api/routers/v1/sources.py +44 -45
- letta/server/rest_api/routers/v1/steps.py +27 -25
- letta/server/rest_api/routers/v1/tags.py +11 -7
- letta/server/rest_api/routers/v1/telemetry.py +11 -6
- letta/server/rest_api/routers/v1/tools.py +78 -80
- letta/server/rest_api/routers/v1/users.py +1 -1
- letta/server/rest_api/routers/v1/voice.py +6 -5
- letta/server/rest_api/utils.py +1 -18
- letta/services/agent_manager.py +17 -9
- letta/services/agent_serialization_manager.py +11 -3
- letta/services/archive_manager.py +73 -0
- letta/services/file_manager.py +6 -0
- letta/services/group_manager.py +2 -1
- letta/services/helpers/agent_manager_helper.py +6 -1
- letta/services/identity_manager.py +67 -0
- letta/services/job_manager.py +18 -2
- letta/services/mcp_manager.py +198 -82
- letta/services/provider_manager.py +14 -1
- letta/services/source_manager.py +11 -1
- letta/services/telemetry_manager.py +2 -0
- letta/services/tool_executor/composio_tool_executor.py +1 -1
- letta/services/tool_manager.py +46 -9
- letta/services/tool_sandbox/base.py +2 -3
- letta/utils.py +4 -2
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/METADATA +5 -2
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/RECORD +85 -94
- letta/prompts/system/memgpt_base.txt +0 -54
- letta/prompts/system/memgpt_chat_compressed.txt +0 -13
- letta/prompts/system/memgpt_chat_fstring.txt +0 -51
- letta/prompts/system/memgpt_convo_only.txt +0 -12
- letta/prompts/system/memgpt_doc.txt +0 -50
- letta/prompts/system/memgpt_gpt35_extralong.txt +0 -53
- letta/prompts/system/memgpt_intuitive_knowledge.txt +0 -31
- letta/prompts/system/memgpt_memory_only.txt +0 -29
- letta/prompts/system/memgpt_modified_chat.txt +0 -23
- letta/prompts/system/memgpt_modified_o1.txt +0 -31
- letta/prompts/system/memgpt_offline_memory.txt +0 -23
- letta/prompts/system/memgpt_offline_memory_chat.txt +0 -35
- letta/prompts/system/memgpt_sleeptime_chat.txt +0 -52
- letta/prompts/system/sleeptime.txt +0 -37
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/entry_points.txt +0 -0
- {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.
|
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
|
-
|
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
|
-
|
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
|
54
|
-
auth_token=f"{MCP_AUTH_TOKEN_BEARER_PREFIX} {
|
55
|
-
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
|
74
|
-
auth_token=f"{MCP_AUTH_TOKEN_BEARER_PREFIX} {
|
75
|
-
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."""
|
letta/schemas/provider_trace.py
CHANGED
@@ -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":
|