import { bsky } from "./bsky.ts"; import { agentContext } from "./agentContext.ts"; type threadPost = { authorHandle: string; message: string; uri: string; authorDID: string; postedDateTime: string; bookmarks: number; replies: number; reposts: number; likes: number; quotes: number; }; type threadTruncationIndicator = { message: string; }; type threadItem = threadPost | threadTruncationIndicator; export const getCleanThread = async (uri: string): Promise => { const res = await bsky.getPostThread({ uri: uri }); const { thread } = res.data; const postsThread: threadItem[] = []; // Type guard to check if thread is a ThreadViewPost if (thread && "post" in thread) { postsThread.push({ authorHandle: `@${thread.post.author.handle}`, message: (thread.post.record as { text: string }).text, uri: thread.post.uri, authorDID: thread.post.author.did, postedDateTime: (thread.post.record as { createdAt: string }).createdAt, bookmarks: thread.post.bookmarkCount ?? 0, replies: thread.post.replyCount ?? 0, reposts: thread.post.repostCount ?? 0, likes: thread.post.likeCount ?? 0, quotes: thread.post.quoteCount ?? 0, }); // Now traverse the parent chain if ("parent" in thread) { let current = thread.parent; let postCount = 1; // Start at 1 for the main post let wasTruncated = false; // Collect up to configured limit of posts while (current && "post" in current && postCount < agentContext.maxThreadPosts) { postsThread.push({ authorHandle: `@${current.post.author.handle}`, message: (current.post.record as { text: string }).text, uri: current.post.uri, authorDID: current.post.author.did, postedDateTime: (current.post.record as { createdAt: string }).createdAt, bookmarks: current.post.bookmarkCount ?? 0, replies: current.post.replyCount ?? 0, reposts: current.post.repostCount ?? 0, likes: current.post.likeCount ?? 0, quotes: current.post.quoteCount ?? 0, }); postCount++; current = "parent" in current ? current.parent : undefined; } // Check if we stopped early (thread is longer than configured limit) if (current && "post" in current) { wasTruncated = true; // Continue traversing to find the root post without collecting while (current && "parent" in current) { current = current.parent; } // Extract the root post if (current && "post" in current) { postsThread.push({ authorHandle: `@${current.post.author.handle}`, message: (current.post.record as { text: string }).text, uri: current.post.uri, authorDID: current.post.author.did, postedDateTime: (current.post.record as { createdAt: string }).createdAt, bookmarks: current.post.bookmarkCount ?? 0, replies: current.post.replyCount ?? 0, reposts: current.post.repostCount ?? 0, likes: current.post.likeCount ?? 0, quotes: current.post.quoteCount ?? 0, }); } } // Reverse and insert truncation indicator if needed postsThread.reverse(); if (wasTruncated) { const limit = agentContext.maxThreadPosts; const truncationIndicator: threadTruncationIndicator = { message: `This thread exceeded ${limit} posts. This includes the ${limit} most recent posts and the root post that started the thread.`, }; postsThread.splice(1, 0, truncationIndicator); } } } return postsThread; }; export const isThreadPost = (item: threadItem): item is threadPost => { return "authorHandle" in item; };