import secrets import string import uuid from pathlib import Path from unittest.mock import patch import pytest from sqlalchemy import delete from letta import create_client from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY from letta.functions.function_sets.base import core_memory_append, core_memory_replace from letta.orm.sandbox_config import SandboxConfig, SandboxEnvironmentVariable from letta.schemas.agent import AgentState from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.environment_variables import AgentEnvironmentVariable, SandboxEnvironmentVariableCreate from letta.schemas.llm_config import LLMConfig from letta.schemas.memory import ChatMemory from letta.schemas.organization import Organization from letta.schemas.sandbox_config import E2BSandboxConfig, LocalSandboxConfig, SandboxConfigCreate, SandboxConfigUpdate, SandboxType from letta.schemas.tool import Tool, ToolCreate from letta.schemas.user import User from letta.services.organization_manager import OrganizationManager from letta.services.sandbox_config_manager import SandboxConfigManager from letta.services.tool_execution_sandbox import ToolExecutionSandbox from letta.services.tool_manager import ToolManager from letta.services.user_manager import UserManager from letta.settings import tool_settings from tests.helpers.utils import create_tool_from_func # Constants namespace = uuid.NAMESPACE_DNS org_name = str(uuid.uuid5(namespace, "test-tool-execution-sandbox-org")) user_name = str(uuid.uuid5(namespace, "test-tool-execution-sandbox-user")) # Fixtures @pytest.fixture(autouse=True) def clear_tables(): """Fixture to clear the organization table before each test.""" from letta.server.server import db_context with db_context() as session: session.execute(delete(SandboxEnvironmentVariable)) session.execute(delete(SandboxConfig)) session.commit() # Commit the deletion @pytest.fixture def check_composio_key_set(): original_api_key = tool_settings.composio_api_key assert original_api_key is not None, "Missing composio key! Cannot execute this test." yield @pytest.fixture def test_organization(): """Fixture to create and return the default organization.""" org = OrganizationManager().create_organization(Organization(name=org_name)) yield org @pytest.fixture def test_user(test_organization): """Fixture to create and return the default user within the default organization.""" user = UserManager().create_user(User(name=user_name, organization_id=test_organization.id)) yield user @pytest.fixture def add_integers_tool(test_user): def add(x: int, y: int) -> int: """ Simple function that adds two integers. Parameters: x (int): The first integer to add. y (int): The second integer to add. Returns: int: The result of adding x and y. """ return x + y tool = create_tool_from_func(add) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def cowsay_tool(test_user): # This defines a tool for a package we definitely do NOT have in letta # If this test passes, that means the tool was correctly executed in a separate Python environment def cowsay() -> str: """ Simple function that uses the cowsay package to print out the secret word env variable. Returns: str: The cowsay ASCII art. """ import os import cowsay cowsay.cow(os.getenv("secret_word")) tool = create_tool_from_func(cowsay) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def get_env_tool(test_user): def get_env() -> str: """ Simple function that returns the secret word env variable. Returns: str: The secret word """ import os secret_word = os.getenv("secret_word") print(secret_word) return secret_word tool = create_tool_from_func(get_env) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def get_warning_tool(test_user): def warn_hello_world() -> str: """ Simple function that warns hello world. Returns: str: hello world """ import warnings msg = "Hello World" warnings.warn(msg) return msg tool = create_tool_from_func(warn_hello_world) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def always_err_tool(test_user): def error() -> str: """ Simple function that errors Returns: str: not important """ # Raise a unusual error so we know it's from this function print("Going to error now") raise ZeroDivisionError("This is an intentionally weird division!") tool = create_tool_from_func(error) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def list_tool(test_user): def create_list(): """Simple function that returns a list""" return [1] * 5 tool = create_tool_from_func(create_list) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def composio_github_star_tool(test_user): tool_manager = ToolManager() tool_create = ToolCreate.from_composio(action_name="GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER") tool = tool_manager.create_or_update_composio_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=test_user) yield tool @pytest.fixture def composio_gmail_get_profile_tool(test_user): tool_manager = ToolManager() tool_create = ToolCreate.from_composio(action_name="GMAIL_GET_PROFILE") tool = tool_manager.create_or_update_composio_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=test_user) yield tool @pytest.fixture def composio_gmail_get_profile_tool(test_user): tool_manager = ToolManager() tool_create = ToolCreate.from_composio(action_name="GMAIL_GET_PROFILE") tool = tool_manager.create_or_update_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=test_user) yield tool @pytest.fixture def clear_core_memory_tool(test_user): def clear_memory(agent_state: "AgentState"): """Clear the core memory""" agent_state.memory.get_block("human").value = "" agent_state.memory.get_block("persona").value = "" tool = create_tool_from_func(clear_memory) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def external_codebase_tool(test_user): from tests.test_tool_sandbox.restaurant_management_system.adjust_menu_prices import adjust_menu_prices tool = create_tool_from_func(adjust_menu_prices) tool = ToolManager().create_or_update_tool(tool, test_user) yield tool @pytest.fixture def agent_state(): client = create_client() agent_state = client.create_agent( memory=ChatMemory(persona="This is the persona", human="My name is Chad"), embedding_config=EmbeddingConfig.default_config(provider="openai"), llm_config=LLMConfig.default_config(model_name="gpt-4"), ) agent_state.tool_rules = [] yield agent_state @pytest.fixture def custom_test_sandbox_config(test_user): """ Fixture to create a consistent local sandbox configuration for tests. Args: test_user: The test user to be used for creating the sandbox configuration. Returns: A tuple containing the SandboxConfigManager and the created sandbox configuration. """ # Create the SandboxConfigManager manager = SandboxConfigManager(tool_settings) # Set the sandbox to be within the external codebase path and use a venv external_codebase_path = str(Path(__file__).parent / "test_tool_sandbox" / "restaurant_management_system") local_sandbox_config = LocalSandboxConfig(sandbox_dir=external_codebase_path, use_venv=True) # Create the sandbox configuration config_create = SandboxConfigCreate(config=local_sandbox_config.model_dump()) # Create or update the sandbox configuration manager.create_or_update_sandbox_config(sandbox_config_create=config_create, actor=test_user) return manager, local_sandbox_config # Tool-specific fixtures @pytest.fixture def core_memory_tools(test_user): """Create all base tools for testing.""" tools = {} for func in [ core_memory_replace, core_memory_append, ]: tool = create_tool_from_func(func) tool = ToolManager().create_or_update_tool(tool, test_user) tools[func.__name__] = tool yield tools # Local sandbox tests @pytest.mark.local_sandbox def test_local_sandbox_default(mock_e2b_api_key_none, add_integers_tool, test_user): args = {"x": 10, "y": 5} # Mock and assert correct pathway was invoked with patch.object(ToolExecutionSandbox, "run_local_dir_sandbox") as mock_run_local_dir_sandbox: sandbox = ToolExecutionSandbox(add_integers_tool.name, args, user=test_user) sandbox.run() mock_run_local_dir_sandbox.assert_called_once() # Run again to get actual response sandbox = ToolExecutionSandbox(add_integers_tool.name, args, user=test_user) result = sandbox.run() assert result.func_return == args["x"] + args["y"] @pytest.mark.local_sandbox def test_local_sandbox_stateful_tool(mock_e2b_api_key_none, clear_core_memory_tool, test_user, agent_state): args = {} # Run again to get actual response sandbox = ToolExecutionSandbox(clear_core_memory_tool.name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert result.agent_state.memory.get_block("human").value == "" assert result.agent_state.memory.get_block("persona").value == "" assert result.func_return is None @pytest.mark.local_sandbox def test_local_sandbox_with_list_rv(mock_e2b_api_key_none, list_tool, test_user): sandbox = ToolExecutionSandbox(list_tool.name, {}, user=test_user) result = sandbox.run() assert len(result.func_return) == 5 @pytest.mark.local_sandbox def test_local_sandbox_env(mock_e2b_api_key_none, get_env_tool, test_user): manager = SandboxConfigManager(tool_settings) # Make a custom local sandbox config sandbox_dir = str(Path(__file__).parent / "test_tool_sandbox") config_create = SandboxConfigCreate(config=LocalSandboxConfig(sandbox_dir=sandbox_dir).model_dump()) config = manager.create_or_update_sandbox_config(config_create, test_user) # Make a environment variable with a long random string key = "secret_word" long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key=key, value=long_random_string), sandbox_config_id=config.id, actor=test_user ) # Create tool and args args = {} # Run the custom sandbox sandbox = ToolExecutionSandbox(get_env_tool.name, args, user=test_user) result = sandbox.run() assert long_random_string in result.func_return @pytest.mark.local_sandbox def test_local_sandbox_per_agent_env(mock_e2b_api_key_none, get_env_tool, agent_state, test_user): manager = SandboxConfigManager(tool_settings) key = "secret_word" # Make a custom local sandbox config sandbox_dir = str(Path(__file__).parent / "test_tool_sandbox") config_create = SandboxConfigCreate(config=LocalSandboxConfig(sandbox_dir=sandbox_dir).model_dump()) config = manager.create_or_update_sandbox_config(config_create, test_user) # Make a environment variable with a long random string # Note: This has an overlapping key with agent state's environment variables # We expect that the agent's env var supersedes this wrong_long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key=key, value=wrong_long_random_string), sandbox_config_id=config.id, actor=test_user ) # Make a environment variable with a long random string and put into agent state correct_long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) agent_state.tool_exec_environment_variables = [ AgentEnvironmentVariable(key=key, value=correct_long_random_string, agent_id=agent_state.id) ] # Create tool and args args = {} # Run the custom sandbox sandbox = ToolExecutionSandbox(get_env_tool.name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert wrong_long_random_string not in result.func_return assert correct_long_random_string in result.func_return @pytest.mark.local_sandbox def test_local_sandbox_e2e_composio_star_github(mock_e2b_api_key_none, check_composio_key_set, composio_github_star_tool, test_user): # Add the composio key manager = SandboxConfigManager(tool_settings) config = manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.LOCAL, actor=test_user) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key="COMPOSIO_API_KEY", value=tool_settings.composio_api_key), sandbox_config_id=config.id, actor=test_user, ) result = ToolExecutionSandbox(composio_github_star_tool.name, {"owner": "letta-ai", "repo": "letta"}, user=test_user).run() assert result.func_return["details"] == "Action executed successfully" # Missing args causes error result = ToolExecutionSandbox(composio_github_star_tool.name, {}, user=test_user).run() assert "Invalid request data provided" in result.func_return @pytest.mark.local_sandbox def test_local_sandbox_multiple_composio_entities( mock_e2b_api_key_none, check_composio_key_set, composio_gmail_get_profile_tool, agent_state, test_user ): # Agent state with no composio entity ID result = ToolExecutionSandbox(composio_gmail_get_profile_tool.name, {}, user=test_user).run(agent_state=agent_state) assert result.func_return["response_data"]["emailAddress"] == "sarah@letta.com" # Agent state with the composio entity set to 'matt' agent_state.tool_exec_environment_variables = [ AgentEnvironmentVariable(key=COMPOSIO_ENTITY_ENV_VAR_KEY, value="matt", agent_id=agent_state.id) ] result = ToolExecutionSandbox(composio_gmail_get_profile_tool.name, {}, user=test_user).run(agent_state=agent_state) assert result.func_return["response_data"]["emailAddress"] == "matt@letta.com" # Agent state with composio entity ID set to default agent_state.tool_exec_environment_variables = [ AgentEnvironmentVariable(key=COMPOSIO_ENTITY_ENV_VAR_KEY, value="default", agent_id=agent_state.id) ] result = ToolExecutionSandbox(composio_gmail_get_profile_tool.name, {}, user=test_user).run(agent_state=agent_state) assert result.func_return["response_data"]["emailAddress"] == "sarah@letta.com" @pytest.mark.local_sandbox def test_local_sandbox_e2e_composio_star_github_without_setting_db_env_vars( mock_e2b_api_key_none, check_composio_key_set, composio_github_star_tool, test_user ): result = ToolExecutionSandbox(composio_github_star_tool.name, {"owner": "letta-ai", "repo": "letta"}, user=test_user).run() assert result.func_return["details"] == "Action executed successfully" @pytest.mark.local_sandbox def test_local_sandbox_e2e_composio_star_github_without_setting_db_env_vars( mock_e2b_api_key_none, check_composio_key_set, composio_github_star_tool, test_user ): result = ToolExecutionSandbox(composio_github_star_tool.name, {"owner": "letta-ai", "repo": "letta"}, user=test_user).run() assert result.func_return["details"] == "Action executed successfully" @pytest.mark.local_sandbox def test_local_sandbox_external_codebase(mock_e2b_api_key_none, custom_test_sandbox_config, external_codebase_tool, test_user): # Set the args args = {"percentage": 10} # Run again to get actual response sandbox = ToolExecutionSandbox(external_codebase_tool.name, args, user=test_user) result = sandbox.run() # Assert that the function return is correct assert result.func_return == "Price Adjustments:\nBurger: $8.99 -> $9.89\nFries: $2.99 -> $3.29\nSoda: $1.99 -> $2.19" assert "Hello World" in result.stdout[0] @pytest.mark.local_sandbox def test_local_sandbox_with_venv_and_warnings_does_not_error( mock_e2b_api_key_none, custom_test_sandbox_config, get_warning_tool, test_user ): sandbox = ToolExecutionSandbox(get_warning_tool.name, {}, user=test_user) result = sandbox.run() assert result.func_return == "Hello World" @pytest.mark.e2b_sandbox def test_local_sandbox_with_venv_errors(mock_e2b_api_key_none, custom_test_sandbox_config, always_err_tool, test_user): sandbox = ToolExecutionSandbox(always_err_tool.name, {}, user=test_user) # run the sandbox result = sandbox.run() assert len(result.stdout) != 0, "stdout not empty" assert "error" in result.stdout[0], "stdout contains printed string" assert len(result.stderr) != 0, "stderr not empty" assert "ZeroDivisionError: This is an intentionally weird division!" in result.stderr[0], "stderr contains expected error" # E2B sandbox tests @pytest.mark.e2b_sandbox def test_e2b_sandbox_default(check_e2b_key_is_set, add_integers_tool, test_user): args = {"x": 10, "y": 5} # Mock and assert correct pathway was invoked with patch.object(ToolExecutionSandbox, "run_e2b_sandbox") as mock_run_local_dir_sandbox: sandbox = ToolExecutionSandbox(add_integers_tool.name, args, user=test_user) sandbox.run() mock_run_local_dir_sandbox.assert_called_once() # Run again to get actual response sandbox = ToolExecutionSandbox(add_integers_tool.name, args, user=test_user) result = sandbox.run() assert int(result.func_return) == args["x"] + args["y"] @pytest.mark.e2b_sandbox def test_e2b_sandbox_pip_installs(check_e2b_key_is_set, cowsay_tool, test_user): manager = SandboxConfigManager(tool_settings) config_create = SandboxConfigCreate(config=E2BSandboxConfig(pip_requirements=["cowsay"]).model_dump()) config = manager.create_or_update_sandbox_config(config_create, test_user) # Add an environment variable key = "secret_word" long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key=key, value=long_random_string), sandbox_config_id=config.id, actor=test_user ) sandbox = ToolExecutionSandbox(cowsay_tool.name, {}, user=test_user) result = sandbox.run() assert long_random_string in result.stdout[0] @pytest.mark.e2b_sandbox def test_e2b_sandbox_reuses_same_sandbox(check_e2b_key_is_set, list_tool, test_user): sandbox = ToolExecutionSandbox(list_tool.name, {}, user=test_user) # Run the function once result = sandbox.run() old_config_fingerprint = result.sandbox_config_fingerprint # Run it again to ensure that there is still only one running sandbox result = sandbox.run() new_config_fingerprint = result.sandbox_config_fingerprint assert old_config_fingerprint == new_config_fingerprint @pytest.mark.e2b_sandbox def test_e2b_sandbox_stateful_tool(check_e2b_key_is_set, clear_core_memory_tool, test_user, agent_state): sandbox = ToolExecutionSandbox(clear_core_memory_tool.name, {}, user=test_user) # run the sandbox result = sandbox.run(agent_state=agent_state) assert result.agent_state.memory.get_block("human").value == "" assert result.agent_state.memory.get_block("persona").value == "" assert result.func_return is None @pytest.mark.e2b_sandbox def test_e2b_sandbox_inject_env_var_existing_sandbox(check_e2b_key_is_set, get_env_tool, test_user): manager = SandboxConfigManager(tool_settings) config_create = SandboxConfigCreate(config=E2BSandboxConfig().model_dump()) config = manager.create_or_update_sandbox_config(config_create, test_user) # Run the custom sandbox once, assert nothing returns because missing env variable sandbox = ToolExecutionSandbox(get_env_tool.name, {}, user=test_user) result = sandbox.run() # response should be None assert result.func_return is None # Add an environment variable key = "secret_word" long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key=key, value=long_random_string), sandbox_config_id=config.id, actor=test_user ) # Assert that the environment variable gets injected correctly, even when the sandbox is NOT refreshed sandbox = ToolExecutionSandbox(get_env_tool.name, {}, user=test_user) result = sandbox.run() assert long_random_string in result.func_return # TODO: There is a near dupe of this test above for local sandbox - we should try to make it parameterized tests to minimize code bloat @pytest.mark.e2b_sandbox def test_e2b_sandbox_per_agent_env(check_e2b_key_is_set, get_env_tool, agent_state, test_user): manager = SandboxConfigManager(tool_settings) key = "secret_word" # Make a custom local sandbox config sandbox_dir = str(Path(__file__).parent / "test_tool_sandbox") config_create = SandboxConfigCreate(config=LocalSandboxConfig(sandbox_dir=sandbox_dir).model_dump()) config = manager.create_or_update_sandbox_config(config_create, test_user) # Make a environment variable with a long random string # Note: This has an overlapping key with agent state's environment variables # We expect that the agent's env var supersedes this wrong_long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key=key, value=wrong_long_random_string), sandbox_config_id=config.id, actor=test_user ) # Make a environment variable with a long random string and put into agent state correct_long_random_string = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)) agent_state.tool_exec_environment_variables = [ AgentEnvironmentVariable(key=key, value=correct_long_random_string, agent_id=agent_state.id) ] # Create tool and args args = {} # Run the custom sandbox sandbox = ToolExecutionSandbox(get_env_tool.name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert wrong_long_random_string not in result.func_return assert correct_long_random_string in result.func_return @pytest.mark.e2b_sandbox def test_e2b_sandbox_config_change_force_recreates_sandbox(check_e2b_key_is_set, list_tool, test_user): manager = SandboxConfigManager(tool_settings) old_timeout = 5 * 60 new_timeout = 10 * 60 # Make the config config_create = SandboxConfigCreate(config=E2BSandboxConfig(timeout=old_timeout)) config = manager.create_or_update_sandbox_config(config_create, test_user) # Run the custom sandbox once, assert a failure gets returned because missing environment variable sandbox = ToolExecutionSandbox(list_tool.name, {}, user=test_user) result = sandbox.run() assert len(result.func_return) == 5 old_config_fingerprint = result.sandbox_config_fingerprint # Change the config config_update = SandboxConfigUpdate(config=E2BSandboxConfig(timeout=new_timeout)) config = manager.update_sandbox_config(config.id, config_update, test_user) # Run again result = ToolExecutionSandbox(list_tool.name, {}, user=test_user).run() new_config_fingerprint = result.sandbox_config_fingerprint assert config.fingerprint() == new_config_fingerprint # Assert the fingerprints are different assert old_config_fingerprint != new_config_fingerprint @pytest.mark.e2b_sandbox def test_e2b_sandbox_with_list_rv(check_e2b_key_is_set, list_tool, test_user): sandbox = ToolExecutionSandbox(list_tool.name, {}, user=test_user) result = sandbox.run() assert len(result.func_return) == 5 @pytest.mark.e2b_sandbox def test_e2b_e2e_composio_star_github(check_e2b_key_is_set, check_composio_key_set, composio_github_star_tool, test_user): # Add the composio key manager = SandboxConfigManager(tool_settings) config = manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.E2B, actor=test_user) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key="COMPOSIO_API_KEY", value=tool_settings.composio_api_key), sandbox_config_id=config.id, actor=test_user, ) result = ToolExecutionSandbox(composio_github_star_tool.name, {"owner": "letta-ai", "repo": "letta"}, user=test_user).run() assert result.func_return["details"] == "Action executed successfully" # Missing args causes error result = ToolExecutionSandbox(composio_github_star_tool.name, {}, user=test_user).run() assert "Invalid request data provided" in result.func_return @pytest.mark.e2b_sandbox def test_e2b_multiple_composio_entities( check_e2b_key_is_set, check_composio_key_set, composio_gmail_get_profile_tool, agent_state, test_user ): manager = SandboxConfigManager(tool_settings) config = manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.E2B, actor=test_user) manager.create_sandbox_env_var( SandboxEnvironmentVariableCreate(key="COMPOSIO_API_KEY", value=tool_settings.composio_api_key), sandbox_config_id=config.id, actor=test_user, ) # Agent state with no composio entity ID result = ToolExecutionSandbox(composio_gmail_get_profile_tool.name, {}, user=test_user).run(agent_state=agent_state) assert result.func_return["response_data"]["emailAddress"] == "sarah@letta.com" # Agent state with the composio entity set to 'matt' agent_state.tool_exec_environment_variables = [ AgentEnvironmentVariable(key=COMPOSIO_ENTITY_ENV_VAR_KEY, value="matt", agent_id=agent_state.id) ] result = ToolExecutionSandbox(composio_gmail_get_profile_tool.name, {}, user=test_user).run(agent_state=agent_state) assert result.func_return["response_data"]["emailAddress"] == "matt@letta.com" # Agent state with composio entity ID set to default agent_state.tool_exec_environment_variables = [ AgentEnvironmentVariable(key=COMPOSIO_ENTITY_ENV_VAR_KEY, value="default", agent_id=agent_state.id) ] result = ToolExecutionSandbox(composio_gmail_get_profile_tool.name, {}, user=test_user).run(agent_state=agent_state) assert result.func_return["response_data"]["emailAddress"] == "sarah@letta.com" # Core memory integration tests class TestCoreMemoryTools: """ Tests for core memory manipulation tools. Tests run in both local sandbox and e2b environments. """ # Local sandbox tests @pytest.mark.local_sandbox def test_core_memory_replace_local(self, mock_e2b_api_key_none, core_memory_tools, test_user, agent_state): """Test successful replacement of content in core memory - local sandbox.""" new_name = "Charles" args = {"label": "human", "old_content": "Chad", "new_content": new_name} sandbox = ToolExecutionSandbox(core_memory_tools["core_memory_replace"].name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert new_name in result.agent_state.memory.get_block("human").value assert result.func_return is None @pytest.mark.local_sandbox def test_core_memory_append_local(self, mock_e2b_api_key_none, core_memory_tools, test_user, agent_state): """Test successful appending of content to core memory - local sandbox.""" append_text = "\nLikes coffee" args = {"label": "human", "content": append_text} sandbox = ToolExecutionSandbox(core_memory_tools["core_memory_append"].name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert append_text in result.agent_state.memory.get_block("human").value assert result.func_return is None @pytest.mark.local_sandbox def test_core_memory_replace_error_local(self, mock_e2b_api_key_none, core_memory_tools, test_user, agent_state): """Test error handling when trying to replace non-existent content - local sandbox.""" nonexistent_name = "Alexander Wang" args = {"label": "human", "old_content": nonexistent_name, "new_content": "Charles"} sandbox = ToolExecutionSandbox(core_memory_tools["core_memory_replace"].name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert len(result.stderr) != 0 assert f"ValueError: Old content '{nonexistent_name}' not found in memory block 'human'" in result.stderr[0] # E2B sandbox tests @pytest.mark.e2b_sandbox def test_core_memory_replace_e2b(self, check_e2b_key_is_set, core_memory_tools, test_user, agent_state): """Test successful replacement of content in core memory - e2b sandbox.""" new_name = "Charles" args = {"label": "human", "old_content": "Chad", "new_content": new_name} sandbox = ToolExecutionSandbox(core_memory_tools["core_memory_replace"].name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert new_name in result.agent_state.memory.get_block("human").value assert result.func_return is None @pytest.mark.e2b_sandbox def test_core_memory_append_e2b(self, check_e2b_key_is_set, core_memory_tools, test_user, agent_state): """Test successful appending of content to core memory - e2b sandbox.""" append_text = "\nLikes coffee" args = {"label": "human", "content": append_text} sandbox = ToolExecutionSandbox(core_memory_tools["core_memory_append"].name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert append_text in result.agent_state.memory.get_block("human").value assert result.func_return is None @pytest.mark.e2b_sandbox def test_core_memory_replace_error_e2b(self, check_e2b_key_is_set, core_memory_tools, test_user, agent_state): """Test error handling when trying to replace non-existent content - e2b sandbox.""" nonexistent_name = "Alexander Wang" args = {"label": "human", "old_content": nonexistent_name, "new_content": "Charles"} sandbox = ToolExecutionSandbox(core_memory_tools["core_memory_replace"].name, args, user=test_user) result = sandbox.run(agent_state=agent_state) assert len(result.stderr) != 0 assert f"ValueError: Old content '{nonexistent_name}' not found in memory block 'human'" in result.stderr[0]