a digital person for bluesky
at main 7.6 kB view raw
1#!/usr/bin/env python3 2"""Register X-specific tools with a Letta agent.""" 3import logging 4from typing import List 5from letta_client import Letta 6from rich.console import Console 7from rich.table import Table 8from x import get_x_letta_config 9 10# Import standalone functions and their schemas 11from tools.halt import halt_activity, HaltArgs 12from tools.ignore import ignore_notification, IgnoreNotificationArgs 13from tools.whitewind import create_whitewind_blog_post, WhitewindPostArgs 14from tools.ack import annotate_ack, AnnotateAckArgs 15from tools.webpage import fetch_webpage, WebpageArgs 16 17# Import X thread tool 18from tools.x_thread import add_post_to_x_thread, XThreadPostArgs 19 20# Import X post tool 21from tools.x_post import post_to_x, PostToXArgs 22 23# Import X search tool 24from tools.search_x import search_x_posts, SearchXArgs 25 26letta_config = get_x_letta_config() 27logging.basicConfig(level=logging.INFO) 28logger = logging.getLogger(__name__) 29console = Console() 30 31# X-specific tool configurations 32X_TOOL_CONFIGS = [ 33 # Keep these core tools 34 { 35 "func": halt_activity, 36 "args_schema": HaltArgs, 37 "description": "Signal to halt all bot activity and terminate X bot", 38 "tags": ["control", "halt", "terminate"] 39 }, 40 { 41 "func": ignore_notification, 42 "args_schema": IgnoreNotificationArgs, 43 "description": "Explicitly ignore an X notification without replying", 44 "tags": ["notification", "ignore", "control", "bot"] 45 }, 46 { 47 "func": annotate_ack, 48 "args_schema": AnnotateAckArgs, 49 "description": "Add a note to the acknowledgment record for the current X interaction", 50 "tags": ["acknowledgment", "note", "annotation", "metadata"] 51 }, 52 { 53 "func": create_whitewind_blog_post, 54 "args_schema": WhitewindPostArgs, 55 "description": "Create a blog post on Whitewind with markdown support", 56 "tags": ["whitewind", "blog", "post", "markdown"] 57 }, 58 { 59 "func": fetch_webpage, 60 "args_schema": WebpageArgs, 61 "description": "Fetch a webpage and convert it to markdown/text format using Jina AI reader", 62 "tags": ["web", "fetch", "webpage", "markdown", "jina"] 63 }, 64 65 # X thread tool 66 { 67 "func": add_post_to_x_thread, 68 "args_schema": XThreadPostArgs, 69 "description": "Add a single post to the current X reply thread atomically", 70 "tags": ["x", "twitter", "reply", "thread", "atomic"] 71 }, 72 73 # X post tool 74 { 75 "func": post_to_x, 76 "args_schema": PostToXArgs, 77 "description": "Create a new standalone post on X (Twitter)", 78 "tags": ["x", "twitter", "post", "create", "standalone"] 79 }, 80 81 # X search tool 82 { 83 "func": search_x_posts, 84 "args_schema": SearchXArgs, 85 "description": "Get recent posts from a specific X (Twitter) user", 86 "tags": ["x", "twitter", "search", "posts", "user"] 87 } 88] 89 90def register_x_tools(agent_id: str = None, tools: List[str] = None): 91 """Register X-specific tools with a Letta agent. 92 93 Args: 94 agent_id: ID of the agent to attach tools to. If None, uses config default. 95 tools: List of tool names to register. If None, registers all tools. 96 """ 97 # Use agent ID from config if not provided 98 if agent_id is None: 99 agent_id = letta_config['agent_id'] 100 101 try: 102 # Initialize Letta client with API key and base_url from config 103 client_params = { 104 'token': letta_config['api_key'], 105 'timeout': letta_config['timeout'] 106 } 107 if letta_config.get('base_url'): 108 client_params['base_url'] = letta_config['base_url'] 109 client = Letta(**client_params) 110 111 # Get the agent by ID 112 try: 113 agent = client.agents.retrieve(agent_id=agent_id) 114 except Exception as e: 115 console.print(f"[red]Error: Agent '{agent_id}' not found[/red]") 116 console.print(f"Error details: {e}") 117 return 118 119 # Filter tools if specific ones requested 120 tools_to_register = X_TOOL_CONFIGS 121 if tools: 122 tools_to_register = [t for t in X_TOOL_CONFIGS if t["func"] and t["func"].__name__ in tools] 123 if len(tools_to_register) != len(tools): 124 registered_names = {t["func"].__name__ for t in tools_to_register if t["func"]} 125 missing = set(tools) - registered_names 126 console.print(f"[yellow]Warning: Unknown tools: {missing}[/yellow]") 127 128 # Create results table 129 table = Table(title=f"X Tool Registration for Agent '{agent.name}' ({agent_id})") 130 table.add_column("Tool", style="cyan") 131 table.add_column("Status", style="green") 132 table.add_column("Description") 133 134 # Register each tool 135 for tool_config in tools_to_register: 136 func = tool_config["func"] 137 if not func: 138 continue 139 140 tool_name = func.__name__ 141 142 try: 143 # Create or update the tool using the standalone function 144 created_tool = client.tools.upsert_from_function( 145 func=func, 146 args_schema=tool_config["args_schema"], 147 tags=tool_config["tags"] 148 ) 149 150 # Get current agent tools 151 current_tools = client.agents.tools.list(agent_id=str(agent.id)) 152 tool_names = [t.name for t in current_tools] 153 154 # Check if already attached 155 if created_tool.name in tool_names: 156 table.add_row(tool_name, "Already Attached", tool_config["description"]) 157 else: 158 # Attach to agent 159 client.agents.tools.attach( 160 agent_id=str(agent.id), 161 tool_id=str(created_tool.id) 162 ) 163 table.add_row(tool_name, "✓ Attached", tool_config["description"]) 164 165 except Exception as e: 166 table.add_row(tool_name, f"✗ Error: {str(e)}", tool_config["description"]) 167 logger.error(f"Error registering tool {tool_name}: {e}") 168 169 console.print(table) 170 171 except Exception as e: 172 console.print(f"[red]Error: {str(e)}[/red]") 173 logger.error(f"Fatal error: {e}") 174 175 176def list_available_x_tools(): 177 """List all available X tools.""" 178 table = Table(title="Available X Tools") 179 table.add_column("Tool Name", style="cyan") 180 table.add_column("Description") 181 table.add_column("Tags", style="dim") 182 183 for tool_config in X_TOOL_CONFIGS: 184 if tool_config["func"]: 185 table.add_row( 186 tool_config["func"].__name__, 187 tool_config["description"], 188 ", ".join(tool_config["tags"]) 189 ) 190 191 console.print(table) 192 193 194if __name__ == "__main__": 195 import argparse 196 197 parser = argparse.ArgumentParser(description="Register X tools with a Letta agent") 198 parser.add_argument("--agent-id", help=f"Agent ID (default: from config)") 199 parser.add_argument("--tools", nargs="+", help="Specific tools to register (default: all)") 200 parser.add_argument("--list", action="store_true", help="List available tools") 201 202 args = parser.parse_args() 203 204 if args.list: 205 list_available_x_tools() 206 else: 207 # Use config default if no agent specified 208 agent_id = args.agent_id if args.agent_id else letta_config['agent_id'] 209 console.print(f"\n[bold]Registering X tools for agent: {agent_id}[/bold]\n") 210 register_x_tools(agent_id, args.tools)