a digital person for bluesky

Migrate to Letta SDK v1.0 and rename whitewind tool to blog_post_create

SDK Migration (letta-client 0.1.235 → 1.0.0):
- Letta() constructor: token → api_key
- Method renames: .modify() → .update()
- Streaming: .create_stream() → .stream()
- Pagination: .list() now returns page objects (use .items)

Tool Rename:
- create_whitewind_blog_post → blog_post_create
- WhitewindPostArgs → BlogPostCreateArgs
- Updated docstrings to reference Greengale service (greengale.app)

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

+108 -91
+4 -3
attach_user_block.py
··· 21 """Create and attach a user block to an agent.""" 22 23 # Create client 24 - client = Letta(token=os.environ["LETTA_API_KEY"]) 25 26 # Find the agent 27 agents = client.agents.list(name=agent_name) ··· 36 print(f"🔍 Creating user block for {handle}...") 37 user_block = create_user_block_for_handle(client, handle) 38 39 - # Check if already attached 40 - agent_blocks = client.agents.blocks.list(agent_id=agent.id) 41 for block in agent_blocks: 42 if block.id == user_block.id: 43 print(f"✅ User block for {handle} is already attached to {agent.name}")
··· 21 """Create and attach a user block to an agent.""" 22 23 # Create client 24 + client = Letta(api_key=os.environ["LETTA_API_KEY"]) # v1.0: token → api_key 25 26 # Find the agent 27 agents = client.agents.list(name=agent_name) ··· 36 print(f"🔍 Creating user block for {handle}...") 37 user_block = create_user_block_for_handle(client, handle) 38 39 + # Check if already attached (v1.0: list returns page object) 40 + agent_blocks_page = client.agents.blocks.list(agent_id=agent.id) 41 + agent_blocks = agent_blocks_page.items if hasattr(agent_blocks_page, 'items') else agent_blocks_page 42 for block in agent_blocks: 43 if block.id == user_block.id: 44 print(f"✅ User block for {handle} is already attached to {agent.name}")
+17 -10
bsky.py
··· 412 413 try: 414 # Use streaming to avoid 524 timeout errors 415 - message_stream = CLIENT.agents.messages.create_stream( 416 agent_id=void_agent.id, 417 messages=[{"role": "user", "content": prompt}], 418 stream_tokens=False, # Step streaming only (faster than token streaming) ··· 899 try: 900 # Search for passages with this exact text 901 logger.debug(f"Searching for passages matching: {memory_text[:100]}...") 902 - passages = CLIENT.agents.passages.list( 903 agent_id=void_agent.id, 904 query=memory_text 905 ) 906 907 if not passages: 908 logger.warning(f"No passages found matching flagged memory: {memory_text[:50]}...") ··· 1581 logger.info("🧠 Sending enhanced synthesis prompt to agent") 1582 1583 # Send synthesis message with streaming to show tool use 1584 - message_stream = client.agents.messages.create_stream( 1585 agent_id=agent_id, 1586 messages=[{"role": "user", "content": synthesis_prompt}], 1587 stream_tokens=False, ··· 1762 attached_labels = [] 1763 1764 # Get current blocks attached to agent 1765 - current_blocks = client.agents.blocks.list(agent_id=agent_id) 1766 current_block_labels = {block.label for block in current_blocks} 1767 current_block_ids = {str(block.id) for block in current_blocks} 1768 ··· 1775 continue 1776 1777 # Check if block exists globally 1778 - blocks = client.blocks.list(label=label) 1779 1780 if blocks and len(blocks) > 0: 1781 block = blocks[0] ··· 1853 ] 1854 1855 # Get current blocks and build label to ID mapping 1856 - current_blocks = client.agents.blocks.list(agent_id=agent_id) 1857 block_label_to_id = {block.label: str(block.id) for block in current_blocks} 1858 1859 detached_count = 0 ··· 1908 attached_labels = [] 1909 1910 try: 1911 - current_blocks = client.agents.blocks.list(agent_id=agent_id) 1912 current_block_labels = {block.label for block in current_blocks} 1913 current_block_ids = {str(block.id) for block in current_blocks} 1914 ··· 1923 attached_labels.append(label) 1924 continue 1925 1926 - blocks = client.blocks.list(label=label) 1927 1928 if blocks and len(blocks) > 0: 1929 block = blocks[0] ··· 1978 return True 1979 1980 try: 1981 - current_blocks = client.agents.blocks.list(agent_id=agent_id) 1982 block_label_to_id = {block.label: str(block.id) for block in current_blocks} 1983 1984 detached_count = 0 ··· 2041 2042 # Create Letta client with configuration 2043 CLIENT_PARAMS = { 2044 - 'token': letta_config['api_key'], 2045 'timeout': letta_config['timeout'] 2046 } 2047 if letta_config.get('base_url'):
··· 412 413 try: 414 # Use streaming to avoid 524 timeout errors 415 + message_stream = CLIENT.agents.messages.stream( 416 agent_id=void_agent.id, 417 messages=[{"role": "user", "content": prompt}], 418 stream_tokens=False, # Step streaming only (faster than token streaming) ··· 899 try: 900 # Search for passages with this exact text 901 logger.debug(f"Searching for passages matching: {memory_text[:100]}...") 902 + passages_page = CLIENT.agents.passages.list( 903 agent_id=void_agent.id, 904 query=memory_text 905 ) 906 + passages = passages_page.items if hasattr(passages_page, 'items') else passages_page 907 908 if not passages: 909 logger.warning(f"No passages found matching flagged memory: {memory_text[:50]}...") ··· 1582 logger.info("🧠 Sending enhanced synthesis prompt to agent") 1583 1584 # Send synthesis message with streaming to show tool use 1585 + message_stream = client.agents.messages.stream( 1586 agent_id=agent_id, 1587 messages=[{"role": "user", "content": synthesis_prompt}], 1588 stream_tokens=False, ··· 1763 attached_labels = [] 1764 1765 # Get current blocks attached to agent 1766 + current_blocks_page = client.agents.blocks.list(agent_id=agent_id) 1767 + current_blocks = current_blocks_page.items if hasattr(current_blocks_page, 'items') else current_blocks_page 1768 current_block_labels = {block.label for block in current_blocks} 1769 current_block_ids = {str(block.id) for block in current_blocks} 1770 ··· 1777 continue 1778 1779 # Check if block exists globally 1780 + blocks_page = client.blocks.list(label=label) 1781 + blocks = blocks_page.items if hasattr(blocks_page, 'items') else blocks_page 1782 1783 if blocks and len(blocks) > 0: 1784 block = blocks[0] ··· 1856 ] 1857 1858 # Get current blocks and build label to ID mapping 1859 + current_blocks_page = client.agents.blocks.list(agent_id=agent_id) 1860 + current_blocks = current_blocks_page.items if hasattr(current_blocks_page, 'items') else current_blocks_page 1861 block_label_to_id = {block.label: str(block.id) for block in current_blocks} 1862 1863 detached_count = 0 ··· 1912 attached_labels = [] 1913 1914 try: 1915 + current_blocks_page = client.agents.blocks.list(agent_id=agent_id) 1916 + current_blocks = current_blocks_page.items if hasattr(current_blocks_page, 'items') else current_blocks_page 1917 current_block_labels = {block.label for block in current_blocks} 1918 current_block_ids = {str(block.id) for block in current_blocks} 1919 ··· 1928 attached_labels.append(label) 1929 continue 1930 1931 + blocks_page = client.blocks.list(label=label) 1932 + blocks = blocks_page.items if hasattr(blocks_page, 'items') else blocks_page 1933 1934 if blocks and len(blocks) > 0: 1935 block = blocks[0] ··· 1984 return True 1985 1986 try: 1987 + current_blocks_page = client.agents.blocks.list(agent_id=agent_id) 1988 + current_blocks = current_blocks_page.items if hasattr(current_blocks_page, 'items') else current_blocks_page 1989 block_label_to_id = {block.label: str(block.id) for block in current_blocks} 1990 1991 detached_count = 0 ··· 2048 2049 # Create Letta client with configuration 2050 CLIENT_PARAMS = { 2051 + 'api_key': letta_config['api_key'], # v1.0: token → api_key 2052 'timeout': letta_config['timeout'] 2053 } 2054 if letta_config.get('base_url'):
+11 -10
register_tools.py
··· 16 from tools.halt import halt_activity, HaltArgs 17 from tools.thread import add_post_to_bluesky_reply_thread, ReplyThreadPostArgs 18 from tools.ignore import ignore_notification, IgnoreNotificationArgs 19 - from tools.whitewind import create_whitewind_blog_post, WhitewindPostArgs 20 from tools.ack import annotate_ack, AnnotateAckArgs 21 from tools.webpage import fetch_webpage, WebpageArgs 22 from tools.flag_memory_deletion import flag_archival_memory_for_deletion, FlagArchivalMemoryForDeletionArgs ··· 65 "tags": ["notification", "ignore", "control", "bot"] 66 }, 67 { 68 - "func": create_whitewind_blog_post, 69 - "args_schema": WhitewindPostArgs, 70 - "description": "Create a blog post on Whitewind with markdown support", 71 - "tags": ["whitewind", "blog", "post", "markdown"] 72 }, 73 { 74 "func": annotate_ack, ··· 109 try: 110 # Initialize Letta client with API key and base_url from config 111 client_params = { 112 - 'token': letta_config['api_key'], 113 'timeout': letta_config['timeout'] 114 } 115 if letta_config.get('base_url'): ··· 139 console.print(f" PDS_URI: {env_vars['PDS_URI']}") 140 console.print(f" BSKY_PASSWORD: {'*' * len(env_vars['BSKY_PASSWORD'])}\n") 141 142 - # Modify agent with environment variables 143 - client.agents.modify( 144 agent_id=agent_id, 145 tool_exec_environment_variables=env_vars 146 ) ··· 177 tags=tool_config["tags"] 178 ) 179 180 - # Get current agent tools 181 - current_tools = client.agents.tools.list(agent_id=str(agent.id)) 182 tool_names = [t.name for t in current_tools] 183 184 # Check if already attached
··· 16 from tools.halt import halt_activity, HaltArgs 17 from tools.thread import add_post_to_bluesky_reply_thread, ReplyThreadPostArgs 18 from tools.ignore import ignore_notification, IgnoreNotificationArgs 19 + from tools.whitewind import blog_post_create, BlogPostCreateArgs 20 from tools.ack import annotate_ack, AnnotateAckArgs 21 from tools.webpage import fetch_webpage, WebpageArgs 22 from tools.flag_memory_deletion import flag_archival_memory_for_deletion, FlagArchivalMemoryForDeletionArgs ··· 65 "tags": ["notification", "ignore", "control", "bot"] 66 }, 67 { 68 + "func": blog_post_create, 69 + "args_schema": BlogPostCreateArgs, 70 + "description": "Create a blog post on Greengale (served at greengale.app) with markdown support", 71 + "tags": ["greengale", "blog", "post", "markdown"] 72 }, 73 { 74 "func": annotate_ack, ··· 109 try: 110 # Initialize Letta client with API key and base_url from config 111 client_params = { 112 + 'api_key': letta_config['api_key'], # v1.0: token → api_key 113 'timeout': letta_config['timeout'] 114 } 115 if letta_config.get('base_url'): ··· 139 console.print(f" PDS_URI: {env_vars['PDS_URI']}") 140 console.print(f" BSKY_PASSWORD: {'*' * len(env_vars['BSKY_PASSWORD'])}\n") 141 142 + # Update agent with environment variables (v1.0: modify → update) 143 + client.agents.update( 144 agent_id=agent_id, 145 tool_exec_environment_variables=env_vars 146 ) ··· 177 tags=tool_config["tags"] 178 ) 179 180 + # Get current agent tools (v1.0: list returns page object) 181 + current_tools_page = client.agents.tools.list(agent_id=str(agent.id)) 182 + current_tools = current_tools_page.items if hasattr(current_tools_page, 'items') else current_tools_page 183 tool_names = [t.name for t in current_tools] 184 185 # Check if already attached
+4 -3
register_x_tools.py
··· 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'): ··· 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
··· 101 try: 102 # Initialize Letta client with API key and base_url from config 103 client_params = { 104 + 'api_key': letta_config['api_key'], # v1.0: token → api_key 105 'timeout': letta_config['timeout'] 106 } 107 if letta_config.get('base_url'): ··· 147 tags=tool_config["tags"] 148 ) 149 150 + # Get current agent tools (v1.0: list returns page object) 151 + current_tools_page = client.agents.tools.list(agent_id=str(agent.id)) 152 + current_tools = current_tools_page.items if hasattr(current_tools_page, 'items') else current_tools_page 153 tool_names = [t.name for t in current_tools] 154 155 # Check if already attached
+1 -1
requirements.txt
··· 12 httpx==0.28.1 13 httpx-sse==0.4.0 14 idna==3.10 15 - letta-client==0.1.235 16 libipld==3.1.1 17 markdown-it-py==3.0.0 18 mdurl==0.1.2
··· 12 httpx==0.28.1 13 httpx-sse==0.4.0 14 idna==3.10 15 + letta-client==1.0.0 16 libipld==3.1.1 17 markdown-it-py==3.0.0 18 mdurl==0.1.2
+8 -7
send_to_void.py
··· 11 """Send a message to void and stream the response.""" 12 load_dotenv() 13 14 - # Create Letta client 15 client = Letta( 16 base_url=os.getenv("LETTA_BASE_URL", "http://localhost:8283"), 17 - token=os.getenv("LETTA_API_KEY") 18 ) 19 20 - # Get the void agent 21 - agents = client.agents.list() 22 void_agent = next((a for a in agents if a.name == "void"), None) 23 24 if not void_agent: ··· 30 31 # Send message and stream response 32 try: 33 - # Use the streaming interface 34 - message_stream = client.agents.messages.create_stream( 35 agent_id=void_agent.id, 36 messages=[{"role": "user", "content": message}], 37 stream_tokens=False, # Step streaming only ··· 87 send_message_to_void(message) 88 89 if __name__ == "__main__": 90 - main()
··· 11 """Send a message to void and stream the response.""" 12 load_dotenv() 13 14 + # Create Letta client (v1.0: token → api_key) 15 client = Letta( 16 base_url=os.getenv("LETTA_BASE_URL", "http://localhost:8283"), 17 + api_key=os.getenv("LETTA_API_KEY") 18 ) 19 20 + # Get the void agent (v1.0: list returns page object) 21 + agents_page = client.agents.list() 22 + agents = agents_page.items if hasattr(agents_page, 'items') else agents_page 23 void_agent = next((a for a in agents if a.name == "void"), None) 24 25 if not void_agent: ··· 31 32 # Send message and stream response 33 try: 34 + # Use the streaming interface (v1.0: create_stream → stream) 35 + message_stream = client.agents.messages.stream( 36 agent_id=void_agent.id, 37 messages=[{"role": "user", "content": message}], 38 stream_tokens=False, # Step streaming only ··· 88 send_message_to_void(message) 89 90 if __name__ == "__main__": 91 + main()
+16 -10
show_agent_capabilities.py
··· 9 def show_agent_capabilities(): 10 """Display the capabilities of both agents.""" 11 12 - client = Letta(token=os.environ["LETTA_API_KEY"]) 13 14 print("🤖 LETTA AGENT CAPABILITIES") 15 print("=" * 50) 16 17 - # Profile Researcher Agent 18 - researchers = client.agents.list(name="profile-researcher") 19 if researchers: 20 researcher = researchers[0] 21 print(f"\n📊 PROFILE RESEARCHER AGENT") 22 print(f" ID: {researcher.id}") 23 print(f" Name: {researcher.name}") 24 25 - researcher_tools = client.agents.tools.list(agent_id=researcher.id) 26 print(f" Tools ({len(researcher_tools)}):") 27 for tool in researcher_tools: 28 print(f" - {tool.name}") 29 30 - researcher_blocks = client.agents.blocks.list(agent_id=researcher.id) 31 print(f" Memory Blocks ({len(researcher_blocks)}):") 32 for block in researcher_blocks: 33 print(f" - {block.label}") 34 35 - # Void Agent 36 - voids = client.agents.list(name="void") 37 if voids: 38 void = voids[0] 39 print(f"\n🌌 VOID AGENT") 40 print(f" ID: {void.id}") 41 print(f" Name: {void.name}") 42 43 - void_tools = client.agents.tools.list(agent_id=void.id) 44 print(f" Tools ({len(void_tools)}):") 45 for tool in void_tools: 46 print(f" - {tool.name}") 47 48 - void_blocks = client.agents.blocks.list(agent_id=void.id) 49 print(f" Memory Blocks ({len(void_blocks)}):") 50 for block in void_blocks: 51 print(f" - {block.label}") ··· 60 print(f" Void Agent: 'Attach user block for cameron.pfiffer.org before responding'") 61 62 if __name__ == "__main__": 63 - show_agent_capabilities()
··· 9 def show_agent_capabilities(): 10 """Display the capabilities of both agents.""" 11 12 + client = Letta(api_key=os.environ["LETTA_API_KEY"]) # v1.0: token → api_key 13 14 print("🤖 LETTA AGENT CAPABILITIES") 15 print("=" * 50) 16 17 + # Profile Researcher Agent (v1.0: list returns page object) 18 + researchers_page = client.agents.list(name="profile-researcher") 19 + researchers = researchers_page.items if hasattr(researchers_page, 'items') else researchers_page 20 if researchers: 21 researcher = researchers[0] 22 print(f"\n📊 PROFILE RESEARCHER AGENT") 23 print(f" ID: {researcher.id}") 24 print(f" Name: {researcher.name}") 25 26 + researcher_tools_page = client.agents.tools.list(agent_id=researcher.id) 27 + researcher_tools = researcher_tools_page.items if hasattr(researcher_tools_page, 'items') else researcher_tools_page 28 print(f" Tools ({len(researcher_tools)}):") 29 for tool in researcher_tools: 30 print(f" - {tool.name}") 31 32 + researcher_blocks_page = client.agents.blocks.list(agent_id=researcher.id) 33 + researcher_blocks = researcher_blocks_page.items if hasattr(researcher_blocks_page, 'items') else researcher_blocks_page 34 print(f" Memory Blocks ({len(researcher_blocks)}):") 35 for block in researcher_blocks: 36 print(f" - {block.label}") 37 38 + # Void Agent (v1.0: list returns page object) 39 + voids_page = client.agents.list(name="void") 40 + voids = voids_page.items if hasattr(voids_page, 'items') else voids_page 41 if voids: 42 void = voids[0] 43 print(f"\n🌌 VOID AGENT") 44 print(f" ID: {void.id}") 45 print(f" Name: {void.name}") 46 47 + void_tools_page = client.agents.tools.list(agent_id=void.id) 48 + void_tools = void_tools_page.items if hasattr(void_tools_page, 'items') else void_tools_page 49 print(f" Tools ({len(void_tools)}):") 50 for tool in void_tools: 51 print(f" - {tool.name}") 52 53 + void_blocks_page = client.agents.blocks.list(agent_id=void.id) 54 + void_blocks = void_blocks_page.items if hasattr(void_blocks_page, 'items') else void_blocks_page 55 print(f" Memory Blocks ({len(void_blocks)}):") 56 for block in void_blocks: 57 print(f" - {block.label}") ··· 66 print(f" Void Agent: 'Attach user block for cameron.pfiffer.org before responding'") 67 68 if __name__ == "__main__": 69 + show_agent_capabilities()
+6 -4
tool_manager.py
··· 37 'halt_activity', 38 'ignore_notification', 39 'annotate_ack', 40 - 'create_whitewind_blog_post', 41 'fetch_webpage', 42 } 43 ··· 72 73 try: 74 # Initialize Letta client with proper base_url for self-hosted servers 75 - client_params = {'token': api_key} 76 if letta_config.get('base_url'): 77 client_params['base_url'] = letta_config['base_url'] 78 client = Letta(**client_params) ··· 159 160 try: 161 # Initialize Letta client with proper base_url for self-hosted servers 162 - client_params = {'token': api_key} 163 if letta_config.get('base_url'): 164 client_params['base_url'] = letta_config['base_url'] 165 client = Letta(**client_params) 166 agent = client.agents.retrieve(agent_id=agent_id) 167 - current_tools = client.agents.tools.list(agent_id=str(agent.id)) 168 return {tool.name for tool in current_tools} 169 except Exception as e: 170 logger.error(f"Error getting attached tools: {e}")
··· 37 'halt_activity', 38 'ignore_notification', 39 'annotate_ack', 40 + 'blog_post_create', 41 'fetch_webpage', 42 } 43 ··· 72 73 try: 74 # Initialize Letta client with proper base_url for self-hosted servers 75 + client_params = {'api_key': api_key} # v1.0: token → api_key 76 if letta_config.get('base_url'): 77 client_params['base_url'] = letta_config['base_url'] 78 client = Letta(**client_params) ··· 159 160 try: 161 # Initialize Letta client with proper base_url for self-hosted servers 162 + client_params = {'api_key': api_key} # v1.0: token → api_key 163 if letta_config.get('base_url'): 164 client_params['base_url'] = letta_config['base_url'] 165 client = Letta(**client_params) 166 agent = client.agents.retrieve(agent_id=agent_id) 167 + # v1.0: list returns page object 168 + current_tools_page = client.agents.tools.list(agent_id=str(agent.id)) 169 + current_tools = current_tools_page.items if hasattr(current_tools_page, 'items') else current_tools_page 170 return {tool.name for tool in current_tools} 171 except Exception as e: 172 logger.error(f"Error getting attached tools: {e}")
+3 -3
tools/__init__.py
··· 3 from .search import search_bluesky_posts, SearchArgs 4 from .post import create_new_bluesky_post, PostArgs 5 from .feed import get_bluesky_feed, FeedArgs 6 - from .whitewind import create_whitewind_blog_post, WhitewindPostArgs 7 from .ack import annotate_ack, AnnotateAckArgs 8 9 __all__ = [ ··· 11 "search_bluesky_posts", 12 "create_new_bluesky_post", 13 "get_bluesky_feed", 14 - "create_whitewind_blog_post", 15 "annotate_ack", 16 # Pydantic models 17 "SearchArgs", 18 "PostArgs", 19 "FeedArgs", 20 - "WhitewindPostArgs", 21 "AnnotateAckArgs", 22 ]
··· 3 from .search import search_bluesky_posts, SearchArgs 4 from .post import create_new_bluesky_post, PostArgs 5 from .feed import get_bluesky_feed, FeedArgs 6 + from .whitewind import blog_post_create, BlogPostCreateArgs 7 from .ack import annotate_ack, AnnotateAckArgs 8 9 __all__ = [ ··· 11 "search_bluesky_posts", 12 "create_new_bluesky_post", 13 "get_bluesky_feed", 14 + "blog_post_create", 15 "annotate_ack", 16 # Pydantic models 17 "SearchArgs", 18 "PostArgs", 19 "FeedArgs", 20 + "BlogPostCreateArgs", 21 "AnnotateAckArgs", 22 ]
+4 -2
tools/bot_detection.py
··· 31 32 try: 33 # Create Letta client inline (for cloud execution) 34 - client = Letta(token=os.environ["LETTA_API_KEY"]) 35 36 # Get all blocks attached to the agent to check if known_bots is mounted 37 - attached_blocks = client.agents.blocks.list(agent_id=str(agent_state.id)) 38 attached_labels = {block.label for block in attached_blocks} 39 40 if "known_bots" not in attached_labels:
··· 31 32 try: 33 # Create Letta client inline (for cloud execution) 34 + client = Letta(api_key=os.environ["LETTA_API_KEY"]) # v1.0: token → api_key 35 36 # Get all blocks attached to the agent to check if known_bots is mounted 37 + # v1.0: list returns page object 38 + attached_blocks_page = client.agents.blocks.list(agent_id=str(agent_state.id)) 39 + attached_blocks = attached_blocks_page.items if hasattr(attached_blocks_page, 'items') else attached_blocks_page 40 attached_labels = {block.label for block in attached_blocks} 41 42 if "known_bots" not in attached_labels:
+19 -13
tools/whitewind.py
··· 1 - """Whitewind blog post creation tool.""" 2 from typing import Optional 3 from pydantic import BaseModel, Field 4 5 6 - class WhitewindPostArgs(BaseModel): 7 title: str = Field( 8 ..., 9 description="Title of the blog post" ··· 18 ) 19 20 21 - def create_whitewind_blog_post(title: str, content: str, subtitle: Optional[str] = None) -> str: 22 """ 23 - Create a new blog post on Whitewind. 24 25 - This tool creates blog posts using the com.whtwnd.blog.entry lexicon on the ATProto network. 26 - The posts are publicly visible and use the github-light theme. 27 28 Args: 29 title: Title of the blog post ··· 31 subtitle: Optional subtitle for the blog post 32 33 Returns: 34 - Success message with the blog post URL 35 36 Raises: 37 Exception: If the post creation fails ··· 70 now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") 71 72 blog_record = { 73 - "$type": "com.whtwnd.blog.entry", 74 "theme": "github-light", 75 "title": title, 76 "content": content, ··· 88 89 create_data = { 90 "repo": user_did, 91 - "collection": "com.whtwnd.blog.entry", 92 "record": blog_record 93 } 94 ··· 100 post_uri = result.get("uri") 101 if post_uri: 102 rkey = post_uri.split("/")[-1] 103 - # Construct the Whitewind blog URL 104 - blog_url = f"https://whtwnd.com/{handle}/{rkey}" 105 else: 106 blog_url = "URL generation failed" 107 108 # Build success message 109 success_parts = [ 110 - f"Successfully created Whitewind blog post!", 111 f"Title: {title}" 112 ] 113 if subtitle: ··· 121 return "\n".join(success_parts) 122 123 except Exception as e: 124 - raise Exception(f"Error creating Whitewind blog post: {str(e)}")
··· 1 + """Greengale blog post creation tool. 2 + 3 + Creates blog posts on Greengale (https://greengale.app), an ATProto-based blogging service. 4 + Posts are created using the app.greengale.blog.entry lexicon and served at greengale.app. 5 + """ 6 from typing import Optional 7 from pydantic import BaseModel, Field 8 9 10 + class BlogPostCreateArgs(BaseModel): 11 title: str = Field( 12 ..., 13 description="Title of the blog post" ··· 22 ) 23 24 25 + def blog_post_create(title: str, content: str, subtitle: Optional[str] = None) -> str: 26 """ 27 + Create a new blog post on Greengale. 28 29 + This tool creates blog posts using the app.greengale.blog.entry lexicon on the ATProto network. 30 + Blog posts are publicly visible and use the github-light theme. 31 + 32 + The blog post will be served at: https://greengale.app/{handle}/{post_id} 33 34 Args: 35 title: Title of the blog post ··· 37 subtitle: Optional subtitle for the blog post 38 39 Returns: 40 + Success message with the blog post URL on greengale.app 41 42 Raises: 43 Exception: If the post creation fails ··· 76 now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") 77 78 blog_record = { 79 + "$type": "app.greengale.blog.entry", 80 "theme": "github-light", 81 "title": title, 82 "content": content, ··· 94 95 create_data = { 96 "repo": user_did, 97 + "collection": "app.greengale.blog.entry", 98 "record": blog_record 99 } 100 ··· 106 post_uri = result.get("uri") 107 if post_uri: 108 rkey = post_uri.split("/")[-1] 109 + # Construct the Greengale blog URL 110 + blog_url = f"https://greengale.app/{handle}/{rkey}" 111 else: 112 blog_url = "URL generation failed" 113 114 # Build success message 115 success_parts = [ 116 + f"Successfully created blog post on Greengale!", 117 f"Title: {title}" 118 ] 119 if subtitle: ··· 127 return "\n".join(success_parts) 128 129 except Exception as e: 130 + raise Exception(f"Error creating blog post: {str(e)}")
+11 -21
utils.py
··· 6 Ensures that a block by this label exists. If the block exists, it will 7 replace content provided by kwargs with the values in this function call. 8 """ 9 - # Get the list of blocks 10 - blocks = letta.blocks.list(label=label) 11 12 # Check if we had any -- if not, create it 13 if len(blocks) == 0: ··· 27 existing_block = blocks[0] 28 29 if kwargs.get('update', False): 30 - # Remove 'update' from kwargs before passing to modify 31 kwargs_copy = kwargs.copy() 32 kwargs_copy.pop('update', None) 33 34 - updated_block = letta.blocks.modify( 35 block_id = existing_block.id, 36 label = label, 37 value = value, ··· 47 Ensures that an agent by this label exists. If the agent exists, it will 48 update the agent to match kwargs. 49 """ 50 - # Get the list of agents 51 - agents = letta.agents.list(name=name) 52 53 # Check if we had any -- if not, create it 54 if len(agents) == 0: ··· 61 return new_agent 62 63 if len(agents) > 1: 64 - raise Exception(f"{len(agents)} agents by the label '{label}' retrieved, label must identify a unique agent") 65 66 else: 67 existing_agent = agents[0] 68 69 if kwargs.get('update', False): 70 - # Remove 'update' from kwargs before passing to modify 71 kwargs_copy = kwargs.copy() 72 kwargs_copy.pop('update', None) 73 74 - updated_agent = letta.agents.modify( 75 agent_id = existing_agent.id, 76 **kwargs_copy 77 ) ··· 79 return updated_agent 80 else: 81 return existing_agent 82 - 83 - 84 - 85 - 86 - 87 - 88 - 89 - 90 - 91 - 92 - 93 -
··· 6 Ensures that a block by this label exists. If the block exists, it will 7 replace content provided by kwargs with the values in this function call. 8 """ 9 + # Get the list of blocks (v1.0: list returns page object) 10 + blocks_page = letta.blocks.list(label=label) 11 + blocks = blocks_page.items if hasattr(blocks_page, 'items') else blocks_page 12 13 # Check if we had any -- if not, create it 14 if len(blocks) == 0: ··· 28 existing_block = blocks[0] 29 30 if kwargs.get('update', False): 31 + # Remove 'update' from kwargs before passing to update 32 kwargs_copy = kwargs.copy() 33 kwargs_copy.pop('update', None) 34 35 + updated_block = letta.blocks.update( 36 block_id = existing_block.id, 37 label = label, 38 value = value, ··· 48 Ensures that an agent by this label exists. If the agent exists, it will 49 update the agent to match kwargs. 50 """ 51 + # Get the list of agents (v1.0: list returns page object) 52 + agents_page = letta.agents.list(name=name) 53 + agents = agents_page.items if hasattr(agents_page, 'items') else agents_page 54 55 # Check if we had any -- if not, create it 56 if len(agents) == 0: ··· 63 return new_agent 64 65 if len(agents) > 1: 66 + raise Exception(f"{len(agents)} agents by the name '{name}' retrieved, name must identify a unique agent") 67 68 else: 69 existing_agent = agents[0] 70 71 if kwargs.get('update', False): 72 + # Remove 'update' from kwargs before passing to update 73 kwargs_copy = kwargs.copy() 74 kwargs_copy.pop('update', None) 75 76 + updated_agent = letta.agents.update( 77 agent_id = existing_agent.id, 78 **kwargs_copy 79 ) ··· 81 return updated_agent 82 else: 83 return existing_agent
+4 -4
x.py
··· 1280 rprint(Panel(prompt, title=f"Prompt ({prompt_char_count} chars)", border_style="blue")) 1281 1282 # Send to Letta agent using streaming 1283 - message_stream = letta_client.agents.messages.create_stream( 1284 agent_id=agent_id, 1285 messages=[{"role": "user", "content": prompt}], 1286 stream_tokens=False, ··· 1590 from letta_client import Letta 1591 1592 config = get_x_letta_config() 1593 - letta_client = Letta(token=config['api_key'], timeout=config['timeout']) 1594 1595 prompt_char_count = len(prompt) 1596 logger.debug(f"Sending to LLM: @{author_username} mention | msg: \"{mention_text[:50]}...\" | context: {len(thread_context)} chars | prompt: {prompt_char_count} chars") 1597 1598 try: 1599 # Use streaming to avoid timeout errors 1600 - message_stream = letta_client.agents.messages.create_stream( 1601 agent_id=void_agent.id, 1602 messages=[{"role": "user", "content": prompt}], 1603 stream_tokens=False, ··· 2055 2056 # Get Letta client for periodic cleanup 2057 config = get_x_letta_config() 2058 - letta_client = Letta(token=config['api_key'], timeout=config['timeout']) 2059 2060 # Main loop 2061 FETCH_DELAY_SEC = 120 # Check every 2 minutes for X mentions (reduced from 60s to conserve API calls)
··· 1280 rprint(Panel(prompt, title=f"Prompt ({prompt_char_count} chars)", border_style="blue")) 1281 1282 # Send to Letta agent using streaming 1283 + message_stream = letta_client.agents.messages.stream( 1284 agent_id=agent_id, 1285 messages=[{"role": "user", "content": prompt}], 1286 stream_tokens=False, ··· 1590 from letta_client import Letta 1591 1592 config = get_x_letta_config() 1593 + letta_client = Letta(api_key=config['api_key'], timeout=config['timeout']) # v1.0: token → api_key 1594 1595 prompt_char_count = len(prompt) 1596 logger.debug(f"Sending to LLM: @{author_username} mention | msg: \"{mention_text[:50]}...\" | context: {len(thread_context)} chars | prompt: {prompt_char_count} chars") 1597 1598 try: 1599 # Use streaming to avoid timeout errors 1600 + message_stream = letta_client.agents.messages.stream( 1601 agent_id=void_agent.id, 1602 messages=[{"role": "user", "content": prompt}], 1603 stream_tokens=False, ··· 2055 2056 # Get Letta client for periodic cleanup 2057 config = get_x_letta_config() 2058 + letta_client = Letta(api_key=config['api_key'], timeout=config['timeout']) # v1.0: token → api_key 2059 2060 # Main loop 2061 FETCH_DELAY_SEC = 120 # Check every 2 minutes for X mentions (reduced from 60s to conserve API calls)