a digital person for bluesky

Fix reply tool implementation to match post tool pattern

- Remove non-existent base_tool import
- Implement direct API calls matching post tool structure
- Add proper reply reference in post record

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

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

+58 -9
+58 -9
tools/reply.py
··· 2 2 3 3 from typing import Optional 4 4 from pydantic import BaseModel, Field 5 - from atproto import Client 6 - from tools.base_tool import get_bsky_client, log 5 + from pydantic import BaseModel, Field 7 6 8 7 class ReplyArgs(BaseModel): 9 8 """Arguments for replying to a Bluesky post.""" ··· 38 37 if not reply_to_cid: 39 38 raise ValueError("reply_to_cid is required for replies") 40 39 40 + import os 41 + import requests 42 + from datetime import datetime, timezone 43 + 41 44 try: 42 - client = get_bsky_client() 45 + # Get credentials from environment 46 + username = os.getenv("BSKY_USERNAME") 47 + password = os.getenv("BSKY_PASSWORD") 48 + pds_host = os.getenv("PDS_URI", "https://bsky.social") 49 + 50 + if not username or not password: 51 + raise Exception("BSKY_USERNAME and BSKY_PASSWORD environment variables must be set") 52 + 53 + # Create session 54 + session_url = f"{pds_host}/xrpc/com.atproto.server.createSession" 55 + session_data = { 56 + "identifier": username, 57 + "password": password 58 + } 59 + 60 + session_response = requests.post(session_url, json=session_data, timeout=10) 61 + session_response.raise_for_status() 62 + session = session_response.json() 63 + access_token = session.get("accessJwt") 64 + user_did = session.get("did") 65 + 66 + if not access_token or not user_did: 67 + raise Exception("Failed to get access token or DID from session") 68 + 69 + # Build reply record 70 + now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") 43 71 44 72 # Create the reply reference 45 73 reply_ref = { ··· 47 75 "parent": {"uri": reply_to_uri, "cid": reply_to_cid} 48 76 } 49 77 50 - # Send the reply 51 - response = client.send_post(text=text, reply_to=reply_ref) 78 + post_record = { 79 + "$type": "app.bsky.feed.post", 80 + "text": text, 81 + "createdAt": now, 82 + "reply": reply_ref 83 + } 84 + 85 + # Create the post 86 + create_record_url = f"{pds_host}/xrpc/com.atproto.repo.createRecord" 87 + headers = {"Authorization": f"Bearer {access_token}"} 88 + 89 + create_data = { 90 + "repo": user_did, 91 + "collection": "app.bsky.feed.post", 92 + "record": post_record 93 + } 94 + 95 + post_response = requests.post(create_record_url, headers=headers, json=create_data, timeout=10) 96 + post_response.raise_for_status() 97 + result = post_response.json() 52 98 53 - log.info(f"Successfully replied to post {reply_to_uri}") 54 - return f"Successfully posted reply: {response.uri}" 99 + post_uri = result.get("uri") 100 + handle = session.get("handle", username) 101 + rkey = post_uri.split("/")[-1] if post_uri else "" 102 + post_url = f"https://bsky.app/profile/{handle}/post/{rkey}" 103 + 104 + return f"Successfully posted reply to Bluesky!\nReply URL: {post_url}\nText: {text}" 55 105 56 106 except Exception as e: 57 - log.error(f"Failed to reply to post: {str(e)}") 58 - raise 107 + raise Exception(f"Error replying to Bluesky post: {str(e)}")