chore: officially migrate to submodule (#4502)

* remove apps/core and apps/fern

* fix precommit

* add submodule updates in workflows

* submodule

* remove core tests

* update core revision

* Add submodules: true to all GitHub workflows

- Ensure all workflows can access git submodules
- Add submodules support to deployment, test, and CI workflows
- Fix YAML syntax issues in workflow files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* remove core-lint

* upgrade core with latest main of oss

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Kian Jones
2025-09-09 12:45:53 -07:00
committed by GitHub
parent 48b5722095
commit 22f70ca07c
953 changed files with 0 additions and 181472 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,233 +0,0 @@
import os
import platform
import subprocess
import venv
from typing import TYPE_CHECKING, Dict, Optional
from datamodel_code_generator import DataModelType, PythonVersion
from datamodel_code_generator.model import get_data_model_types
from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
from letta.log import get_logger
from letta.schemas.sandbox_config import LocalSandboxConfig
if TYPE_CHECKING:
from letta.schemas.tool import Tool
logger = get_logger(__name__)
def find_python_executable(local_configs: LocalSandboxConfig) -> str:
"""
Determines the Python executable path based on sandbox configuration and platform.
Resolves any '~' (tilde) paths to absolute paths.
Returns:
str: Full path to the Python binary.
"""
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
if not local_configs.use_venv:
return "python.exe" if platform.system().lower().startswith("win") else "python3"
venv_path = os.path.join(sandbox_dir, local_configs.venv_name)
python_exec = (
os.path.join(venv_path, "Scripts", "python.exe")
if platform.system().startswith("Win")
else os.path.join(venv_path, "bin", "python3")
)
if not os.path.isfile(python_exec):
raise FileNotFoundError(f"Python executable not found: {python_exec}. Ensure the virtual environment exists.")
return python_exec
def run_subprocess(command: list, env: Optional[Dict[str, str]] = None, fail_msg: str = "Command failed"):
"""
Helper to execute a subprocess with logging and error handling.
Args:
command (list): The command to run as a list of arguments.
env (dict, optional): The environment variables to use for the process.
fail_msg (str): The error message to log in case of failure.
Raises:
RuntimeError: If the subprocess execution fails.
"""
logger.info(f"Running command: {' '.join(command)}")
try:
result = subprocess.run(command, check=True, capture_output=True, text=True, env=env)
logger.info(f"Command successful. Output:\n{result.stdout}")
return result.stdout
except subprocess.CalledProcessError as e:
logger.error(f"{fail_msg}\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
raise RuntimeError(f"{fail_msg}: {e.stderr.strip()}") from e
except Exception as e:
logger.error(f"{fail_msg}: {e}")
raise RuntimeError(f"{fail_msg}: {e}")
def ensure_pip_is_up_to_date(python_exec: str, env: Optional[Dict[str, str]] = None):
"""
Ensures pip, setuptools, and wheel are up to date before installing any other dependencies.
Args:
python_exec (str): Path to the Python executable to use.
env (dict, optional): Environment variables to pass to subprocess.
"""
run_subprocess(
[python_exec, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"],
env=env,
fail_msg="Failed to upgrade pip, setuptools, and wheel.",
)
def install_pip_requirements_for_sandbox(
local_configs: LocalSandboxConfig,
upgrade: bool = True,
user_install_if_no_venv: bool = False,
env: Optional[Dict[str, str]] = None,
tool: Optional["Tool"] = None,
):
"""
Installs the specified pip requirements inside the correct environment (venv or system).
Installs both sandbox-level and tool-specific pip requirements.
"""
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
local_configs.sandbox_dir = sandbox_dir # Update the object to store the absolute path
python_exec = find_python_executable(local_configs)
# If using a virtual environment, upgrade pip before installing dependencies.
if local_configs.use_venv:
ensure_pip_is_up_to_date(python_exec, env=env)
# Collect all pip requirements
all_packages = []
# Add sandbox-level pip requirements
if local_configs.pip_requirements:
packages = [f"{req.name}=={req.version}" if req.version else req.name for req in local_configs.pip_requirements]
all_packages.extend(packages)
logger.debug(f"Added sandbox pip requirements: {packages}")
# Add tool-specific pip requirements
if tool and tool.pip_requirements:
tool_packages = [str(req) for req in tool.pip_requirements]
all_packages.extend(tool_packages)
logger.debug(f"Added tool pip requirements for {tool.name}: {tool_packages}")
if not all_packages:
logger.debug("No pip requirements specified; skipping installation.")
return
# Construct pip install command
pip_cmd = [python_exec, "-m", "pip", "install"]
if upgrade:
pip_cmd.append("--upgrade")
pip_cmd += all_packages
if user_install_if_no_venv and not local_configs.use_venv:
pip_cmd.append("--user")
# Enhanced error message for better debugging
sandbox_packages = [f"{req.name}=={req.version}" if req.version else req.name for req in (local_configs.pip_requirements or [])]
tool_packages = [str(req) for req in (tool.pip_requirements if tool and tool.pip_requirements else [])]
error_details = []
if sandbox_packages:
error_details.append(f"sandbox requirements: {', '.join(sandbox_packages)}")
if tool_packages:
error_details.append(f"tool requirements: {', '.join(tool_packages)}")
context = f" ({'; '.join(error_details)})" if error_details else ""
fail_msg = f"Failed to install pip packages{context}. This may be due to package version incompatibility. Consider updating package versions or removing version constraints."
run_subprocess(pip_cmd, env=env, fail_msg=fail_msg)
def create_venv_for_local_sandbox(sandbox_dir_path: str, venv_path: str, env: Dict[str, str], force_recreate: bool):
"""
Creates a virtual environment for the sandbox. If force_recreate is True, deletes and recreates the venv.
Args:
sandbox_dir_path (str): Path to the sandbox directory.
venv_path (str): Path to the virtual environment directory.
env (dict): Environment variables to use.
force_recreate (bool): If True, delete and recreate the virtual environment.
"""
sandbox_dir_path = os.path.expanduser(sandbox_dir_path)
venv_path = os.path.expanduser(venv_path)
# If venv exists and force_recreate is True, delete it
if force_recreate and os.path.isdir(venv_path):
logger.warning(f"Force recreating virtual environment at: {venv_path}")
import shutil
shutil.rmtree(venv_path)
# Create venv if it does not exist
if not os.path.isdir(venv_path):
logger.info(f"Creating new virtual environment at {venv_path}")
venv.create(venv_path, with_pip=True)
pip_path = os.path.join(venv_path, "bin", "pip")
try:
# Step 2: Upgrade pip
logger.info("Upgrading pip in the virtual environment...")
subprocess.run([pip_path, "install", "--upgrade", "pip"], env=env, check=True)
# Step 3: Install packages from requirements.txt if available
requirements_txt_path = os.path.join(sandbox_dir_path, "requirements.txt")
if os.path.isfile(requirements_txt_path):
logger.info(f"Installing packages from requirements file: {requirements_txt_path}")
subprocess.run([pip_path, "install", "-r", requirements_txt_path], env=env, check=True)
logger.info("Successfully installed packages from requirements.txt")
else:
logger.warning("No requirements.txt file found. Skipping package installation.")
except subprocess.CalledProcessError as e:
logger.error(f"Error while setting up the virtual environment: {e}")
raise RuntimeError(f"Failed to set up the virtual environment: {e}")
def add_imports_and_pydantic_schemas_for_args(args_json_schema: dict) -> str:
data_model_types = get_data_model_types(DataModelType.PydanticV2BaseModel, target_python_version=PythonVersion.PY_311)
parser = JsonSchemaParser(
str(args_json_schema),
data_model_type=data_model_types.data_model,
data_model_root_type=data_model_types.root_model,
data_model_field_type=data_model_types.field_model,
data_type_manager_type=data_model_types.data_type_manager,
dump_resolve_reference_action=data_model_types.dump_resolve_reference_action,
)
result = parser.parse()
return result
def prepare_local_sandbox(
local_cfg: LocalSandboxConfig,
env: Dict[str, str],
force_recreate: bool = False,
) -> None:
"""
Ensure the sandbox virtual-env is freshly created and that
requirements are installed. Uses your existing helpers.
"""
sandbox_dir = os.path.expanduser(local_cfg.sandbox_dir)
venv_path = os.path.join(sandbox_dir, local_cfg.venv_name)
create_venv_for_local_sandbox(
sandbox_dir_path=sandbox_dir,
venv_path=venv_path,
env=env,
force_recreate=force_recreate,
)
install_pip_requirements_for_sandbox(
local_cfg,
upgrade=True,
user_install_if_no_venv=False,
env=env,
)

View File

@@ -1,104 +0,0 @@
import ast
import base64
import pickle
from typing import Any, Union
from letta.constants import REQUEST_HEARTBEAT_DESCRIPTION, REQUEST_HEARTBEAT_PARAM, SEND_MESSAGE_TOOL_NAME
from letta.schemas.agent import AgentState
from letta.schemas.response_format import ResponseFormatType, ResponseFormatUnion
from letta.types import JsonDict, JsonValue
def parse_stdout_best_effort(text: Union[str, bytes]) -> tuple[Any, AgentState | None]:
"""
Decode and unpickle the result from the function execution if possible.
Returns (function_return_value, agent_state).
"""
if not text:
return None, None
if isinstance(text, str):
text = base64.b64decode(text)
result = pickle.loads(text)
agent_state = result["agent_state"]
return result["results"], agent_state
def parse_function_arguments(source_code: str, tool_name: str):
"""Get arguments of a function from its source code"""
tree = ast.parse(source_code)
args = []
for node in ast.walk(tree):
# Handle both sync and async functions
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == tool_name:
for arg in node.args.args:
args.append(arg.arg)
return args
def convert_param_to_str_value(param_type: str, raw_value: JsonValue) -> str:
"""
Convert parameter to Python code representation based on JSON schema type.
TODO (cliandy): increase sanitization checks here to fail at the right place
"""
valid_types = {"string", "integer", "boolean", "number", "array", "object"}
if param_type not in valid_types:
raise TypeError(f"Unsupported type: {param_type}, raw_value={raw_value}")
if param_type == "string":
# Safely handle python string
return repr(raw_value)
if param_type == "integer":
return str(int(raw_value))
if param_type == "boolean":
if isinstance(raw_value, bool):
return str(raw_value)
if isinstance(raw_value, int) and raw_value in (0, 1):
return str(bool(raw_value))
if isinstance(raw_value, str) and raw_value.strip().lower() in ("true", "false"):
return raw_value.strip().lower().capitalize()
raise ValueError(f"Invalid boolean value: {raw_value}")
if param_type == "array":
pass # need more testing here
# if isinstance(raw_value, str):
# if raw_value.strip()[0] != "[" or raw_value.strip()[-1] != "]":
# raise ValueError(f'Invalid array value: "{raw_value}"')
# return raw_value.strip()
return str(raw_value)
def runtime_override_tool_json_schema(
tool_list: list[JsonDict],
response_format: ResponseFormatUnion | None,
request_heartbeat: bool = True,
terminal_tools: set[str] | None = None,
) -> list[JsonDict]:
"""Override the tool JSON schemas at runtime if certain conditions are met.
Cases:
1. We will inject `send_message` tool calls with `response_format` if provided
2. Tools will have an additional `request_heartbeat` parameter added (except for terminal tools).
"""
if terminal_tools is None:
terminal_tools = set()
for tool_json in tool_list:
if tool_json["name"] == SEND_MESSAGE_TOOL_NAME and response_format and response_format.type != ResponseFormatType.text:
if response_format.type == ResponseFormatType.json_schema:
tool_json["parameters"]["properties"]["message"] = response_format.json_schema["schema"]
if response_format.type == ResponseFormatType.json_object:
tool_json["parameters"]["properties"]["message"] = {
"type": "object",
"description": "Message contents. All unicode (including emojis) are supported.",
"additionalProperties": True,
"properties": {},
}
if request_heartbeat:
# Only add request_heartbeat to non-terminal tools
if tool_json["name"] not in terminal_tools:
tool_json["parameters"]["properties"][REQUEST_HEARTBEAT_PARAM] = {
"type": "boolean",
"description": REQUEST_HEARTBEAT_DESCRIPTION,
}
if REQUEST_HEARTBEAT_PARAM not in tool_json["parameters"]["required"]:
tool_json["parameters"]["required"].append(REQUEST_HEARTBEAT_PARAM)
return tool_list