a digital person for bluesky
at 42e4d0055155dada2db9c31df06a3238decf3328 166 lines 6.1 kB view raw
1#!/usr/bin/env python3 2""" 3Add Bluesky feed retrieval tool to the main void agent. 4""" 5 6import os 7import logging 8from letta_client import Letta 9 10# Configure logging 11logging.basicConfig( 12 level=logging.INFO, 13 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" 14) 15logger = logging.getLogger("add_feed_tool") 16 17def create_feed_tool(client: Letta): 18 """Create the Bluesky feed retrieval tool using Letta SDK.""" 19 20 def get_bluesky_feed(feed_uri: str = None, max_posts: int = 25) -> str: 21 """ 22 Retrieve a Bluesky feed. If no feed_uri provided, gets the authenticated user's home timeline. 23 24 Args: 25 feed_uri: The AT-URI of the feed to retrieve (optional - defaults to home timeline) 26 max_posts: Maximum number of posts to return (default: 25, max: 100) 27 28 Returns: 29 YAML-formatted feed data with posts and metadata 30 """ 31 import os 32 import requests 33 import json 34 import yaml 35 from datetime import datetime 36 37 try: 38 # Get credentials from environment 39 username = os.getenv("BSKY_USERNAME") 40 password = os.getenv("BSKY_PASSWORD") 41 pds_host = os.getenv("PDS_URI", "https://bsky.social") 42 43 if not username or not password: 44 return "Error: BSKY_USERNAME and BSKY_PASSWORD environment variables must be set" 45 46 # Create session 47 session_url = f"{pds_host}/xrpc/com.atproto.server.createSession" 48 session_data = { 49 "identifier": username, 50 "password": password 51 } 52 53 try: 54 session_response = requests.post(session_url, json=session_data, timeout=10) 55 session_response.raise_for_status() 56 session = session_response.json() 57 access_token = session.get("accessJwt") 58 59 if not access_token: 60 return "Error: Failed to get access token from session" 61 except Exception as e: 62 return f"Error: Authentication failed. ({str(e)})" 63 64 # Build feed parameters 65 params = { 66 "limit": min(max_posts, 100) 67 } 68 69 # Determine which endpoint to use 70 if feed_uri: 71 # Use getFeed for custom feeds 72 feed_url = f"{pds_host}/xrpc/app.bsky.feed.getFeed" 73 params["feed"] = feed_uri 74 feed_type = "custom_feed" 75 else: 76 # Use getTimeline for home feed 77 feed_url = f"{pds_host}/xrpc/app.bsky.feed.getTimeline" 78 feed_type = "home_timeline" 79 80 # Make authenticated feed request 81 try: 82 headers = {"Authorization": f"Bearer {access_token}"} 83 feed_response = requests.get(feed_url, params=params, headers=headers, timeout=10) 84 feed_response.raise_for_status() 85 feed_data = feed_response.json() 86 except Exception as e: 87 feed_identifier = feed_uri if feed_uri else "home timeline" 88 return f"Error: Failed to retrieve feed '{feed_identifier}'. ({str(e)})" 89 90 # Build feed results structure 91 results_data = { 92 "feed_data": { 93 "feed_type": feed_type, 94 "feed_uri": feed_uri if feed_uri else "home_timeline", 95 "timestamp": datetime.now().isoformat(), 96 "parameters": { 97 "max_posts": max_posts, 98 "user": username 99 }, 100 "results": feed_data 101 } 102 } 103 104 # Convert to YAML directly without field stripping complications 105 # This avoids the JSON parsing errors we had before 106 return yaml.dump(results_data, default_flow_style=False, allow_unicode=True) 107 108 except Exception as e: 109 error_msg = f"Error retrieving feed: {str(e)}" 110 return error_msg 111 112 # Create the tool using upsert 113 tool = client.tools.upsert_from_function( 114 func=get_bluesky_feed, 115 tags=["bluesky", "feed", "timeline"] 116 ) 117 118 logger.info(f"Created tool: {tool.name} (ID: {tool.id})") 119 return tool 120 121def add_feed_tool_to_void(): 122 """Add feed tool to the void agent.""" 123 124 # Create client 125 client = Letta(token=os.environ["LETTA_API_KEY"]) 126 127 logger.info("Adding feed tool to void agent...") 128 129 # Create the feed tool 130 feed_tool = create_feed_tool(client) 131 132 # Find the void agent 133 agents = client.agents.list(name="void") 134 if not agents: 135 print("❌ Void agent not found") 136 return 137 138 void_agent = agents[0] 139 140 # Get current tools 141 current_tools = client.agents.tools.list(agent_id=void_agent.id) 142 tool_names = [tool.name for tool in current_tools] 143 144 # Add feed tool if not already present 145 if feed_tool.name not in tool_names: 146 client.agents.tools.attach(agent_id=void_agent.id, tool_id=feed_tool.id) 147 logger.info(f"Added {feed_tool.name} to void agent") 148 print(f"✅ Added get_bluesky_feed tool to void agent!") 149 print(f"\nVoid agent can now retrieve Bluesky feeds:") 150 print(f" - Home timeline: 'Show me my home feed'") 151 print(f" - Custom feed: 'Get posts from at://did:plc:xxx/app.bsky.feed.generator/xxx'") 152 print(f" - Limited posts: 'Show me the latest 10 posts from my timeline'") 153 else: 154 logger.info(f"Tool {feed_tool.name} already attached to void agent") 155 print(f"✅ Feed tool already present on void agent") 156 157def main(): 158 """Main function.""" 159 try: 160 add_feed_tool_to_void() 161 except Exception as e: 162 logger.error(f"Error: {e}") 163 print(f"❌ Error: {e}") 164 165if __name__ == "__main__": 166 main()