From e6372de09553c47f3dd8d53826db8815dd481119 Mon Sep 17 00:00:00 2001 From: Index Date: Mon, 22 Dec 2025 16:33:26 -0600 Subject: [PATCH] feat: cache post URIs & prevent recursion loop --- src/utils/cache.ts | 42 +++++++++++++++++++++++++++++++++++++++ src/utils/conversation.ts | 10 ++++++++-- src/utils/post.ts | 31 +++++++++++++++++++++-------- 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 src/utils/cache.ts diff --git a/src/utils/cache.ts b/src/utils/cache.ts new file mode 100644 index 0000000..64f8112 --- /dev/null +++ b/src/utils/cache.ts @@ -0,0 +1,42 @@ +interface CacheEntry { + value: T; + expiry: number; +} + +class TimedCache { + private cache = new Map>(); + private ttl: number; // Time to live in milliseconds + + constructor(ttl: number) { + this.ttl = ttl; + } + + get(key: string): T | undefined { + const entry = this.cache.get(key); + if (!entry) { + return undefined; + } + + if (Date.now() > entry.expiry) { + this.cache.delete(key); // Entry expired + return undefined; + } + + return entry.value; + } + + set(key: string, value: T): void { + const expiry = Date.now() + this.ttl; + this.cache.set(key, { value, expiry }); + } + + delete(key: string): void { + this.cache.delete(key); + } + + clear(): void { + this.cache.clear(); + } +} + +export const postCache = new TimedCache(2 * 60 * 1000); // 2 minutes cache diff --git a/src/utils/conversation.ts b/src/utils/conversation.ts index 1b36d43..18a36e2 100644 --- a/src/utils/conversation.ts +++ b/src/utils/conversation.ts @@ -11,6 +11,7 @@ import { and, eq } from "drizzle-orm"; import { env } from "../env"; import { bot, ERROR_MESSAGE, MAX_GRAPHEMES } from "../core"; import { parsePost, parsePostImages, traverseThread } from "./post"; +import { postCache } from "../utils/cache"; /* Utilities @@ -123,14 +124,19 @@ export async function parseConversation( }); } - const post = await bot.getPost(row.postUri); + let post = postCache.get(row.postUri); + if (!post) { + post = await bot.getPost(row.postUri); + postCache.set(row.postUri, post); + } const convoMessages = await getRelevantMessages(row!); let parseResult = null; try { + const parsedPost = await parsePost(post, true, new Set()); parseResult = { context: yaml.dump({ - post: await parsePost(post, true), + post: parsedPost || null, }), messages: convoMessages.map((message) => { const role = message.did == env.DID ? "model" : "user"; diff --git a/src/utils/post.ts b/src/utils/post.ts index c893732..42d1527 100644 --- a/src/utils/post.ts +++ b/src/utils/post.ts @@ -8,14 +8,21 @@ import { import * as c from "../core"; import * as yaml from "js-yaml"; import type { ParsedPost } from "../types"; +import { postCache } from "../utils/cache"; export async function parsePost( post: Post, includeThread: boolean, -): Promise { + seenUris: Set = new Set(), +): Promise { + if (seenUris.has(post.uri)) { + return undefined; + } + seenUris.add(post.uri); + const [images, quotePost, ancestorPosts] = await Promise.all([ parsePostImages(post), - parseQuote(post), + parseQuote(post, seenUris), includeThread ? traverseThread(post) : Promise.resolve(null), ]); @@ -28,23 +35,31 @@ export async function parsePost( ...(quotePost && { quotePost }), ...(ancestorPosts && { thread: { - ancestors: await Promise.all( - ancestorPosts.map((ancestor) => parsePost(ancestor, false)), - ), + ancestors: (await Promise.all( + ancestorPosts.map((ancestor) => parsePost(ancestor, false, seenUris)), + )).filter((post): post is ParsedPost => post !== undefined), }, }), }; } -async function parseQuote(post: Post) { +async function parseQuote(post: Post, seenUris: Set) { if ( !post.embed || (!post.embed.isRecord() && !post.embed.isRecordWithMedia()) ) return undefined; const record = (post.embed as RecordEmbed || RecordWithMediaEmbed).record; - const embedPost = await c.bot.getPost(record.uri); + if (seenUris.has(record.uri)) { + return undefined; + } - return await parsePost(embedPost, false); + let embedPost = postCache.get(record.uri); + if (!embedPost) { + embedPost = await c.bot.getPost(record.uri); + postCache.set(record.uri, embedPost); + } + + return await parsePost(embedPost, false, seenUris); } export function parsePostImages(post: Post) {