From e5bda413c0ae636c7af7475a41e354b153a3ea99 Mon Sep 17 00:00:00 2001 From: jnjpng Date: Fri, 5 Dec 2025 11:27:12 -0800 Subject: [PATCH] fix: agent environment variables not using encrypted values (#6520) * base * clean up --------- Co-authored-by: Letta Bot --- letta/schemas/environment_variables.py | 14 +++++++++++++- letta/server/rest_api/routers/v1/agents.py | 2 +- letta/services/tool_sandbox/modal_sandbox.py | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/letta/schemas/environment_variables.py b/letta/schemas/environment_variables.py index c1fd1b3b..f9198470 100644 --- a/letta/schemas/environment_variables.py +++ b/letta/schemas/environment_variables.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import Field +from pydantic import Field, model_validator from letta.schemas.enums import PrimitiveType from letta.schemas.letta_base import LettaBase, OrmMetadataBase @@ -20,6 +20,18 @@ class EnvironmentVariableBase(OrmMetadataBase): # Secret class handles validation and serialization automatically via __get_pydantic_core_schema__ value_enc: Secret | None = Field(None, description="Encrypted value as Secret object") + # TODO: deprecate value and use value_enc + @model_validator(mode="after") + def populate_value_from_encrypted(self) -> "EnvironmentVariableBase": + """Populate value field from value_enc if value is empty but value_enc exists. + + This ensures API responses include the decrypted value in the `value` field + for backwards compatibility with clients that read from `value`. + """ + if (not self.value or self.value == "") and self.value_enc is not None: + self.value = self.value_enc.get_plaintext() or "" + return self + def get_value_secret(self) -> Secret: """Get the value as a Secret object. Prefers encrypted, falls back to plaintext with error logging.""" # If value_enc is already a Secret, return it diff --git a/letta/server/rest_api/routers/v1/agents.py b/letta/server/rest_api/routers/v1/agents.py index be46a2d7..35a3fee1 100644 --- a/letta/server/rest_api/routers/v1/agents.py +++ b/letta/server/rest_api/routers/v1/agents.py @@ -640,7 +640,7 @@ async def run_tool_for_agent( sandbox_env_vars = {} if agent.tool_exec_environment_variables: for env_var in agent.tool_exec_environment_variables: - sandbox_env_vars[env_var.key] = env_var.value + sandbox_env_vars[env_var.key] = env_var.get_value_secret().get_plaintext() # Create tool execution manager and execute the tool from letta.services.tool_executor.tool_execution_manager import ToolExecutionManager diff --git a/letta/services/tool_sandbox/modal_sandbox.py b/letta/services/tool_sandbox/modal_sandbox.py index d7f5e52d..0bd4a6ec 100644 --- a/letta/services/tool_sandbox/modal_sandbox.py +++ b/letta/services/tool_sandbox/modal_sandbox.py @@ -145,7 +145,7 @@ class AsyncToolSandboxModal(AsyncToolSandboxBase): # Add agent-specific environment variables (these override sandbox-level) if agent_state and agent_state.secrets: for secret in agent_state.secrets: - env_vars[secret.key] = secret.value + env_vars[secret.key] = secret.get_value_secret().get_plaintext() # Add any additional env vars passed at runtime (highest priority) if additional_env_vars: