a tool to help your Letta AI agents navigate bluesky
at main 3.8 kB view raw
1import { bsky } from "./bsky.ts"; 2import { agentContext } from "./agentContext.ts"; 3 4type threadPost = { 5 authorHandle: string; 6 message: string; 7 uri: string; 8 authorDID: string; 9 postedDateTime: string; 10 bookmarks: number; 11 replies: number; 12 reposts: number; 13 likes: number; 14 quotes: number; 15}; 16 17type threadTruncationIndicator = { 18 message: string; 19}; 20 21type threadItem = threadPost | threadTruncationIndicator; 22 23export const getCleanThread = async (uri: string): Promise<threadItem[]> => { 24 const res = await bsky.getPostThread({ uri: uri }); 25 const { thread } = res.data; 26 27 const postsThread: threadItem[] = []; 28 29 // Type guard to check if thread is a ThreadViewPost 30 if (thread && "post" in thread) { 31 postsThread.push({ 32 authorHandle: `@${thread.post.author.handle}`, 33 message: (thread.post.record as { text: string }).text, 34 uri: thread.post.uri, 35 authorDID: thread.post.author.did, 36 postedDateTime: (thread.post.record as { createdAt: string }).createdAt, 37 bookmarks: thread.post.bookmarkCount ?? 0, 38 replies: thread.post.replyCount ?? 0, 39 reposts: thread.post.repostCount ?? 0, 40 likes: thread.post.likeCount ?? 0, 41 quotes: thread.post.quoteCount ?? 0, 42 }); 43 44 // Now traverse the parent chain 45 if ("parent" in thread) { 46 let current = thread.parent; 47 let postCount = 1; // Start at 1 for the main post 48 let wasTruncated = false; 49 50 // Collect up to configured limit of posts 51 while (current && "post" in current && postCount < agentContext.maxThreadPosts) { 52 postsThread.push({ 53 authorHandle: `@${current.post.author.handle}`, 54 message: (current.post.record as { text: string }).text, 55 uri: current.post.uri, 56 authorDID: current.post.author.did, 57 postedDateTime: (current.post.record as { createdAt: string }).createdAt, 58 bookmarks: current.post.bookmarkCount ?? 0, 59 replies: current.post.replyCount ?? 0, 60 reposts: current.post.repostCount ?? 0, 61 likes: current.post.likeCount ?? 0, 62 quotes: current.post.quoteCount ?? 0, 63 }); 64 postCount++; 65 current = "parent" in current ? current.parent : undefined; 66 } 67 68 // Check if we stopped early (thread is longer than configured limit) 69 if (current && "post" in current) { 70 wasTruncated = true; 71 72 // Continue traversing to find the root post without collecting 73 while (current && "parent" in current) { 74 current = current.parent; 75 } 76 77 // Extract the root post 78 if (current && "post" in current) { 79 postsThread.push({ 80 authorHandle: `@${current.post.author.handle}`, 81 message: (current.post.record as { text: string }).text, 82 uri: current.post.uri, 83 authorDID: current.post.author.did, 84 postedDateTime: (current.post.record as { createdAt: string }).createdAt, 85 bookmarks: current.post.bookmarkCount ?? 0, 86 replies: current.post.replyCount ?? 0, 87 reposts: current.post.repostCount ?? 0, 88 likes: current.post.likeCount ?? 0, 89 quotes: current.post.quoteCount ?? 0, 90 }); 91 } 92 } 93 94 // Reverse and insert truncation indicator if needed 95 postsThread.reverse(); 96 97 if (wasTruncated) { 98 const limit = agentContext.maxThreadPosts; 99 const truncationIndicator: threadTruncationIndicator = { 100 message: `This thread exceeded ${limit} posts. This includes the ${limit} most recent posts and the root post that started the thread.`, 101 }; 102 postsThread.splice(1, 0, truncationIndicator); 103 } 104 } 105 } 106 107 return postsThread; 108}; 109 110export const isThreadPost = (item: threadItem): item is threadPost => { 111 return "authorHandle" in item; 112};