From f5a9d832fdc3307cc370c20b4906847857274c96 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Sun, 28 Sep 2025 09:49:43 -0700 Subject: [PATCH] Add self-hosted Letta server support - Remove unused project_id (not needed for agent retrieval) - Add base_url configuration for self-hosted servers - Update all Letta client instantiations to use base_url when configured - Default behavior unchanged (uses cloud API when base_url not set) - Tested with localhost:8283 self-hosted server --- bsky.py | 24 ++++++++++++++---------- config.example.yaml | 7 ++++++- config.yaml.bkp | 30 ++++++++++++++++++++++++++++++ config_loader.py | 2 +- register_tools.py | 10 ++++++++-- register_x_tools.py | 10 ++++++++-- tool_manager.py | 13 ++++++++++--- tools/blocks.py | 19 ++++++++++++++----- 8 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 config.yaml.bkp diff --git a/bsky.py b/bsky.py index ef96fcd..0ea2b5e 100644 --- a/bsky.py +++ b/bsky.py @@ -17,6 +17,7 @@ from utils import ( upsert_block, upsert_agent ) +from config_loader import get_letta_config import bsky_utils from tools.blocks import attach_user_blocks, detach_user_blocks @@ -73,14 +74,18 @@ def log_with_panel(message, title=None, border_color="white"): print(message) -# Create a client with extended timeout for LLM operations -CLIENT= Letta( - token=os.environ["LETTA_API_KEY"], - timeout=600 # 10 minutes timeout for API calls - higher than Cloudflare's 524 timeout -) +# Load Letta configuration from config.yaml +letta_config = get_letta_config() + +# Create a client with configuration from config.yaml +CLIENT_PARAMS = { + 'token': letta_config['api_key'], + 'timeout': letta_config['timeout'] +} +if letta_config.get('base_url'): + CLIENT_PARAMS['base_url'] = letta_config['base_url'] -# Use the "Bluesky" project -PROJECT_ID = "5ec33d52-ab14-4fd6-91b5-9dbc43e888a8" +CLIENT = Letta(**CLIENT_PARAMS) # Notification check delay FETCH_NOTIFICATIONS_DELAY_SEC = 10 # Check every 10 seconds for faster response @@ -165,8 +170,6 @@ def initialize_void(): # Get the configured void agent by ID logger.info("Loading void agent from config...") - from config_loader import get_letta_config - letta_config = get_letta_config() agent_id = letta_config['agent_id'] try: @@ -186,7 +189,8 @@ def initialize_void(): logger.info(f"Agent name: {void_agent.name}") if hasattr(void_agent, 'llm_config'): logger.info(f"Agent model: {void_agent.llm_config.model}") - logger.info(f"Agent project_id: {void_agent.project_id}") + if hasattr(void_agent, 'project_id') and void_agent.project_id: + logger.info(f"Agent project_id: {void_agent.project_id}") if hasattr(void_agent, 'tools'): logger.info(f"Agent has {len(void_agent.tools)} tools") for tool in void_agent.tools[:3]: # Show first 3 tools diff --git a/config.example.yaml b/config.example.yaml index 2eef509..38226f1 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -5,9 +5,14 @@ letta: api_key: "your-letta-api-key-here" timeout: 600 # 10 minutes timeout for API calls - project_id: "c82faea2-3ce8-4aa9-a220-b56433e62c92" # Use your specific project ID agent_id: "agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Your void agent ID + # For cloud API (default - leave base_url unset) + # base_url: "https://app.letta.com" # Optional, defaults to cloud API if not specified + + # For self-hosted Letta server + # base_url: "http://localhost:8283" # Self-hosted Letta server URL + # Bluesky Configuration bluesky: username: "handle.example.com" diff --git a/config.yaml.bkp b/config.yaml.bkp new file mode 100644 index 0000000..f7a5b01 --- /dev/null +++ b/config.yaml.bkp @@ -0,0 +1,30 @@ +# Void Bot Configuration +# Generated by migration script +# Created: 2025-07-12 13:45:55 +# See config.yaml.example for all available options + +bluesky: + password: 2xbh-dpcc-i3uf-meks + pds_uri: https://comind.network + username: void.comind.network +bot: + fetch_notifications_delay: 30 + max_notification_pages: 20 + max_processed_notifications: 10000 +letta: + # mine + # api_key: sk-let-NmYyZTZmMzQtZDYxNC00MDg0LTllMGQtYjFmMDRjNDA1YTEwOjM4YWJiYmJlLWNiNTQtNDIxZi1hOTZjLWNiYmU4NDA1ZDUwOA== + # company + api_key: sk-let-MTNjYjFkOTctYWViNS00NzU3LTk5YzAtM2M5ZmEzY2U1NTUwOmY1Y2FlODA3LTQzYzAtNDM3Yi04MWNlLTA0ZWEyYjkyMzlhNA== + # project_id: 5a5254d5-dabb-407c-a44b-4ad8fc650d6d #cameron-s-project + project_id: d8805cc9-bddd-49f0-bf26-e5d75c4e007a # kaleidoscope + timeout: 600 + agent_id: agent-eabbbdea-3e20-4806-8cf9-595ec4187284 + base_url: https://app.letta.com +x: + api_key: AAAAAAAAAAAAAAAAAAAAAHdV3QEAAAAAwcq9M%2BTKhTMTNs%2Bd%2FOqJUSbash8%3DK8aLEyx59e1ZusC9QDmAxaDwAkXR5KO7eH52meXS8QjWZ4KyKF + consumer_key: QV9hiFjb4qYq7rmzfUh0qB49z + consumer_secret: P3UeHIf6ryNsmiX60XG1wTiaziEJTN19d3qQko5xzKfpto7mDL + access_token: 1950680610282094592-OueEU6CjeIGXiDuR3bXPCzVxROyqNQ + access_token_secret: smfw5Xqvif9wNLH8uFJZW346SByLBGUX6GoxV18T8ZnCn + user_id: "1950680610282094592" diff --git a/config_loader.py b/config_loader.py index 64ff2d7..a3c2377 100644 --- a/config_loader.py +++ b/config_loader.py @@ -174,8 +174,8 @@ def get_letta_config() -> Dict[str, Any]: return { 'api_key': config.get_required('letta.api_key'), 'timeout': config.get('letta.timeout', 600), - 'project_id': config.get_required('letta.project_id'), 'agent_id': config.get_required('letta.agent_id'), + 'base_url': config.get('letta.base_url'), # None uses default cloud API } def get_bluesky_config() -> Dict[str, Any]: diff --git a/register_tools.py b/register_tools.py index 0f677d1..7bc6a32 100755 --- a/register_tools.py +++ b/register_tools.py @@ -130,8 +130,14 @@ def register_tools(agent_id: str = None, tools: List[str] = None): agent_id = letta_config['agent_id'] try: - # Initialize Letta client with API key from config - client = Letta(token=letta_config['api_key'], timeout=letta_config['timeout']) + # Initialize Letta client with API key and base_url from config + client_params = { + 'token': letta_config['api_key'], + 'timeout': letta_config['timeout'] + } + if letta_config.get('base_url'): + client_params['base_url'] = letta_config['base_url'] + client = Letta(**client_params) # Get the agent by ID try: diff --git a/register_x_tools.py b/register_x_tools.py index 2552620..d5a5c03 100644 --- a/register_x_tools.py +++ b/register_x_tools.py @@ -117,8 +117,14 @@ def register_x_tools(agent_id: str = None, tools: List[str] = None): agent_id = letta_config['agent_id'] try: - # Initialize Letta client with API key from config - client = Letta(token=letta_config['api_key'], timeout=letta_config['timeout']) + # Initialize Letta client with API key and base_url from config + client_params = { + 'token': letta_config['api_key'], + 'timeout': letta_config['timeout'] + } + if letta_config.get('base_url'): + client_params['base_url'] = letta_config['base_url'] + client = Letta(**client_params) # Get the agent by ID try: diff --git a/tool_manager.py b/tool_manager.py index a970de1..ad352cf 100644 --- a/tool_manager.py +++ b/tool_manager.py @@ -71,8 +71,11 @@ def ensure_platform_tools(platform: str, agent_id: str = None, api_key: str = No api_key = letta_config['api_key'] try: - # Initialize Letta client - client = Letta(token=api_key) + # Initialize Letta client with proper base_url for self-hosted servers + client_params = {'token': api_key} + if letta_config.get('base_url'): + client_params['base_url'] = letta_config['base_url'] + client = Letta(**client_params) # Get the agent try: @@ -155,7 +158,11 @@ def get_attached_tools(agent_id: str = None, api_key: str = None) -> Set[str]: api_key = letta_config['api_key'] try: - client = Letta(token=api_key) + # Initialize Letta client with proper base_url for self-hosted servers + client_params = {'token': api_key} + if letta_config.get('base_url'): + client_params['base_url'] = letta_config['base_url'] + client = Letta(**client_params) agent = client.agents.retrieve(agent_id=agent_id) current_tools = client.agents.tools.list(agent_id=str(agent.id)) return {tool.name for tool in current_tools} diff --git a/tools/blocks.py b/tools/blocks.py index d687054..5ea3228 100644 --- a/tools/blocks.py +++ b/tools/blocks.py @@ -8,7 +8,13 @@ def get_letta_client(): from config_loader import get_letta_config from letta_client import Letta config = get_letta_config() - return Letta(token=config['api_key'], timeout=config['timeout']) + client_params = { + 'token': config['api_key'], + 'timeout': config['timeout'] + } + if config.get('base_url'): + client_params['base_url'] = config['base_url'] + return Letta(**client_params) except (ImportError, FileNotFoundError, KeyError): # Fallback to environment variable import os @@ -32,10 +38,13 @@ def get_x_letta_client(): config = yaml.safe_load(f) letta_config = config.get('letta', {}) - return Letta( - token=letta_config['api_key'], - timeout=letta_config.get('timeout', 600) - ) + client_params = { + 'token': letta_config['api_key'], + 'timeout': letta_config.get('timeout', 600) + } + if letta_config.get('base_url'): + client_params['base_url'] = letta_config['base_url'] + return Letta(**client_params) except (ImportError, FileNotFoundError, KeyError, yaml.YAMLError): # Fall back to regular client return get_letta_client() -- 2.51.2