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