forked from
cameron.stream/void
this repo has no description
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 dotenv import load_dotenv
8from letta_client import Letta
9from rich.console import Console
10from rich.table import Table
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, user_note_append, user_note_replace, user_note_set, user_note_view, AttachUserBlocksArgs, DetachUserBlocksArgs, UserNoteAppendArgs, UserNoteReplaceArgs, UserNoteSetArgs, UserNoteViewArgs
17from tools.halt import halt_activity, HaltArgs
18from tools.thread import add_post_to_bluesky_reply_thread, ReplyThreadPostArgs
19
20load_dotenv()
21logging.basicConfig(level=logging.INFO)
22logger = logging.getLogger(__name__)
23console = Console()
24
25
26# Tool configurations: function paired with its args_schema and metadata
27TOOL_CONFIGS = [
28 {
29 "func": search_bluesky_posts,
30 "args_schema": SearchArgs,
31 "description": "Search for posts on Bluesky matching the given criteria",
32 "tags": ["bluesky", "search", "posts"]
33 },
34 {
35 "func": create_new_bluesky_post,
36 "args_schema": PostArgs,
37 "description": "Create a new Bluesky post or thread",
38 "tags": ["bluesky", "post", "create", "thread"]
39 },
40 {
41 "func": get_bluesky_feed,
42 "args_schema": FeedArgs,
43 "description": "Retrieve a Bluesky feed (home timeline or custom feed)",
44 "tags": ["bluesky", "feed", "timeline"]
45 },
46 {
47 "func": attach_user_blocks,
48 "args_schema": AttachUserBlocksArgs,
49 "description": "Attach user-specific memory blocks to the agent. Creates blocks if they don't exist.",
50 "tags": ["memory", "blocks", "user"]
51 },
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": user_note_append,
60 "args_schema": UserNoteAppendArgs,
61 "description": "Append a note to a user's memory block. Creates the block if it doesn't exist.",
62 "tags": ["memory", "blocks", "user", "append"]
63 },
64 {
65 "func": user_note_replace,
66 "args_schema": UserNoteReplaceArgs,
67 "description": "Replace text in a user's memory block.",
68 "tags": ["memory", "blocks", "user", "replace"]
69 },
70 {
71 "func": user_note_set,
72 "args_schema": UserNoteSetArgs,
73 "description": "Set the complete content of a user's memory block.",
74 "tags": ["memory", "blocks", "user", "set"]
75 },
76 {
77 "func": user_note_view,
78 "args_schema": UserNoteViewArgs,
79 "description": "View the content of a user's memory block.",
80 "tags": ["memory", "blocks", "user", "view"]
81 },
82 {
83 "func": halt_activity,
84 "args_schema": HaltArgs,
85 "description": "Signal to halt all bot activity and terminate bsky.py",
86 "tags": ["control", "halt", "terminate"]
87 },
88 {
89 "func": add_post_to_bluesky_reply_thread,
90 "args_schema": ReplyThreadPostArgs,
91 "description": "Add a single post to the current Bluesky reply thread atomically",
92 "tags": ["bluesky", "reply", "thread", "atomic"]
93 },
94]
95
96
97def register_tools(agent_name: str = "void", tools: List[str] = None):
98 """Register tools with a Letta agent.
99
100 Args:
101 agent_name: Name of the agent to attach tools to
102 tools: List of tool names to register. If None, registers all tools.
103 """
104 try:
105 # Initialize Letta client with API key
106 client = Letta(token=os.environ["LETTA_API_KEY"])
107
108 # Find the agent
109 agents = client.agents.list()
110 agent = None
111 for a in agents:
112 if a.name == agent_name:
113 agent = a
114 break
115
116 if not agent:
117 console.print(f"[red]Error: Agent '{agent_name}' not found[/red]")
118 console.print("\nAvailable agents:")
119 for a in agents:
120 console.print(f" - {a.name}")
121 return
122
123 # Filter tools if specific ones requested
124 tools_to_register = TOOL_CONFIGS
125 if tools:
126 tools_to_register = [t for t in TOOL_CONFIGS if t["func"].__name__ in tools]
127 if len(tools_to_register) != len(tools):
128 missing = set(tools) - {t["func"].__name__ for t in tools_to_register}
129 console.print(f"[yellow]Warning: Unknown tools: {missing}[/yellow]")
130
131 # Create results table
132 table = Table(title=f"Tool Registration for Agent '{agent_name}'")
133 table.add_column("Tool", style="cyan")
134 table.add_column("Status", style="green")
135 table.add_column("Description")
136
137 # Register each tool
138 for tool_config in tools_to_register:
139 func = tool_config["func"]
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_tools():
177 """List all available tools."""
178 table = Table(title="Available Void 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 TOOL_CONFIGS:
184 table.add_row(
185 tool_config["func"].__name__,
186 tool_config["description"],
187 ", ".join(tool_config["tags"])
188 )
189
190 console.print(table)
191
192
193if __name__ == "__main__":
194 import argparse
195
196 parser = argparse.ArgumentParser(description="Register Void tools with a Letta agent")
197 parser.add_argument("agent", nargs="?", default="void", help="Agent name (default: void)")
198 parser.add_argument("--tools", nargs="+", help="Specific tools to register (default: all)")
199 parser.add_argument("--list", action="store_true", help="List available tools")
200
201 args = parser.parse_args()
202
203 if args.list:
204 list_available_tools()
205 else:
206 console.print(f"\n[bold]Registering tools for agent: {args.agent}[/bold]\n")
207 register_tools(args.agent, args.tools)