a digital person for bluesky

Add language support for Bluesky replies

- Updated bluesky_reply tool to accept optional lang parameter (defaults to en-US)
- Modified bsky.py to extract language from tool call and pass to reply function
- Updated bsky_utils.py reply functions to accept and use langs parameter
- Posts are now tagged with the specified language code for better filtering

This allows Void to post in different languages by specifying codes like
'es', 'ja', 'th', etc. when using the bluesky_reply tool.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+31 -15
tools
+10 -6
bsky.py
··· 368 368 logger.debug("Successfully received response from Letta API") 369 369 logger.debug(f"Number of messages in response: {len(message_response.messages) if hasattr(message_response, 'messages') else 'N/A'}") 370 370 371 - # Extract the reply text from the agent's response 371 + # Extract the reply text and language from the agent's response 372 372 reply_text = "" 373 + reply_lang = "en-US" # Default language 373 374 logger.debug(f"Processing {len(message_response.messages)} response messages...") 374 375 375 376 for i, message in enumerate(message_response.messages, 1): ··· 392 393 # Check if this is a ToolCallMessage with bluesky_reply tool 393 394 if hasattr(message, 'tool_call') and message.tool_call: 394 395 if message.tool_call.name == 'bluesky_reply': 395 - # Parse the JSON arguments to get the message 396 + # Parse the JSON arguments to get the message and language 396 397 try: 397 398 args = json.loads(message.tool_call.arguments) 398 399 reply_text = args.get('message', '') 399 - logger.info(f"Extracted reply from tool call: {reply_text[:50]}...") 400 + reply_lang = args.get('lang', 'en-US') # Extract language or use default 401 + logger.info(f"Extracted reply from tool call: {reply_text[:50]}... (lang: {reply_lang})") 400 402 break 401 403 except json.JSONDecodeError as e: 402 404 logger.error(f"Failed to parse tool call arguments: {e}") ··· 411 413 print(f"\n=== GENERATED REPLY ===") 412 414 print(f"To: @{author_handle}") 413 415 print(f"Reply: {reply_text}") 416 + print(f"Language: {reply_lang}") 414 417 print(f"======================\n") 415 418 416 - # Send the reply 417 - logger.info(f"Sending reply: {reply_text[:50]}...") 419 + # Send the reply with language 420 + logger.info(f"Sending reply: {reply_text[:50]}... (lang: {reply_lang})") 418 421 response = bsky_utils.reply_to_notification( 419 422 client=atproto_client, 420 423 notification=notification_data, 421 - reply_text=reply_text 424 + reply_text=reply_text, 425 + lang=reply_lang 422 426 ) 423 427 424 428 if response:
+12 -6
bsky_utils.py
··· 253 253 254 254 return init_client(username, password) 255 255 256 - def reply_to_post(client: Client, text: str, reply_to_uri: str, reply_to_cid: str, root_uri: Optional[str] = None, root_cid: Optional[str] = None) -> Dict[str, Any]: 256 + def reply_to_post(client: Client, text: str, reply_to_uri: str, reply_to_cid: str, root_uri: Optional[str] = None, root_cid: Optional[str] = None, lang: Optional[str] = None) -> Dict[str, Any]: 257 257 """ 258 258 Reply to a post on Bluesky with rich text support. 259 259 ··· 264 264 reply_to_cid: The CID of the post being replied to (parent) 265 265 root_uri: The URI of the root post (if replying to a reply). If None, uses reply_to_uri 266 266 root_cid: The CID of the root post (if replying to a reply). If None, uses reply_to_cid 267 + lang: Language code for the post (e.g., 'en-US', 'es', 'ja') 267 268 268 269 Returns: 269 270 The response from sending the post ··· 331 332 response = client.send_post( 332 333 text=text, 333 334 reply_to=models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref), 334 - facets=facets 335 + facets=facets, 336 + langs=[lang] if lang else None 335 337 ) 336 338 else: 337 339 response = client.send_post( 338 340 text=text, 339 - reply_to=models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref) 341 + reply_to=models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref), 342 + langs=[lang] if lang else None 340 343 ) 341 344 342 345 logger.info(f"Reply sent successfully: {response.uri}") ··· 362 365 return None 363 366 364 367 365 - def reply_to_notification(client: Client, notification: Any, reply_text: str) -> Optional[Dict[str, Any]]: 368 + def reply_to_notification(client: Client, notification: Any, reply_text: str, lang: str = "en-US") -> Optional[Dict[str, Any]]: 366 369 """ 367 370 Reply to a notification (mention or reply). 368 371 ··· 370 373 client: Authenticated Bluesky client 371 374 notification: The notification object from list_notifications 372 375 reply_text: The text to reply with 376 + lang: Language code for the post (defaults to "en-US") 373 377 374 378 Returns: 375 379 The response from sending the reply or None if failed ··· 417 421 reply_to_uri=post_uri, 418 422 reply_to_cid=post_cid, 419 423 root_uri=root_uri, 420 - root_cid=root_cid 424 + root_cid=root_cid, 425 + lang=lang 421 426 ) 422 427 else: 423 428 # If we can't get thread data, just reply directly ··· 425 430 client=client, 426 431 text=reply_text, 427 432 reply_to_uri=post_uri, 428 - reply_to_cid=post_cid 433 + reply_to_cid=post_cid, 434 + lang=lang 429 435 ) 430 436 431 437 except Exception as e:
+9 -3
tools/reply.py
··· 1 1 """Reply tool for Bluesky - a simple tool for the Letta agent to indicate a reply.""" 2 + from typing import Optional 2 3 from pydantic import BaseModel, Field, validator 3 4 4 5 ··· 7 8 ..., 8 9 description="The reply message text (max 300 characters)" 9 10 ) 11 + lang: Optional[str] = Field( 12 + default="en-US", 13 + description="Language code for the post (e.g., 'en-US', 'es', 'ja', 'th'). Defaults to 'en-US'" 14 + ) 10 15 11 16 @validator('message') 12 17 def validate_message_length(cls, v): ··· 15 20 return v 16 21 17 22 18 - def bluesky_reply(message: str) -> str: 23 + def bluesky_reply(message: str, lang: str = "en-US") -> str: 19 24 """ 20 25 This is a simple function that returns a string. MUST be less than 300 characters. 21 26 22 27 Args: 23 28 message: The reply text (max 300 characters) 29 + lang: Language code for the post (e.g., 'en-US', 'es', 'ja', 'th'). Defaults to 'en-US' 24 30 25 31 Returns: 26 - Confirmation message 32 + Confirmation message with language info 27 33 28 34 Raises: 29 35 Exception: If message exceeds 300 characters ··· 31 37 if len(message) > 300: 32 38 raise Exception(f"Message cannot be longer than 300 characters (current: {len(message)} characters)") 33 39 34 - return 'Reply sent' 40 + return f'Reply sent (language: {lang})'